@loglayer/transport-pretty-terminal 4.0.5 → 4.1.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.
package/dist/index.js CHANGED
@@ -1,742 +1,698 @@
1
- // src/PrettyTerminalTransport.ts
2
1
  import { LoggerlessTransport } from "@loglayer/transport";
3
- import chalk7 from "chalk";
4
-
5
- // src/views/DetailView.ts
6
- import chalk3 from "chalk";
2
+ import * as chalk from "chalk";
3
+ import chalk$1 from "chalk";
7
4
  import wrap from "wrap-ansi";
5
+ import truncate from "cli-truncate";
6
+ import { resolve } from "node:path";
7
+ import Database from "better-sqlite3";
8
+ import keypress from "keypress";
8
9
 
9
- // src/vendor/prettyjson.js
10
- import chalk from "chalk";
11
-
12
- // src/vendor/utils.js
10
+ //#region src/vendor/utils.js
11
+ /**
12
+ * Creates a string with the same length as `numSpaces` parameter
13
+ **/
13
14
  function indent(numSpaces) {
14
- return new Array(numSpaces + 1).join(" ");
15
+ return new Array(numSpaces + 1).join(" ");
15
16
  }
17
+ /**
18
+ * Gets the string length of the longer index in a hash
19
+ **/
16
20
  function getMaxIndexLength(input) {
17
- var maxWidth = 0;
18
- Object.getOwnPropertyNames(input).forEach((key) => {
19
- if (input[key] === void 0) {
20
- return;
21
- }
22
- maxWidth = Math.max(maxWidth, key.length);
23
- });
24
- return maxWidth;
21
+ var maxWidth = 0;
22
+ Object.getOwnPropertyNames(input).forEach((key) => {
23
+ if (input[key] === void 0) return;
24
+ maxWidth = Math.max(maxWidth, key.length);
25
+ });
26
+ return maxWidth;
25
27
  }
26
28
  function isIsoStringDate(isoString) {
27
- if (typeof isoString !== "string") {
28
- return false;
29
- }
30
- if (!/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/.test(isoString)) {
31
- return false;
32
- }
33
- const testDate = new Date(isoString);
34
- return !Number.isNaN(testDate.getTime());
29
+ if (typeof isoString !== "string") return false;
30
+ if (!/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/.test(isoString)) return false;
31
+ const testDate = new Date(isoString);
32
+ return !Number.isNaN(testDate.getTime());
35
33
  }
36
34
 
37
- // src/vendor/prettyjson.js
38
- var conflictChars = /[^\w\s\n\r\v\t.,]/i;
39
- var isPrintable = (input, options) => input !== void 0 || options.renderUndefined;
40
- var isSerializable = (input, onlyPrimitives, options) => {
41
- if (typeof input === "boolean" || typeof input === "number" || typeof input === "function" || input === null || input === void 0 || input instanceof Date) {
42
- return true;
43
- }
44
- if (typeof input === "string" && input.indexOf("\n") === -1) {
45
- return true;
46
- }
47
- if (options.inlineArrays && !onlyPrimitives) {
48
- if (Array.isArray(input) && isSerializable(input[0], true, options)) {
49
- return true;
50
- }
51
- }
52
- return false;
35
+ //#endregion
36
+ //#region src/vendor/prettyjson.js
37
+ const conflictChars = /[^\w\s\n\r\v\t.,]/i;
38
+ const isPrintable = (input, options) => input !== void 0 || options.renderUndefined;
39
+ const isSerializable = (input, onlyPrimitives, options) => {
40
+ if (typeof input === "boolean" || typeof input === "number" || typeof input === "function" || input === null || input === void 0 || input instanceof Date) return true;
41
+ if (typeof input === "string" && input.indexOf("\n") === -1) return true;
42
+ if (options.inlineArrays && !onlyPrimitives) {
43
+ if (Array.isArray(input) && isSerializable(input[0], true, options)) return true;
44
+ }
45
+ return false;
53
46
  };
54
- var getColorRenderer = (colorFn, options) => {
55
- if (options.noColor) {
56
- return (text) => text;
57
- }
58
- if (typeof colorFn !== "function") {
59
- return (text) => text;
60
- }
61
- return colorFn;
47
+ /**
48
+ * @param {Function} colorFn
49
+ * @param {PrettyJSONOptions} options
50
+ */
51
+ const getColorRenderer = (colorFn, options) => {
52
+ if (options.noColor) return (text) => text;
53
+ if (typeof colorFn !== "function") return (text) => text;
54
+ return colorFn;
62
55
  };
63
- var addColorToData = (input, options) => {
64
- if (options.noColor) {
65
- return input;
66
- }
67
- if (input instanceof Date) {
68
- return getColorRenderer(options.dateColor, options)(input.toISOString());
69
- }
70
- if (typeof input === "string") {
71
- if (isIsoStringDate(input)) {
72
- return getColorRenderer(options.dateColor, options)(input);
73
- }
74
- return getColorRenderer(options.stringColor, options)(input);
75
- }
76
- const sInput = `${input}`;
77
- if (typeof input === "boolean") {
78
- return getColorRenderer(options.booleanColor, options)(sInput);
79
- }
80
- if (input === null || input === void 0) {
81
- return getColorRenderer(options.nullUndefinedColor, options)(sInput);
82
- }
83
- if (typeof input === "number") {
84
- if (input >= 0) {
85
- return getColorRenderer(options.positiveNumberColor, options)(sInput);
86
- }
87
- return getColorRenderer(options.negativeNumberColor, options)(sInput);
88
- }
89
- if (typeof input === "function") {
90
- return "function() {}";
91
- }
92
- if (Array.isArray(input)) {
93
- return input.join(", ");
94
- }
95
- return sInput;
56
+ const addColorToData = (input, options) => {
57
+ if (options.noColor) return input;
58
+ if (input instanceof Date) return getColorRenderer(options.dateColor, options)(input.toISOString());
59
+ if (typeof input === "string") {
60
+ if (isIsoStringDate(input)) return getColorRenderer(options.dateColor, options)(input);
61
+ return getColorRenderer(options.stringColor, options)(input);
62
+ }
63
+ const sInput = `${input}`;
64
+ if (typeof input === "boolean") return getColorRenderer(options.booleanColor, options)(sInput);
65
+ if (input === null || input === void 0) return getColorRenderer(options.nullUndefinedColor, options)(sInput);
66
+ if (typeof input === "number") {
67
+ if (input >= 0) return getColorRenderer(options.positiveNumberColor, options)(sInput);
68
+ return getColorRenderer(options.negativeNumberColor, options)(sInput);
69
+ }
70
+ if (typeof input === "function") return "function() {}";
71
+ if (Array.isArray(input)) return input.join(", ");
72
+ return sInput;
96
73
  };
97
- var colorMultilineString = (options, line) => getColorRenderer(options.multilineStringColor, options)(line);
98
- var indentLines = (string, spaces, options) => {
99
- let lines = string.split("\n");
100
- lines = lines.map((line) => indent(spaces) + colorMultilineString(options, line));
101
- return lines.join("\n");
74
+ const colorMultilineString = (options, line) => getColorRenderer(options.multilineStringColor, options)(line);
75
+ const indentLines = (string, spaces, options) => {
76
+ let lines = string.split("\n");
77
+ lines = lines.map((line) => indent(spaces) + colorMultilineString(options, line));
78
+ return lines.join("\n");
102
79
  };
103
- var renderToArray = (data, options, indentation) => {
104
- if (typeof data === "string" && data.match(conflictChars) && options.escape) {
105
- data = JSON.stringify(data);
106
- }
107
- if (!isPrintable(data, options)) {
108
- return [];
109
- }
110
- if (isSerializable(data, false, options)) {
111
- return [indent(indentation) + addColorToData(data, options)];
112
- }
113
- if (typeof data === "string") {
114
- return [
115
- indent(indentation) + colorMultilineString(options, '"""'),
116
- indentLines(data, indentation + options.defaultIndentation, options),
117
- indent(indentation) + colorMultilineString(options, '"""')
118
- ];
119
- }
120
- if (Array.isArray(data)) {
121
- if (data.length === 0) {
122
- return [indent(indentation) + options.emptyArrayMsg];
123
- }
124
- if (options.collapseArrays && data.length > 0) {
125
- return [`${indent(indentation)}[... ${data.length} items]`];
126
- }
127
- const outputArray = [];
128
- data.forEach((element) => {
129
- if (!isPrintable(element, options)) {
130
- return;
131
- }
132
- let line = "- ";
133
- line = getColorRenderer(options.dashColor, options)(line);
134
- line = indent(indentation) + line;
135
- if (isSerializable(element, false, options)) {
136
- line += renderToArray(element, options, 0)[0];
137
- outputArray.push(line);
138
- } else {
139
- outputArray.push(line);
140
- outputArray.push.apply(outputArray, renderToArray(element, options, indentation + options.defaultIndentation));
141
- }
142
- });
143
- return outputArray;
144
- }
145
- if (data instanceof Error) {
146
- return renderToArray(
147
- {
148
- message: data.message,
149
- stack: data.stack.split("\n")
150
- },
151
- options,
152
- indentation
153
- );
154
- }
155
- const maxIndexLength = options.noAlign ? 0 : getMaxIndexLength(data);
156
- let key;
157
- const output = [];
158
- Object.getOwnPropertyNames(data).forEach((i) => {
159
- if (!isPrintable(data[i], options)) {
160
- return;
161
- }
162
- key = `${i}: `;
163
- key = getColorRenderer(options.keysColor, options)(key);
164
- key = indent(indentation) + key;
165
- if (isSerializable(data[i], false, options)) {
166
- const nextIndentation = options.noAlign ? 0 : maxIndexLength - i.length;
167
- key += renderToArray(data[i], options, nextIndentation)[0];
168
- output.push(key);
169
- } else {
170
- output.push(key);
171
- output.push.apply(output, renderToArray(data[i], options, indentation + options.defaultIndentation));
172
- }
173
- });
174
- return output;
80
+ const renderToArray = (data, options, indentation) => {
81
+ if (typeof data === "string" && data.match(conflictChars) && options.escape) data = JSON.stringify(data);
82
+ if (!isPrintable(data, options)) return [];
83
+ if (isSerializable(data, false, options)) return [indent(indentation) + addColorToData(data, options)];
84
+ if (typeof data === "string") return [
85
+ indent(indentation) + colorMultilineString(options, "\"\"\""),
86
+ indentLines(data, indentation + options.defaultIndentation, options),
87
+ indent(indentation) + colorMultilineString(options, "\"\"\"")
88
+ ];
89
+ if (Array.isArray(data)) {
90
+ if (data.length === 0) return [indent(indentation) + options.emptyArrayMsg];
91
+ if (options.collapseArrays && data.length > 0) return [`${indent(indentation)}[... ${data.length} items]`];
92
+ const outputArray = [];
93
+ data.forEach((element) => {
94
+ if (!isPrintable(element, options)) return;
95
+ let line = "- ";
96
+ line = getColorRenderer(options.dashColor, options)(line);
97
+ line = indent(indentation) + line;
98
+ if (isSerializable(element, false, options)) {
99
+ line += renderToArray(element, options, 0)[0];
100
+ outputArray.push(line);
101
+ } else {
102
+ outputArray.push(line);
103
+ outputArray.push.apply(outputArray, renderToArray(element, options, indentation + options.defaultIndentation));
104
+ }
105
+ });
106
+ return outputArray;
107
+ }
108
+ if (data instanceof Error) return renderToArray({
109
+ message: data.message,
110
+ stack: data.stack.split("\n")
111
+ }, options, indentation);
112
+ const maxIndexLength = options.noAlign ? 0 : getMaxIndexLength(data);
113
+ let key;
114
+ const output = [];
115
+ Object.getOwnPropertyNames(data).forEach((i) => {
116
+ if (!isPrintable(data[i], options)) return;
117
+ key = `${i}: `;
118
+ key = getColorRenderer(options.keysColor, options)(key);
119
+ key = indent(indentation) + key;
120
+ if (isSerializable(data[i], false, options)) {
121
+ const nextIndentation = options.noAlign ? 0 : maxIndexLength - i.length;
122
+ key += renderToArray(data[i], options, nextIndentation)[0];
123
+ output.push(key);
124
+ } else {
125
+ output.push(key);
126
+ output.push.apply(output, renderToArray(data[i], options, indentation + options.defaultIndentation));
127
+ }
128
+ });
129
+ return output;
175
130
  };
176
- var validateOptionsAndSetDefaults = (options) => {
177
- options = options || {};
178
- options.emptyArrayMsg = options.emptyArrayMsg || "(empty array)";
179
- options.keysColor = options.keysColor || chalk.green;
180
- options.dashColor = options.dashColor || chalk.green;
181
- options.booleanColor = options.booleanColor || chalk.cyan;
182
- options.nullUndefinedColor = options.nullUndefinedColor || chalk.grey;
183
- options.numberColor = options.numberColor || chalk.blue;
184
- options.positiveNumberColor = options.positiveNumberColor || options.numberColor;
185
- options.negativeNumberColor = options.negativeNumberColor || options.numberColor;
186
- options.dateColor = options.dateColor || chalk.magenta;
187
- options.defaultIndentation = options.defaultIndentation || 2;
188
- options.noColor = !!options.noColor;
189
- options.noAlign = !!options.noAlign;
190
- options.escape = !!options.escape;
191
- options.renderUndefined = !!options.renderUndefined;
192
- options.collapseArrays = !!options.collapseArrays;
193
- options.stringColor = options.stringColor || null;
194
- options.multilineStringColor = options.multilineStringColor || options.stringColor || null;
195
- return options;
131
+ /**
132
+ * @typedef {Object} PrettyJSONOptions
133
+ * @property {Function} [stringColor=null] Chalk color function for strings
134
+ * @property {Function} [multilineStringColor=null] Chalk color function for multiline strings
135
+ * @property {Function} [keysColor=chalk.green] Chalk color function for keys in hashes
136
+ * @property {Function} [dashColor=chalk.green] Chalk color function for dashes in arrays
137
+ * @property {Function} [numberColor=chalk.blue] Default Chalk color function for numbers
138
+ * @property {Function} [positiveNumberColor=numberColor] Chalk color function for positive numbers
139
+ * @property {Function} [negativeNumberColor=numberColor] Chalk color function for negative numbers
140
+ * @property {Function} [booleanColor=chalk.cyan] Chalk color function for boolean values
141
+ * @property {Function} [nullUndefinedColor=chalk.grey] Chalk color function for null || undefined
142
+ * @property {Function} [dateColor=chalk.magenta] Chalk color function for Date objects
143
+ * @property {number} [defaultIndentation=2] Indentation spaces per object level
144
+ * @property {string} [emptyArrayMsg="(empty array)"] Replace empty strings with
145
+ * @property {boolean} [noColor] Flag to disable colors
146
+ * @property {boolean} [noAlign] Flag to disable alignment
147
+ * @property {boolean} [escape] Flag to escape printed content
148
+ * @property {boolean} [collapseArrays] Flag to collapse arrays
149
+ */
150
+ /**
151
+ * Mutating function that ensures we have a valid options object
152
+ * @param {PrettyJSONOptions} options
153
+ * @returns PrettyJSONOptions
154
+ */
155
+ const validateOptionsAndSetDefaults = (options) => {
156
+ options = options || {};
157
+ options.emptyArrayMsg = options.emptyArrayMsg || "(empty array)";
158
+ options.keysColor = options.keysColor || chalk$1.green;
159
+ options.dashColor = options.dashColor || chalk$1.green;
160
+ options.booleanColor = options.booleanColor || chalk$1.cyan;
161
+ options.nullUndefinedColor = options.nullUndefinedColor || chalk$1.grey;
162
+ options.numberColor = options.numberColor || chalk$1.blue;
163
+ options.positiveNumberColor = options.positiveNumberColor || options.numberColor;
164
+ options.negativeNumberColor = options.negativeNumberColor || options.numberColor;
165
+ options.dateColor = options.dateColor || chalk$1.magenta;
166
+ options.defaultIndentation = options.defaultIndentation || 2;
167
+ options.noColor = !!options.noColor;
168
+ options.noAlign = !!options.noAlign;
169
+ options.escape = !!options.escape;
170
+ options.renderUndefined = !!options.renderUndefined;
171
+ options.collapseArrays = !!options.collapseArrays;
172
+ options.stringColor = options.stringColor || null;
173
+ options.multilineStringColor = options.multilineStringColor || options.stringColor || null;
174
+ return options;
196
175
  };
176
+ /**
177
+ * ### Render function
178
+ * *Parameters:*
179
+ *
180
+ * @param {*} data: Data to render
181
+ * @param {PrettyJSONOptions} options: Hash of different options
182
+ * @param {*} indentation **`indentation`**: Base indentation of the output
183
+ *
184
+ * *Example of options hash:*
185
+ *
186
+ * {
187
+ * emptyArrayMsg: '(empty)', // Rendered message on empty strings
188
+ * keysColor: 'blue', // Color for keys in hashes
189
+ * dashColor: 'red', // Color for the dashes in arrays
190
+ * stringColor: 'grey', // Color for strings
191
+ * multilineStringColor: 'cyan' // Color for multiline strings
192
+ * defaultIndentation: 2 // Indentation on nested objects
193
+ * }
194
+ * @returns string with the rendered data
195
+ */
197
196
  function render(data, options, indentation) {
198
- indentation = indentation || 0;
199
- options = validateOptionsAndSetDefaults(options);
200
- return renderToArray(data, options, indentation).join("\n");
197
+ indentation = indentation || 0;
198
+ options = validateOptionsAndSetDefaults(options);
199
+ return renderToArray(data, options, indentation).join("\n");
201
200
  }
202
201
 
203
- // src/views/utils.ts
204
- import chalk2 from "chalk";
205
- import truncate from "cli-truncate";
202
+ //#endregion
203
+ //#region src/views/utils.ts
204
+ /**
205
+ * Formats a timestamp into a human-readable string.
206
+ * Format: HH:MM:SS.mmm
207
+ */
206
208
  function formatTimestamp(timestamp, colorFn) {
207
- const date = new Date(timestamp);
208
- return colorFn(
209
- `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}.${date.getMilliseconds().toString().padStart(3, "0")}`
210
- );
209
+ const date = new Date(timestamp);
210
+ return colorFn(`${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}.${date.getMilliseconds().toString().padStart(3, "0")}`);
211
211
  }
212
+ /**
213
+ * Gets the color function for a log level.
214
+ */
212
215
  function getLevelColor(level, colors) {
213
- return colors[level] || chalk2.white;
216
+ return colors[level] || chalk$1.white;
214
217
  }
218
+ /**
219
+ * Formats a value for display.
220
+ */
215
221
  function formatValue(value, expanded = false) {
216
- if (typeof value === "string") return value;
217
- if (typeof value === "number" || typeof value === "boolean") return value.toString();
218
- if (value === null) return "null";
219
- if (value === void 0) return "undefined";
220
- if (Array.isArray(value)) return expanded ? JSON.stringify(value) : "[...]";
221
- if (typeof value === "object") return expanded ? JSON.stringify(value) : "{...}";
222
- return value.toString();
222
+ if (typeof value === "string") return value;
223
+ if (typeof value === "number" || typeof value === "boolean") return value.toString();
224
+ if (value === null) return "null";
225
+ if (value === void 0) return "undefined";
226
+ if (Array.isArray(value)) return expanded ? JSON.stringify(value) : "[...]";
227
+ if (typeof value === "object") return expanded ? JSON.stringify(value) : "{...}";
228
+ return value.toString();
223
229
  }
230
+ /**
231
+ * Formats structured data for inline display with depth limiting.
232
+ */
224
233
  function formatInlineData(data, config, maxDepth, maxLength, expanded = false) {
225
- if (!data) return "";
226
- const pairs = [];
227
- const traverse = (obj, prefix = "", depth = 0) => {
228
- if (!expanded && depth >= maxDepth) return;
229
- for (const [key, value] of Object.entries(obj)) {
230
- const fullKey = prefix ? `${prefix}.${key}` : key;
231
- if (value && typeof value === "object" && !Array.isArray(value)) {
232
- if (expanded) {
233
- pairs.push(`${config.dataKeyColor(fullKey)}=${config.dataValueColor(JSON.stringify(value))}`);
234
- } else {
235
- traverse(value, fullKey, depth + 1);
236
- }
237
- } else {
238
- pairs.push(`${config.dataKeyColor(fullKey)}=${config.dataValueColor(formatValue(value, expanded))}`);
239
- }
240
- }
241
- };
242
- traverse(data);
243
- const result = pairs.join(" ");
244
- return expanded ? result : truncate(result, maxLength);
234
+ if (!data) return "";
235
+ const pairs = [];
236
+ const traverse = (obj, prefix = "", depth = 0) => {
237
+ if (!expanded && depth >= maxDepth) return;
238
+ for (const [key, value] of Object.entries(obj)) {
239
+ const fullKey = prefix ? `${prefix}.${key}` : key;
240
+ if (value && typeof value === "object" && !Array.isArray(value)) if (expanded) pairs.push(`${config.dataKeyColor(fullKey)}=${config.dataValueColor(JSON.stringify(value))}`);
241
+ else traverse(value, fullKey, depth + 1);
242
+ else pairs.push(`${config.dataKeyColor(fullKey)}=${config.dataValueColor(formatValue(value, expanded))}`);
243
+ }
244
+ };
245
+ traverse(data);
246
+ const result = pairs.join(" ");
247
+ return expanded ? result : truncate(result, maxLength);
245
248
  }
246
249
 
247
- // src/views/DetailView.ts
250
+ //#endregion
251
+ //#region src/views/DetailView.ts
252
+ /**
253
+ * Handles rendering of the detail view mode.
254
+ * Shows a full-screen view of a single log entry with:
255
+ * - Previous/next log context
256
+ * - Full timestamp information
257
+ * - Pretty-printed data
258
+ * - Navigation help
259
+ */
248
260
  var DetailView = class {
249
- termWidth;
250
- config;
251
- detailViewContent = [];
252
- constructor(config) {
253
- this.config = config;
254
- this.termWidth = process.stdout.columns || 80;
255
- }
256
- updateTerminalWidth(width) {
257
- this.termWidth = width;
258
- }
259
- /**
260
- * Formats a compact single-line version of a log entry
261
- */
262
- formatCompactLogLine(entry, prefix = "") {
263
- if (!entry) return "";
264
- const levelColor = getLevelColor(entry.level, this.config.config.colors);
265
- const logId = this.config.config.logIdColor(`[${entry.id}]`);
266
- const line = `${prefix}${levelColor(entry.level.toUpperCase())} ${logId} ${entry.message}`;
267
- return this.config.config.separatorColor(wrap(line, this.termWidth, { hard: true }));
268
- }
269
- /**
270
- * Formats a timestamp into a human-readable relative time
271
- */
272
- formatRelativeTime(timestamp) {
273
- const now = /* @__PURE__ */ new Date();
274
- const diffMs = now.getTime() - timestamp.getTime();
275
- const diffSecs = Math.floor(diffMs / 1e3);
276
- const diffMins = Math.floor(diffSecs / 60);
277
- const diffHours = Math.floor(diffMins / 60);
278
- const diffDays = Math.floor(diffHours / 24);
279
- if (diffSecs < 60) return `${diffSecs}s ago`;
280
- if (diffMins < 60) return `${diffMins}m ago`;
281
- if (diffHours < 24) return `${diffHours}h ago`;
282
- if (diffDays === 1) return "yesterday";
283
- if (diffDays < 30) return `${diffDays}d ago`;
284
- return timestamp.toLocaleDateString();
285
- }
286
- render() {
287
- console.clear();
288
- console.log("\n".repeat(5));
289
- if (this.config.isJsonView) {
290
- if (this.config.entry.data) {
291
- console.log(JSON.stringify(JSON.parse(this.config.entry.data)));
292
- } else {
293
- console.log("{}");
294
- }
295
- console.log(`
296
- ${this.config.config.separatorColor("TAB to return to detailed view")}`);
297
- return;
298
- }
299
- const headerContent = [];
300
- if (this.config.prevEntry) {
301
- const prevLine = this.formatCompactLogLine(this.config.prevEntry, "\u2190 ");
302
- headerContent.push(prevLine);
303
- headerContent.push(this.config.config.separatorColor("\u2500".repeat(this.termWidth)));
304
- }
305
- const footerContent = [];
306
- if (this.config.nextEntry) {
307
- footerContent.push(this.config.config.separatorColor("\u2500".repeat(this.termWidth)));
308
- const nextLine = this.formatCompactLogLine(this.config.nextEntry, "\u2192 ");
309
- footerContent.push(nextLine);
310
- }
311
- footerContent.push("");
312
- footerContent.push(this.config.config.separatorColor("\u2191/\u2193 scroll \u2022 Q/W page up/down \u2022 J raw JSON"));
313
- footerContent.push(this.config.config.separatorColor("\u2190/\u2192 navigate \u2022 A/S first/last log \u2022 C toggle arrays"));
314
- const mainContent = [];
315
- const levelColor = getLevelColor(this.config.entry.level, this.config.config.colors);
316
- let headerText = `=== Log Detail [${this.config.entry.id}] (${this.config.currentLogIndex} / ${this.config.totalLogs})`;
317
- if (this.config.filterText) {
318
- headerText += ` [Filter: ${this.config.filterText}]`;
319
- }
320
- headerText += " ===";
321
- const header = levelColor(headerText);
322
- mainContent.push(header);
323
- const timestamp = new Date(this.config.entry.timestamp);
324
- const isoTime = formatTimestamp(this.config.entry.timestamp, this.config.config.dataValueColor);
325
- const relativeTime = this.formatRelativeTime(timestamp);
326
- mainContent.push(`${this.config.config.labelColor("Timestamp:")} ${isoTime} (${relativeTime})`);
327
- mainContent.push(
328
- `${this.config.config.labelColor("Level:")} ${levelColor.bold(this.config.entry.level.toUpperCase())}`
329
- );
330
- if (this.config.entry.message) {
331
- mainContent.push(
332
- `${this.config.config.labelColor("Message:")} ${this.config.config.dataValueColor(this.config.entry.message)}`
333
- );
334
- } else {
335
- mainContent.push(
336
- `${this.config.config.labelColor("Message:")} ${this.config.config.dataValueColor.dim("(no message)")}`
337
- );
338
- }
339
- if (this.config.entry.data) {
340
- mainContent.push(this.config.config.labelColor("\nData:"));
341
- const jsonLines = render(JSON.parse(this.config.entry.data), {
342
- ...this.config.config.jsonColors,
343
- defaultIndentation: 2,
344
- collapseArrays: this.config.isArraysCollapsed
345
- }).split("\n");
346
- mainContent.push(...jsonLines);
347
- }
348
- this.detailViewContent = mainContent;
349
- const headerHeight = headerContent.length;
350
- const footerHeight = footerContent.length;
351
- const scrollIndicatorLines = 2;
352
- const bufferSpace = 4;
353
- const topPadding = 2;
354
- const availableHeight = Math.max(
355
- 0,
356
- process.stdout.rows - headerHeight - footerHeight - scrollIndicatorLines - bufferSpace - topPadding
357
- );
358
- for (const line of headerContent) {
359
- console.log(line);
360
- }
361
- if (this.config.scrollPos > 0) {
362
- console.log(chalk3.dim("\u2191 More content above"));
363
- }
364
- const startIndex = this.config.scrollPos;
365
- const visibleContent = mainContent.slice(startIndex, startIndex + availableHeight);
366
- for (const line of visibleContent) {
367
- console.log(line);
368
- }
369
- if (this.config.scrollPos + availableHeight < mainContent.length) {
370
- console.log(chalk3.dim("\u2193 More content below"));
371
- } else {
372
- console.log(chalk3.dim("End of content"));
373
- }
374
- for (const line of footerContent) {
375
- console.log(line);
376
- }
377
- }
378
- /**
379
- * Gets the maximum scroll position based on current content and window size
380
- */
381
- getMaxScrollPosition() {
382
- const headerHeight = this.config.prevEntry ? 2 : 0;
383
- const footerHeight = 5;
384
- const scrollIndicatorLines = 2;
385
- const bufferSpace = 2;
386
- const topPadding = 2;
387
- const availableHeight = Math.max(
388
- 0,
389
- process.stdout.rows - headerHeight - footerHeight - scrollIndicatorLines - bufferSpace - topPadding
390
- );
391
- return Math.max(0, this.detailViewContent.length - availableHeight);
392
- }
261
+ termWidth;
262
+ config;
263
+ detailViewContent = [];
264
+ constructor(config) {
265
+ this.config = config;
266
+ this.termWidth = process.stdout.columns || 80;
267
+ }
268
+ updateTerminalWidth(width) {
269
+ this.termWidth = width;
270
+ }
271
+ /**
272
+ * Formats a compact single-line version of a log entry
273
+ */
274
+ formatCompactLogLine(entry, prefix = "") {
275
+ if (!entry) return "";
276
+ const levelColor = getLevelColor(entry.level, this.config.config.colors);
277
+ const logId = this.config.config.logIdColor(`[${entry.id}]`);
278
+ const line = `${prefix}${levelColor(entry.level.toUpperCase())} ${logId} ${entry.message}`;
279
+ return this.config.config.separatorColor(wrap(line, this.termWidth, { hard: true }));
280
+ }
281
+ /**
282
+ * Formats a timestamp into a human-readable relative time
283
+ */
284
+ formatRelativeTime(timestamp) {
285
+ const diffMs = (/* @__PURE__ */ new Date()).getTime() - timestamp.getTime();
286
+ const diffSecs = Math.floor(diffMs / 1e3);
287
+ const diffMins = Math.floor(diffSecs / 60);
288
+ const diffHours = Math.floor(diffMins / 60);
289
+ const diffDays = Math.floor(diffHours / 24);
290
+ if (diffSecs < 60) return `${diffSecs}s ago`;
291
+ if (diffMins < 60) return `${diffMins}m ago`;
292
+ if (diffHours < 24) return `${diffHours}h ago`;
293
+ if (diffDays === 1) return "yesterday";
294
+ if (diffDays < 30) return `${diffDays}d ago`;
295
+ return timestamp.toLocaleDateString();
296
+ }
297
+ render() {
298
+ console.clear();
299
+ console.log("\n".repeat(5));
300
+ if (this.config.isJsonView) {
301
+ if (this.config.entry.data) console.log(JSON.stringify(JSON.parse(this.config.entry.data)));
302
+ else console.log("{}");
303
+ console.log(`\n${this.config.config.separatorColor("TAB to return to detailed view")}`);
304
+ return;
305
+ }
306
+ const headerContent = [];
307
+ if (this.config.prevEntry) {
308
+ const prevLine = this.formatCompactLogLine(this.config.prevEntry, " ");
309
+ headerContent.push(prevLine);
310
+ headerContent.push(this.config.config.separatorColor("─".repeat(this.termWidth)));
311
+ }
312
+ const footerContent = [];
313
+ if (this.config.nextEntry) {
314
+ footerContent.push(this.config.config.separatorColor("─".repeat(this.termWidth)));
315
+ const nextLine = this.formatCompactLogLine(this.config.nextEntry, "");
316
+ footerContent.push(nextLine);
317
+ }
318
+ footerContent.push("");
319
+ footerContent.push(this.config.config.separatorColor("↑/↓ scroll • Q/W page up/down • J raw JSON"));
320
+ footerContent.push(this.config.config.separatorColor("←/→ navigate • A/S first/last log • C toggle arrays"));
321
+ const mainContent = [];
322
+ const levelColor = getLevelColor(this.config.entry.level, this.config.config.colors);
323
+ let headerText = `=== Log Detail [${this.config.entry.id}] (${this.config.currentLogIndex} / ${this.config.totalLogs})`;
324
+ if (this.config.filterText) headerText += ` [Filter: ${this.config.filterText}]`;
325
+ headerText += " ===";
326
+ const header = levelColor(headerText);
327
+ mainContent.push(header);
328
+ const timestamp = new Date(this.config.entry.timestamp);
329
+ const isoTime = formatTimestamp(this.config.entry.timestamp, this.config.config.dataValueColor);
330
+ const relativeTime = this.formatRelativeTime(timestamp);
331
+ mainContent.push(`${this.config.config.labelColor("Timestamp:")} ${isoTime} (${relativeTime})`);
332
+ mainContent.push(`${this.config.config.labelColor("Level:")} ${levelColor.bold(this.config.entry.level.toUpperCase())}`);
333
+ if (this.config.entry.message) mainContent.push(`${this.config.config.labelColor("Message:")} ${this.config.config.dataValueColor(this.config.entry.message)}`);
334
+ else mainContent.push(`${this.config.config.labelColor("Message:")} ${this.config.config.dataValueColor.dim("(no message)")}`);
335
+ if (this.config.entry.data) {
336
+ mainContent.push(this.config.config.labelColor("\nData:"));
337
+ const jsonLines = render(JSON.parse(this.config.entry.data), {
338
+ ...this.config.config.jsonColors,
339
+ defaultIndentation: 2,
340
+ collapseArrays: this.config.isArraysCollapsed
341
+ }).split("\n");
342
+ mainContent.push(...jsonLines);
343
+ }
344
+ this.detailViewContent = mainContent;
345
+ const headerHeight = headerContent.length;
346
+ const footerHeight = footerContent.length;
347
+ const availableHeight = Math.max(0, process.stdout.rows - headerHeight - footerHeight - 2 - 4 - 2);
348
+ for (const line of headerContent) console.log(line);
349
+ if (this.config.scrollPos > 0) console.log(chalk$1.dim("↑ More content above"));
350
+ const startIndex = this.config.scrollPos;
351
+ const visibleContent = mainContent.slice(startIndex, startIndex + availableHeight);
352
+ for (const line of visibleContent) console.log(line);
353
+ if (this.config.scrollPos + availableHeight < mainContent.length) console.log(chalk$1.dim("↓ More content below"));
354
+ else console.log(chalk$1.dim("End of content"));
355
+ for (const line of footerContent) console.log(line);
356
+ }
357
+ /**
358
+ * Gets the maximum scroll position based on current content and window size
359
+ */
360
+ getMaxScrollPosition() {
361
+ const headerHeight = this.config.prevEntry ? 2 : 0;
362
+ const availableHeight = Math.max(0, process.stdout.rows - headerHeight - 5 - 2 - 2 - 2);
363
+ return Math.max(0, this.detailViewContent.length - availableHeight);
364
+ }
393
365
  };
394
366
 
395
- // src/views/SelectionView.ts
396
- import chalk4 from "chalk";
397
- import wrap2 from "wrap-ansi";
367
+ //#endregion
368
+ //#region src/views/SelectionView.ts
369
+ /**
370
+ * Handles rendering of the selection view mode.
371
+ * Shows an interactive list of logs with:
372
+ * - Filtering capability
373
+ * - Selected item highlighting
374
+ * - Full data preview inline
375
+ * - Scroll indicators
376
+ */
398
377
  var SelectionView = class {
399
- termWidth;
400
- config;
401
- constructor(config) {
402
- this.config = config;
403
- this.termWidth = process.stdout.columns || 80;
404
- }
405
- updateTerminalWidth(width) {
406
- this.termWidth = width;
407
- }
408
- render() {
409
- console.clear();
410
- console.log("\n".repeat(5));
411
- const filterHeight = this.config.filterText ? 2 : 0;
412
- const footerHeight = 2;
413
- const topPadding = 5;
414
- const availableHeight = process.stdout.rows - filterHeight - footerHeight - topPadding;
415
- if (this.config.logs.length === 0) {
416
- console.log(chalk4.yellow("No matching logs found"));
417
- } else {
418
- const visibleLines = Math.min(availableHeight, 20);
419
- const bottomPadding = 2;
420
- let startIdx = Math.max(0, this.config.selectedIndex - (visibleLines - bottomPadding - 1));
421
- const endIdx = Math.min(this.config.logs.length, startIdx + visibleLines);
422
- if (endIdx - startIdx < visibleLines && endIdx < this.config.logs.length) {
423
- startIdx = Math.max(0, endIdx - visibleLines);
424
- }
425
- if (startIdx > 0) {
426
- console.log(chalk4.dim(" \u2191 More logs above"));
427
- }
428
- this.config.logs.slice(startIdx, endIdx).forEach((entry, index) => {
429
- const actualIndex = startIdx + index;
430
- const isSelected = actualIndex === this.config.selectedIndex;
431
- const prefix = isSelected ? this.config.config.selectorColor("\u25BA ") : " ";
432
- const timestamp = formatTimestamp(entry.timestamp, this.config.config.logIdColor);
433
- const levelColor = getLevelColor(entry.level, this.config.config.colors);
434
- const chevron = levelColor(`${entry.level.toUpperCase()} `);
435
- const logId = chalk4.dim(`[${entry.id}]`);
436
- const message = entry.message;
437
- const data = entry.data ? ` ${formatInlineData(JSON.parse(entry.data), this.config.config, 0, 0, true)}` : "";
438
- const mainLine = `${prefix}${timestamp} ${chevron}${logId} ${message}${data}`;
439
- const wrappedText = wrap2(mainLine, this.termWidth - 2, { hard: true });
440
- if (isSelected) {
441
- const lines = wrappedText.split("\n");
442
- console.log(lines[0]);
443
- for (const line of lines.slice(1)) {
444
- console.log(` ${this.config.config.selectorColor("\u2502")} ${line}`);
445
- }
446
- } else {
447
- console.log(wrappedText);
448
- }
449
- });
450
- if (endIdx < this.config.logs.length || this.config.newLogCount > 0) {
451
- const moreLogsText = this.config.newLogCount > 0 ? this.config.config.selectorColor(
452
- ` \u2193 ${this.config.newLogCount} new log${this.config.newLogCount === 1 ? "" : "s"} available (press \u2193 to view)`
453
- ) : chalk4.dim(" \u2193 More logs below");
454
- console.log(moreLogsText);
455
- }
456
- }
457
- console.log(chalk4.dim("\nType to filter \u2022 Enter to view details \u2022 TAB to exit"));
458
- if (this.config.filterText) {
459
- console.log(chalk4.dim("\u2500".repeat(this.termWidth)));
460
- console.log(chalk4.cyan("Filter:"), chalk4.white(this.config.filterText));
461
- }
462
- }
378
+ termWidth;
379
+ config;
380
+ constructor(config) {
381
+ this.config = config;
382
+ this.termWidth = process.stdout.columns || 80;
383
+ }
384
+ updateTerminalWidth(width) {
385
+ this.termWidth = width;
386
+ }
387
+ render() {
388
+ console.clear();
389
+ console.log("\n".repeat(5));
390
+ const filterHeight = this.config.filterText ? 2 : 0;
391
+ const availableHeight = process.stdout.rows - filterHeight - 2 - 5;
392
+ if (this.config.logs.length === 0) console.log(chalk$1.yellow("No matching logs found"));
393
+ else {
394
+ const visibleLines = Math.min(availableHeight, 20);
395
+ let startIdx = Math.max(0, this.config.selectedIndex - (visibleLines - 2 - 1));
396
+ const endIdx = Math.min(this.config.logs.length, startIdx + visibleLines);
397
+ if (endIdx - startIdx < visibleLines && endIdx < this.config.logs.length) startIdx = Math.max(0, endIdx - visibleLines);
398
+ if (startIdx > 0) console.log(chalk$1.dim(" ↑ More logs above"));
399
+ this.config.logs.slice(startIdx, endIdx).forEach((entry, index) => {
400
+ const isSelected = startIdx + index === this.config.selectedIndex;
401
+ const wrappedText = wrap(`${isSelected ? this.config.config.selectorColor("► ") : " "}${formatTimestamp(entry.timestamp, this.config.config.logIdColor)} ${getLevelColor(entry.level, this.config.config.colors)(`${entry.level.toUpperCase()} `)}${chalk$1.dim(`[${entry.id}]`)} ${entry.message}${entry.data ? ` ${formatInlineData(JSON.parse(entry.data), this.config.config, 0, 0, true)}` : ""}`, this.termWidth - 2, { hard: true });
402
+ if (isSelected) {
403
+ const lines = wrappedText.split("\n");
404
+ console.log(lines[0]);
405
+ for (const line of lines.slice(1)) console.log(` ${this.config.config.selectorColor("")} ${line}`);
406
+ } else console.log(wrappedText);
407
+ });
408
+ if (endIdx < this.config.logs.length || this.config.newLogCount > 0) {
409
+ const moreLogsText = this.config.newLogCount > 0 ? this.config.config.selectorColor(` ↓ ${this.config.newLogCount} new log${this.config.newLogCount === 1 ? "" : "s"} available (press ↓ to view)`) : chalk$1.dim(" ↓ More logs below");
410
+ console.log(moreLogsText);
411
+ }
412
+ }
413
+ console.log(chalk$1.dim("\nType to filter • Enter to view details • TAB to exit"));
414
+ if (this.config.filterText) {
415
+ console.log(chalk$1.dim("─".repeat(this.termWidth)));
416
+ console.log(chalk$1.cyan("Filter:"), chalk$1.white(this.config.filterText));
417
+ }
418
+ }
463
419
  };
464
420
 
465
- // src/views/SimpleView.ts
466
- import wrap3 from "wrap-ansi";
421
+ //#endregion
422
+ //#region src/views/SimpleView.ts
423
+ /**
424
+ * Handles rendering of the simple view mode.
425
+ * Supports three display modes:
426
+ * - Truncated: Shows all log details with truncated data
427
+ * - Condensed: Shows timestamp, level and message
428
+ * - Full: Shows all details with complete data
429
+ */
467
430
  var SimpleView = class {
468
- termWidth;
469
- config;
470
- constructor(config) {
471
- this.config = config;
472
- this.termWidth = process.stdout.columns || 80;
473
- }
474
- updateTerminalWidth(width) {
475
- this.termWidth = width;
476
- }
477
- /**
478
- * Renders a single log entry based on the current view mode
479
- */
480
- renderLogLine(entry) {
481
- const levelColor = getLevelColor(entry.level, this.config.config.colors);
482
- const chevron = levelColor(`\u25B6 ${entry.level.toUpperCase()} `);
483
- const message = entry.message || "(no message)";
484
- const timestamp = formatTimestamp(entry.timestamp, this.config.config.logIdColor);
485
- switch (this.config.viewMode) {
486
- case "condensed": {
487
- const condensedLine = `${timestamp} ${chevron}${message}`;
488
- console.log(wrap3(condensedLine, this.termWidth, { hard: true }));
489
- break;
490
- }
491
- case "full": {
492
- const logId = this.config.config.logIdColor(`[${entry.id}]`);
493
- const fullData = entry.data ? formatInlineData(
494
- JSON.parse(entry.data),
495
- this.config.config,
496
- this.config.maxInlineDepth,
497
- this.config.maxInlineLength,
498
- true
499
- ) : "";
500
- const expandedLine = `${timestamp} ${chevron}${logId} ${message}${fullData ? ` ${fullData}` : ""}`;
501
- console.log(wrap3(expandedLine, this.termWidth, { hard: true }));
502
- break;
503
- }
504
- default: {
505
- const logId = this.config.config.logIdColor(`[${entry.id}]`);
506
- const normalData = entry.data ? formatInlineData(
507
- JSON.parse(entry.data),
508
- this.config.config,
509
- this.config.maxInlineDepth,
510
- this.config.maxInlineLength
511
- ) : "";
512
- const normalLine = `${timestamp} ${chevron}${logId} ${message}${normalData ? ` ${normalData}` : ""}`;
513
- console.log(wrap3(normalLine, this.termWidth, { hard: true }));
514
- break;
515
- }
516
- }
517
- }
518
- render() {
519
- throw new Error("SimpleView.render() should not be called directly. Use renderLogLine() instead.");
520
- }
431
+ termWidth;
432
+ config;
433
+ constructor(config) {
434
+ this.config = config;
435
+ this.termWidth = process.stdout.columns || 80;
436
+ }
437
+ updateTerminalWidth(width) {
438
+ this.termWidth = width;
439
+ }
440
+ /**
441
+ * Renders a single log entry based on the current view mode
442
+ */
443
+ renderLogLine(entry) {
444
+ const chevron = getLevelColor(entry.level, this.config.config.colors)(`▶ ${entry.level.toUpperCase()} `);
445
+ const message = entry.message || "(no message)";
446
+ const timestamp = formatTimestamp(entry.timestamp, this.config.config.logIdColor);
447
+ switch (this.config.viewMode) {
448
+ case "condensed": {
449
+ const condensedLine = `${timestamp} ${chevron}${message}`;
450
+ console.log(wrap(condensedLine, this.termWidth, { hard: true }));
451
+ break;
452
+ }
453
+ case "full": {
454
+ const logId = this.config.config.logIdColor(`[${entry.id}]`);
455
+ const fullData = entry.data ? formatInlineData(JSON.parse(entry.data), this.config.config, this.config.maxInlineDepth, this.config.maxInlineLength, true) : "";
456
+ const expandedLine = `${timestamp} ${chevron}${logId} ${message}${fullData ? ` ${fullData}` : ""}`;
457
+ console.log(wrap(expandedLine, this.termWidth, { hard: true }));
458
+ break;
459
+ }
460
+ default: {
461
+ const logId = this.config.config.logIdColor(`[${entry.id}]`);
462
+ const normalData = entry.data ? formatInlineData(JSON.parse(entry.data), this.config.config, this.config.maxInlineDepth, this.config.maxInlineLength) : "";
463
+ const normalLine = `${timestamp} ${chevron}${logId} ${message}${normalData ? ` ${normalData}` : ""}`;
464
+ console.log(wrap(normalLine, this.termWidth, { hard: true }));
465
+ break;
466
+ }
467
+ }
468
+ }
469
+ render() {
470
+ throw new Error("SimpleView.render() should not be called directly. Use renderLogLine() instead.");
471
+ }
521
472
  };
522
473
 
523
- // src/LogRenderer.ts
474
+ //#endregion
475
+ //#region src/LogRenderer.ts
476
+ /**
477
+ * LogRenderer coordinates between different view modes and manages the overall rendering state.
478
+ * Delegates actual rendering to specialized view implementations:
479
+ * - SimpleView: For real-time log output
480
+ * - SelectionView: For interactive log browsing
481
+ * - DetailView: For in-depth log inspection
482
+ */
524
483
  var LogRenderer = class {
525
- /** Current terminal width in characters */
526
- termWidth;
527
- /** Maximum depth for displaying nested objects inline */
528
- maxInlineDepth;
529
- /** Maximum length for inline data before truncating */
530
- maxInlineLength;
531
- /** Whether arrays are currently collapsed in detail view */
532
- isArraysCollapsed = true;
533
- /** Current view mode in simple mode */
534
- viewMode = "full";
535
- /** Color and formatting config for simple log view */
536
- simpleViewConfig;
537
- /** Color and formatting config for detailed view */
538
- detailedViewConfig;
539
- /** Whether we're showing raw JSON view */
540
- isJsonView = false;
541
- /** Current scroll position in detailed view */
542
- detailViewScrollPos = 0;
543
- /** View instances */
544
- simpleView;
545
- detailView = null;
546
- selectionView = null;
547
- /**
548
- * Creates a new LogRenderer instance.
549
- *
550
- * @param simpleViewConfig - Configuration for simple log view
551
- * @param detailedViewConfig - Configuration for detailed view
552
- * @param maxInlineDepth - Maximum depth for inline object display
553
- * @param maxInlineLength - Maximum length before truncation
554
- */
555
- constructor(simpleViewConfig, detailedViewConfig, maxInlineDepth, maxInlineLength) {
556
- this.simpleViewConfig = simpleViewConfig;
557
- this.detailedViewConfig = detailedViewConfig;
558
- this.maxInlineDepth = maxInlineDepth;
559
- this.maxInlineLength = maxInlineLength;
560
- this.termWidth = process.stdout.columns || 80;
561
- this.simpleView = new SimpleView({
562
- viewMode: this.viewMode,
563
- maxInlineDepth: this.maxInlineDepth,
564
- maxInlineLength: this.maxInlineLength,
565
- config: this.simpleViewConfig
566
- });
567
- this.detailView = null;
568
- this.selectionView = null;
569
- process.stdout.on("resize", () => {
570
- this.termWidth = process.stdout.columns || 80;
571
- this.simpleView.updateTerminalWidth(this.termWidth);
572
- if (this.detailView) {
573
- this.detailView.updateTerminalWidth(this.termWidth);
574
- }
575
- if (this.selectionView) {
576
- this.selectionView.updateTerminalWidth(this.termWidth);
577
- }
578
- });
579
- }
580
- /**
581
- * Renders a single log line in simple view format.
582
- * Format: [timestamp] LEVEL [id] message data
583
- * Condensed Format: [timestamp] LEVEL message
584
- * Expanded Format: [timestamp] LEVEL [id] message full_data
585
- *
586
- * @param entry - Log entry to render
587
- */
588
- renderLogLine(entry) {
589
- this.simpleView.renderLogLine(entry);
590
- }
591
- /**
592
- * Renders the detailed view of a log entry.
593
- * Shows full entry details with context and formatted data.
594
- *
595
- * @param entry - Log entry to show in detail
596
- * @param prevEntry - Previous entry for context
597
- * @param nextEntry - Next entry for context
598
- * @param scrollPos - Current scroll position
599
- * @param currentLogIndex - Current log index (optional)
600
- * @param totalLogs - Total logs count (optional)
601
- * @param filterText - Current filter text (if any)
602
- */
603
- renderDetailView(entry, prevEntry, nextEntry, scrollPos = 0, currentLogIndex, totalLogs, filterText = "") {
604
- const config = {
605
- entry,
606
- prevEntry,
607
- nextEntry,
608
- scrollPos,
609
- isArraysCollapsed: this.isArraysCollapsed,
610
- isJsonView: this.isJsonView,
611
- currentLogIndex: currentLogIndex ?? (prevEntry ? 2 : nextEntry ? 1 : 1),
612
- // Use provided index or fallback
613
- totalLogs: totalLogs ?? 1,
614
- // Use provided total or fallback
615
- filterText,
616
- config: this.detailedViewConfig
617
- };
618
- if (!this.detailView) {
619
- this.detailView = new DetailView(config);
620
- } else {
621
- this.detailView = new DetailView(config);
622
- }
623
- this.detailView.render();
624
- }
625
- /**
626
- * Updates the scroll position in detailed view
627
- * @param delta - Number of lines to scroll
628
- */
629
- scrollDetailView(delta) {
630
- if (!this.detailView) return;
631
- const maxScroll = this.detailView.getMaxScrollPosition();
632
- this.detailViewScrollPos = Math.max(0, Math.min(maxScroll, this.detailViewScrollPos + delta));
633
- }
634
- /**
635
- * Gets the current scroll position
636
- */
637
- getDetailViewScrollPos() {
638
- return this.detailViewScrollPos;
639
- }
640
- /**
641
- * Renders the selection view with a list of logs.
642
- * Shows filtered logs with selection highlight and data preview.
643
- *
644
- * @param logs - Array of logs to display
645
- * @param selectedIndex - Index of currently selected log
646
- * @param filterText - Current filter text (if any)
647
- * @param newLogCount - Number of new logs available
648
- */
649
- renderSelectionView(logs, selectedIndex, filterText, newLogCount) {
650
- const config = {
651
- logs,
652
- selectedIndex,
653
- filterText,
654
- newLogCount,
655
- config: this.detailedViewConfig
656
- };
657
- if (!this.selectionView) {
658
- this.selectionView = new SelectionView(config);
659
- } else {
660
- this.selectionView = new SelectionView(config);
661
- }
662
- this.selectionView.render();
663
- }
664
- /**
665
- * Toggles JSON view state
666
- */
667
- toggleJsonView() {
668
- this.isJsonView = !this.isJsonView;
669
- }
670
- /**
671
- * Gets current JSON view state
672
- */
673
- isInJsonView() {
674
- return this.isJsonView;
675
- }
676
- /**
677
- * Toggles array collapse state in detail view
678
- */
679
- toggleArrayCollapse() {
680
- this.isArraysCollapsed = !this.isArraysCollapsed;
681
- }
682
- /**
683
- * Cycles through view modes: full -> truncated -> condensed
684
- */
685
- cycleViewMode() {
686
- switch (this.viewMode) {
687
- case "full":
688
- this.viewMode = "truncated";
689
- break;
690
- case "truncated":
691
- this.viewMode = "condensed";
692
- break;
693
- case "condensed":
694
- this.viewMode = "full";
695
- break;
696
- }
697
- this.simpleView = new SimpleView({
698
- viewMode: this.viewMode,
699
- maxInlineDepth: this.maxInlineDepth,
700
- maxInlineLength: this.maxInlineLength,
701
- config: this.simpleViewConfig
702
- });
703
- return this.viewMode;
704
- }
705
- /**
706
- * Gets current view mode
707
- */
708
- getViewMode() {
709
- return this.viewMode;
710
- }
484
+ /** Current terminal width in characters */
485
+ termWidth;
486
+ /** Maximum depth for displaying nested objects inline */
487
+ maxInlineDepth;
488
+ /** Maximum length for inline data before truncating */
489
+ maxInlineLength;
490
+ /** Whether arrays are currently collapsed in detail view */
491
+ isArraysCollapsed = true;
492
+ /** Current view mode in simple mode */
493
+ viewMode = "full";
494
+ /** Color and formatting config for simple log view */
495
+ simpleViewConfig;
496
+ /** Color and formatting config for detailed view */
497
+ detailedViewConfig;
498
+ /** Whether we're showing raw JSON view */
499
+ isJsonView = false;
500
+ /** Current scroll position in detailed view */
501
+ detailViewScrollPos = 0;
502
+ /** View instances */
503
+ simpleView;
504
+ detailView = null;
505
+ selectionView = null;
506
+ /**
507
+ * Creates a new LogRenderer instance.
508
+ *
509
+ * @param simpleViewConfig - Configuration for simple log view
510
+ * @param detailedViewConfig - Configuration for detailed view
511
+ * @param maxInlineDepth - Maximum depth for inline object display
512
+ * @param maxInlineLength - Maximum length before truncation
513
+ */
514
+ constructor(simpleViewConfig, detailedViewConfig, maxInlineDepth, maxInlineLength) {
515
+ this.simpleViewConfig = simpleViewConfig;
516
+ this.detailedViewConfig = detailedViewConfig;
517
+ this.maxInlineDepth = maxInlineDepth;
518
+ this.maxInlineLength = maxInlineLength;
519
+ this.termWidth = process.stdout.columns || 80;
520
+ this.simpleView = new SimpleView({
521
+ viewMode: this.viewMode,
522
+ maxInlineDepth: this.maxInlineDepth,
523
+ maxInlineLength: this.maxInlineLength,
524
+ config: this.simpleViewConfig
525
+ });
526
+ this.detailView = null;
527
+ this.selectionView = null;
528
+ process.stdout.on("resize", () => {
529
+ this.termWidth = process.stdout.columns || 80;
530
+ this.simpleView.updateTerminalWidth(this.termWidth);
531
+ if (this.detailView) this.detailView.updateTerminalWidth(this.termWidth);
532
+ if (this.selectionView) this.selectionView.updateTerminalWidth(this.termWidth);
533
+ });
534
+ }
535
+ /**
536
+ * Renders a single log line in simple view format.
537
+ * Format: [timestamp] LEVEL [id] message data
538
+ * Condensed Format: [timestamp] LEVEL message
539
+ * Expanded Format: [timestamp] LEVEL [id] message full_data
540
+ *
541
+ * @param entry - Log entry to render
542
+ */
543
+ renderLogLine(entry) {
544
+ this.simpleView.renderLogLine(entry);
545
+ }
546
+ /**
547
+ * Renders the detailed view of a log entry.
548
+ * Shows full entry details with context and formatted data.
549
+ *
550
+ * @param entry - Log entry to show in detail
551
+ * @param prevEntry - Previous entry for context
552
+ * @param nextEntry - Next entry for context
553
+ * @param scrollPos - Current scroll position
554
+ * @param currentLogIndex - Current log index (optional)
555
+ * @param totalLogs - Total logs count (optional)
556
+ * @param filterText - Current filter text (if any)
557
+ */
558
+ renderDetailView(entry, prevEntry, nextEntry, scrollPos = 0, currentLogIndex, totalLogs, filterText = "") {
559
+ const config = {
560
+ entry,
561
+ prevEntry,
562
+ nextEntry,
563
+ scrollPos,
564
+ isArraysCollapsed: this.isArraysCollapsed,
565
+ isJsonView: this.isJsonView,
566
+ currentLogIndex: currentLogIndex ?? (prevEntry ? 2 : nextEntry ? 1 : 1),
567
+ totalLogs: totalLogs ?? 1,
568
+ filterText,
569
+ config: this.detailedViewConfig
570
+ };
571
+ if (!this.detailView) this.detailView = new DetailView(config);
572
+ else this.detailView = new DetailView(config);
573
+ this.detailView.render();
574
+ }
575
+ /**
576
+ * Updates the scroll position in detailed view
577
+ * @param delta - Number of lines to scroll
578
+ */
579
+ scrollDetailView(delta) {
580
+ if (!this.detailView) return;
581
+ const maxScroll = this.detailView.getMaxScrollPosition();
582
+ this.detailViewScrollPos = Math.max(0, Math.min(maxScroll, this.detailViewScrollPos + delta));
583
+ }
584
+ /**
585
+ * Gets the current scroll position
586
+ */
587
+ getDetailViewScrollPos() {
588
+ return this.detailViewScrollPos;
589
+ }
590
+ /**
591
+ * Renders the selection view with a list of logs.
592
+ * Shows filtered logs with selection highlight and data preview.
593
+ *
594
+ * @param logs - Array of logs to display
595
+ * @param selectedIndex - Index of currently selected log
596
+ * @param filterText - Current filter text (if any)
597
+ * @param newLogCount - Number of new logs available
598
+ */
599
+ renderSelectionView(logs, selectedIndex, filterText, newLogCount) {
600
+ const config = {
601
+ logs,
602
+ selectedIndex,
603
+ filterText,
604
+ newLogCount,
605
+ config: this.detailedViewConfig
606
+ };
607
+ if (!this.selectionView) this.selectionView = new SelectionView(config);
608
+ else this.selectionView = new SelectionView(config);
609
+ this.selectionView.render();
610
+ }
611
+ /**
612
+ * Toggles JSON view state
613
+ */
614
+ toggleJsonView() {
615
+ this.isJsonView = !this.isJsonView;
616
+ }
617
+ /**
618
+ * Gets current JSON view state
619
+ */
620
+ isInJsonView() {
621
+ return this.isJsonView;
622
+ }
623
+ /**
624
+ * Toggles array collapse state in detail view
625
+ */
626
+ toggleArrayCollapse() {
627
+ this.isArraysCollapsed = !this.isArraysCollapsed;
628
+ }
629
+ /**
630
+ * Cycles through view modes: full -> truncated -> condensed
631
+ */
632
+ cycleViewMode() {
633
+ switch (this.viewMode) {
634
+ case "full":
635
+ this.viewMode = "truncated";
636
+ break;
637
+ case "truncated":
638
+ this.viewMode = "condensed";
639
+ break;
640
+ case "condensed":
641
+ this.viewMode = "full";
642
+ break;
643
+ }
644
+ this.simpleView = new SimpleView({
645
+ viewMode: this.viewMode,
646
+ maxInlineDepth: this.maxInlineDepth,
647
+ maxInlineLength: this.maxInlineLength,
648
+ config: this.simpleViewConfig
649
+ });
650
+ return this.viewMode;
651
+ }
652
+ /**
653
+ * Gets current view mode
654
+ */
655
+ getViewMode() {
656
+ return this.viewMode;
657
+ }
711
658
  };
712
659
 
713
- // src/LogStorage.ts
714
- import { resolve } from "path";
715
- import Database from "better-sqlite3";
660
+ //#endregion
661
+ //#region src/LogStorage.ts
662
+ /**
663
+ * Handles storage and retrieval of log entries using SQLite.
664
+ * Uses an in-memory database for optimal performance and automatic cleanup.
665
+ *
666
+ * The database schema includes:
667
+ * - id: Unique identifier for each log
668
+ * - timestamp: Unix timestamp in milliseconds
669
+ * - level: Log level (trace, debug, info, etc.)
670
+ * - message: Main log message
671
+ * - data: Optional structured data as JSON string
672
+ */
716
673
  var LogStorage = class {
717
- /** SQLite database instance */
718
- db;
719
- /**
720
- * Creates a new LogStorage instance.
721
- * Initializes a SQLite database and creates the logs table.
722
- *
723
- * @param logFile - Optional path to SQLite file for persistent storage.
724
- * If not provided, uses in-memory database.
725
- * Relative paths are resolved from the current working directory.
726
- */
727
- constructor(logFile) {
728
- const dbPath = logFile ? logFile.startsWith("/") ? logFile : resolve(process.cwd(), logFile) : ":memory:";
729
- this.db = new Database(dbPath);
730
- this.initializeDatabase();
731
- }
732
- /**
733
- * Initializes the database schema.
734
- * Creates the logs table with appropriate columns and indexes.
735
- * If the table already exists, it will be dropped to ensure a clean state.
736
- * @private
737
- */
738
- initializeDatabase() {
739
- this.db.exec(`
674
+ /** SQLite database instance */
675
+ db;
676
+ /**
677
+ * Creates a new LogStorage instance.
678
+ * Initializes a SQLite database and creates the logs table.
679
+ *
680
+ * @param logFile - Optional path to SQLite file for persistent storage.
681
+ * If not provided, uses in-memory database.
682
+ * Relative paths are resolved from the current working directory.
683
+ */
684
+ constructor(logFile) {
685
+ this.db = new Database(logFile ? logFile.startsWith("/") ? logFile : resolve(process.cwd(), logFile) : ":memory:");
686
+ this.initializeDatabase();
687
+ }
688
+ /**
689
+ * Initializes the database schema.
690
+ * Creates the logs table with appropriate columns and indexes.
691
+ * If the table already exists, it will be dropped to ensure a clean state.
692
+ * @private
693
+ */
694
+ initializeDatabase() {
695
+ this.db.exec(`
740
696
  DROP TABLE IF EXISTS logs;
741
697
  CREATE TABLE logs (
742
698
  id TEXT PRIMARY KEY,
@@ -746,1086 +702,938 @@ var LogStorage = class {
746
702
  data TEXT
747
703
  )
748
704
  `);
749
- }
750
- /**
751
- * Stores a new log entry in the database.
752
- * Uses prepared statements for efficient and safe insertion.
753
- *
754
- * @param entry - The log entry to store
755
- * @example
756
- * storage.store({
757
- * id: "abc123",
758
- * timestamp: Date.now(),
759
- * level: "info",
760
- * message: "User logged in",
761
- * data: JSON.stringify({ userId: 123 })
762
- * });
763
- */
764
- store(entry) {
765
- this.db.prepare(`
705
+ }
706
+ /**
707
+ * Stores a new log entry in the database.
708
+ * Uses prepared statements for efficient and safe insertion.
709
+ *
710
+ * @param entry - The log entry to store
711
+ * @example
712
+ * storage.store({
713
+ * id: "abc123",
714
+ * timestamp: Date.now(),
715
+ * level: "info",
716
+ * message: "User logged in",
717
+ * data: JSON.stringify({ userId: 123 })
718
+ * });
719
+ */
720
+ store(entry) {
721
+ this.db.prepare(`
766
722
  INSERT INTO logs (id, timestamp, level, message, data)
767
723
  VALUES (?, ?, ?, ?, ?)
768
724
  `).run(entry.id, entry.timestamp, entry.level, entry.message, entry.data);
769
- }
770
- /**
771
- * Retrieves all logs from the database in chronological order.
772
- * Used when displaying the full log history or clearing filters.
773
- *
774
- * @returns Array of log entries sorted by timestamp
775
- */
776
- getAllLogs() {
777
- return this.db.prepare("SELECT * FROM logs ORDER BY timestamp ASC").all();
778
- }
779
- /**
780
- * Searches logs based on a text query.
781
- * Performs a case-insensitive search across multiple fields:
782
- * - Log ID
783
- * - Message content
784
- * - Structured data
785
- *
786
- * @param searchText - Text to search for
787
- * @returns Array of matching log entries sorted by timestamp
788
- * @example
789
- * const errorLogs = storage.searchLogs("error");
790
- * const userLogs = storage.searchLogs("userId:123");
791
- */
792
- searchLogs(searchText) {
793
- const query = `
725
+ }
726
+ /**
727
+ * Retrieves all logs from the database in chronological order.
728
+ * Used when displaying the full log history or clearing filters.
729
+ *
730
+ * @returns Array of log entries sorted by timestamp
731
+ */
732
+ getAllLogs() {
733
+ return this.db.prepare("SELECT * FROM logs ORDER BY timestamp ASC").all();
734
+ }
735
+ /**
736
+ * Searches logs based on a text query.
737
+ * Performs a case-insensitive search across multiple fields:
738
+ * - Log ID
739
+ * - Message content
740
+ * - Structured data
741
+ *
742
+ * @param searchText - Text to search for
743
+ * @returns Array of matching log entries sorted by timestamp
744
+ * @example
745
+ * const errorLogs = storage.searchLogs("error");
746
+ * const userLogs = storage.searchLogs("userId:123");
747
+ */
748
+ searchLogs(searchText) {
749
+ const query = `
794
750
  SELECT * FROM logs
795
751
  WHERE id LIKE ?
796
752
  OR message LIKE ?
797
753
  OR data LIKE ?
798
754
  ORDER BY timestamp ASC
799
755
  `;
800
- const pattern = `%${searchText}%`;
801
- return this.db.prepare(query).all(pattern, pattern, pattern);
802
- }
803
- /**
804
- * Gets the total number of logs in the database.
805
- * Uses an efficient COUNT query instead of loading all logs.
806
- *
807
- * @returns Total number of log entries
808
- */
809
- getLogCount() {
810
- const result = this.db.prepare("SELECT COUNT(*) as count FROM logs").get();
811
- return result.count;
812
- }
813
- /**
814
- * Gets the total number of logs matching a search query.
815
- * Uses an efficient COUNT query instead of loading all logs.
816
- *
817
- * @param searchText - Text to search for
818
- * @returns Number of matching log entries
819
- */
820
- getFilteredLogCount(searchText) {
821
- const query = `
756
+ const pattern = `%${searchText}%`;
757
+ return this.db.prepare(query).all(pattern, pattern, pattern);
758
+ }
759
+ /**
760
+ * Gets the total number of logs in the database.
761
+ * Uses an efficient COUNT query instead of loading all logs.
762
+ *
763
+ * @returns Total number of log entries
764
+ */
765
+ getLogCount() {
766
+ return this.db.prepare("SELECT COUNT(*) as count FROM logs").get().count;
767
+ }
768
+ /**
769
+ * Gets the total number of logs matching a search query.
770
+ * Uses an efficient COUNT query instead of loading all logs.
771
+ *
772
+ * @param searchText - Text to search for
773
+ * @returns Number of matching log entries
774
+ */
775
+ getFilteredLogCount(searchText) {
776
+ const query = `
822
777
  SELECT COUNT(*) as count FROM logs
823
778
  WHERE id LIKE ?
824
779
  OR message LIKE ?
825
780
  OR data LIKE ?
826
781
  `;
827
- const pattern = `%${searchText}%`;
828
- const result = this.db.prepare(query).get(pattern, pattern, pattern);
829
- return result.count;
830
- }
831
- /**
832
- * Closes the database connection and performs cleanup.
833
- * Should be called when the application exits or the transport is destroyed.
834
- */
835
- close() {
836
- this.db.close();
837
- }
782
+ const pattern = `%${searchText}%`;
783
+ return this.db.prepare(query).get(pattern, pattern, pattern).count;
784
+ }
785
+ /**
786
+ * Closes the database connection and performs cleanup.
787
+ * Should be called when the application exits or the transport is destroyed.
788
+ */
789
+ close() {
790
+ this.db.close();
791
+ }
838
792
  };
839
793
 
840
- // src/themes.ts
841
- import chalk5 from "chalk";
842
- var moonlight = {
843
- simpleView: {
844
- colors: {
845
- trace: chalk5.rgb(114, 135, 153),
846
- // Muted blue-grey for less important info
847
- debug: chalk5.rgb(130, 170, 255),
848
- // Soft blue that pops but doesn't strain
849
- info: chalk5.rgb(195, 232, 141),
850
- // Sage green for good readability
851
- warn: chalk5.rgb(255, 203, 107),
852
- // Warm yellow, less harsh than pure yellow
853
- error: chalk5.rgb(247, 118, 142),
854
- // Soft red that stands out without being aggressive
855
- fatal: chalk5.bgRgb(247, 118, 142).white
856
- // Inverted soft red for maximum visibility
857
- },
858
- logIdColor: chalk5.rgb(84, 98, 117),
859
- // Darker blue-grey for secondary information
860
- dataValueColor: chalk5.rgb(209, 219, 231),
861
- // Light grey-blue for primary content
862
- dataKeyColor: chalk5.rgb(130, 170, 255),
863
- // Matching debug blue for consistency
864
- selectorColor: chalk5.rgb(137, 221, 255)
865
- // Ice blue for selection indicators
866
- },
867
- detailedView: {
868
- colors: {
869
- trace: chalk5.rgb(114, 135, 153),
870
- debug: chalk5.rgb(130, 170, 255),
871
- info: chalk5.rgb(195, 232, 141),
872
- warn: chalk5.rgb(255, 203, 107),
873
- error: chalk5.rgb(247, 118, 142),
874
- fatal: chalk5.bgRgb(247, 118, 142).white
875
- },
876
- logIdColor: chalk5.rgb(84, 98, 117),
877
- dataValueColor: chalk5.rgb(209, 219, 231),
878
- dataKeyColor: chalk5.rgb(130, 170, 255),
879
- selectorColor: chalk5.rgb(137, 221, 255),
880
- // Ice blue for selection indicators
881
- headerColor: chalk5.rgb(137, 221, 255),
882
- labelColor: chalk5.rgb(137, 221, 255).bold,
883
- separatorColor: chalk5.rgb(84, 98, 117),
884
- jsonColors: {
885
- keysColor: chalk5.rgb(130, 170, 255),
886
- dashColor: chalk5.rgb(209, 219, 231),
887
- numberColor: chalk5.rgb(247, 140, 108),
888
- stringColor: chalk5.rgb(195, 232, 141),
889
- multilineStringColor: chalk5.rgb(195, 232, 141),
890
- positiveNumberColor: chalk5.rgb(195, 232, 141),
891
- negativeNumberColor: chalk5.rgb(247, 118, 142),
892
- booleanColor: chalk5.rgb(255, 203, 107),
893
- nullUndefinedColor: chalk5.rgb(114, 135, 153),
894
- dateColor: chalk5.rgb(199, 146, 234)
895
- }
896
- }
794
+ //#endregion
795
+ //#region src/themes.ts
796
+ /**
797
+ * Moonlight - A dark theme with cool blue tones
798
+ * Inspired by moonlit nights and modern IDEs
799
+ *
800
+ * Color Palette:
801
+ * - Primary: Cool blues and soft greens
802
+ * - Accents: Warm yellows and soft reds
803
+ * - Background: Assumes dark terminal (black or very dark grey)
804
+ *
805
+ * Best used with:
806
+ * - Dark terminal themes
807
+ * - Night-time coding sessions
808
+ * - Environments where eye strain is a concern
809
+ */
810
+ const moonlight = {
811
+ simpleView: {
812
+ colors: {
813
+ trace: chalk$1.rgb(114, 135, 153),
814
+ debug: chalk$1.rgb(130, 170, 255),
815
+ info: chalk$1.rgb(195, 232, 141),
816
+ warn: chalk$1.rgb(255, 203, 107),
817
+ error: chalk$1.rgb(247, 118, 142),
818
+ fatal: chalk$1.bgRgb(247, 118, 142).white
819
+ },
820
+ logIdColor: chalk$1.rgb(84, 98, 117),
821
+ dataValueColor: chalk$1.rgb(209, 219, 231),
822
+ dataKeyColor: chalk$1.rgb(130, 170, 255),
823
+ selectorColor: chalk$1.rgb(137, 221, 255)
824
+ },
825
+ detailedView: {
826
+ colors: {
827
+ trace: chalk$1.rgb(114, 135, 153),
828
+ debug: chalk$1.rgb(130, 170, 255),
829
+ info: chalk$1.rgb(195, 232, 141),
830
+ warn: chalk$1.rgb(255, 203, 107),
831
+ error: chalk$1.rgb(247, 118, 142),
832
+ fatal: chalk$1.bgRgb(247, 118, 142).white
833
+ },
834
+ logIdColor: chalk$1.rgb(84, 98, 117),
835
+ dataValueColor: chalk$1.rgb(209, 219, 231),
836
+ dataKeyColor: chalk$1.rgb(130, 170, 255),
837
+ selectorColor: chalk$1.rgb(137, 221, 255),
838
+ headerColor: chalk$1.rgb(137, 221, 255),
839
+ labelColor: chalk$1.rgb(137, 221, 255).bold,
840
+ separatorColor: chalk$1.rgb(84, 98, 117),
841
+ jsonColors: {
842
+ keysColor: chalk$1.rgb(130, 170, 255),
843
+ dashColor: chalk$1.rgb(209, 219, 231),
844
+ numberColor: chalk$1.rgb(247, 140, 108),
845
+ stringColor: chalk$1.rgb(195, 232, 141),
846
+ multilineStringColor: chalk$1.rgb(195, 232, 141),
847
+ positiveNumberColor: chalk$1.rgb(195, 232, 141),
848
+ negativeNumberColor: chalk$1.rgb(247, 118, 142),
849
+ booleanColor: chalk$1.rgb(255, 203, 107),
850
+ nullUndefinedColor: chalk$1.rgb(114, 135, 153),
851
+ dateColor: chalk$1.rgb(199, 146, 234)
852
+ }
853
+ }
897
854
  };
898
- var sunlight = {
899
- simpleView: {
900
- colors: {
901
- trace: chalk5.rgb(110, 110, 110),
902
- // Dark grey for subtle information
903
- debug: chalk5.rgb(32, 96, 159),
904
- // Deep blue for strong contrast on white
905
- info: chalk5.rgb(35, 134, 54),
906
- // Forest green, easier on the eyes than bright green
907
- warn: chalk5.rgb(176, 95, 0),
908
- // Brown-orange for natural warning color
909
- error: chalk5.rgb(191, 0, 0),
910
- // Deep red for clear visibility on light backgrounds
911
- fatal: chalk5.bgRgb(191, 0, 0).white
912
- // White on deep red for critical issues
913
- },
914
- logIdColor: chalk5.rgb(110, 110, 110),
915
- dataValueColor: chalk5.rgb(0, 0, 0),
916
- dataKeyColor: chalk5.rgb(32, 96, 159),
917
- selectorColor: chalk5.rgb(0, 91, 129)
918
- // Deep blue for strong contrast
919
- },
920
- detailedView: {
921
- colors: {
922
- trace: chalk5.rgb(110, 110, 110),
923
- debug: chalk5.rgb(32, 96, 159),
924
- info: chalk5.rgb(35, 134, 54),
925
- warn: chalk5.rgb(176, 95, 0),
926
- error: chalk5.rgb(191, 0, 0),
927
- fatal: chalk5.bgRgb(191, 0, 0).white
928
- },
929
- logIdColor: chalk5.rgb(110, 110, 110),
930
- dataValueColor: chalk5.rgb(0, 0, 0),
931
- dataKeyColor: chalk5.rgb(32, 96, 159),
932
- selectorColor: chalk5.rgb(0, 91, 129),
933
- // Deep blue for strong contrast
934
- headerColor: chalk5.rgb(0, 91, 129),
935
- labelColor: chalk5.rgb(0, 91, 129).bold,
936
- separatorColor: chalk5.rgb(110, 110, 110),
937
- jsonColors: {
938
- keysColor: chalk5.rgb(32, 96, 159),
939
- dashColor: chalk5.rgb(0, 0, 0),
940
- numberColor: chalk5.rgb(170, 55, 49),
941
- stringColor: chalk5.rgb(35, 134, 54),
942
- multilineStringColor: chalk5.rgb(35, 134, 54),
943
- positiveNumberColor: chalk5.rgb(46, 125, 50),
944
- negativeNumberColor: chalk5.rgb(183, 28, 28),
945
- booleanColor: chalk5.rgb(176, 95, 0),
946
- nullUndefinedColor: chalk5.rgb(110, 110, 110),
947
- dateColor: chalk5.rgb(123, 31, 162)
948
- }
949
- }
855
+ /**
856
+ * Sunlight - A light theme with warm tones
857
+ * Inspired by daylight reading and paper documentation
858
+ *
859
+ * Color Palette:
860
+ * - Primary: Deep, rich colors that contrast well with white
861
+ * - Accents: Earth tones and deep jewel tones
862
+ * - Background: Assumes light terminal (white or very light grey)
863
+ *
864
+ * Best used with:
865
+ * - Light terminal themes
866
+ * - Daytime coding sessions
867
+ * - High-glare environments
868
+ * - Printed documentation
869
+ */
870
+ const sunlight = {
871
+ simpleView: {
872
+ colors: {
873
+ trace: chalk$1.rgb(110, 110, 110),
874
+ debug: chalk$1.rgb(32, 96, 159),
875
+ info: chalk$1.rgb(35, 134, 54),
876
+ warn: chalk$1.rgb(176, 95, 0),
877
+ error: chalk$1.rgb(191, 0, 0),
878
+ fatal: chalk$1.bgRgb(191, 0, 0).white
879
+ },
880
+ logIdColor: chalk$1.rgb(110, 110, 110),
881
+ dataValueColor: chalk$1.rgb(0, 0, 0),
882
+ dataKeyColor: chalk$1.rgb(32, 96, 159),
883
+ selectorColor: chalk$1.rgb(0, 91, 129)
884
+ },
885
+ detailedView: {
886
+ colors: {
887
+ trace: chalk$1.rgb(110, 110, 110),
888
+ debug: chalk$1.rgb(32, 96, 159),
889
+ info: chalk$1.rgb(35, 134, 54),
890
+ warn: chalk$1.rgb(176, 95, 0),
891
+ error: chalk$1.rgb(191, 0, 0),
892
+ fatal: chalk$1.bgRgb(191, 0, 0).white
893
+ },
894
+ logIdColor: chalk$1.rgb(110, 110, 110),
895
+ dataValueColor: chalk$1.rgb(0, 0, 0),
896
+ dataKeyColor: chalk$1.rgb(32, 96, 159),
897
+ selectorColor: chalk$1.rgb(0, 91, 129),
898
+ headerColor: chalk$1.rgb(0, 91, 129),
899
+ labelColor: chalk$1.rgb(0, 91, 129).bold,
900
+ separatorColor: chalk$1.rgb(110, 110, 110),
901
+ jsonColors: {
902
+ keysColor: chalk$1.rgb(32, 96, 159),
903
+ dashColor: chalk$1.rgb(0, 0, 0),
904
+ numberColor: chalk$1.rgb(170, 55, 49),
905
+ stringColor: chalk$1.rgb(35, 134, 54),
906
+ multilineStringColor: chalk$1.rgb(35, 134, 54),
907
+ positiveNumberColor: chalk$1.rgb(46, 125, 50),
908
+ negativeNumberColor: chalk$1.rgb(183, 28, 28),
909
+ booleanColor: chalk$1.rgb(176, 95, 0),
910
+ nullUndefinedColor: chalk$1.rgb(110, 110, 110),
911
+ dateColor: chalk$1.rgb(123, 31, 162)
912
+ }
913
+ }
950
914
  };
951
- var neon = {
952
- simpleView: {
953
- colors: {
954
- trace: chalk5.rgb(108, 108, 255),
955
- // Electric blue
956
- debug: chalk5.rgb(255, 82, 246),
957
- // Hot pink
958
- info: chalk5.rgb(0, 255, 163),
959
- // Cyber green
960
- warn: chalk5.rgb(255, 231, 46),
961
- // Electric yellow
962
- error: chalk5.rgb(255, 53, 91),
963
- // Neon red
964
- fatal: chalk5.bgRgb(255, 53, 91).rgb(0, 255, 163)
965
- // Neon red bg with cyber green text
966
- },
967
- logIdColor: chalk5.rgb(187, 134, 252),
968
- // Bright purple
969
- dataValueColor: chalk5.rgb(255, 255, 255),
970
- // Pure white
971
- dataKeyColor: chalk5.rgb(0, 255, 240),
972
- // Cyan
973
- selectorColor: chalk5.rgb(0, 255, 240)
974
- // Electric cyan for cyberpunk feel
975
- },
976
- detailedView: {
977
- colors: {
978
- trace: chalk5.rgb(108, 108, 255),
979
- debug: chalk5.rgb(255, 82, 246),
980
- info: chalk5.rgb(0, 255, 163),
981
- warn: chalk5.rgb(255, 231, 46),
982
- error: chalk5.rgb(255, 53, 91),
983
- fatal: chalk5.bgRgb(255, 53, 91).rgb(0, 255, 163)
984
- },
985
- logIdColor: chalk5.rgb(187, 134, 252),
986
- dataValueColor: chalk5.rgb(255, 255, 255),
987
- dataKeyColor: chalk5.rgb(0, 255, 240),
988
- selectorColor: chalk5.rgb(0, 255, 240),
989
- // Electric cyan for cyberpunk feel
990
- headerColor: chalk5.rgb(255, 82, 246),
991
- labelColor: chalk5.rgb(255, 82, 246).bold,
992
- separatorColor: chalk5.rgb(108, 108, 255),
993
- jsonColors: {
994
- keysColor: chalk5.rgb(0, 255, 240),
995
- dashColor: chalk5.rgb(255, 255, 255),
996
- numberColor: chalk5.rgb(255, 82, 246),
997
- stringColor: chalk5.rgb(0, 255, 163),
998
- multilineStringColor: chalk5.rgb(0, 255, 163),
999
- positiveNumberColor: chalk5.rgb(0, 255, 163),
1000
- negativeNumberColor: chalk5.rgb(255, 53, 91),
1001
- booleanColor: chalk5.rgb(255, 231, 46),
1002
- nullUndefinedColor: chalk5.rgb(108, 108, 255),
1003
- dateColor: chalk5.rgb(187, 134, 252)
1004
- }
1005
- }
915
+ /**
916
+ * Neon - A dark theme with vibrant cyberpunk colors
917
+ * Inspired by neon-lit cityscapes and retro-futuristic aesthetics
918
+ *
919
+ * Color Palette:
920
+ * - Primary: Electric blues and hot pinks
921
+ * - Accents: Bright purples and cyber greens
922
+ * - Background: Assumes dark terminal (black or very dark grey)
923
+ *
924
+ * Best used with:
925
+ * - Dark terminal themes
926
+ * - High-contrast preferences
927
+ * - Modern, tech-focused applications
928
+ */
929
+ const neon = {
930
+ simpleView: {
931
+ colors: {
932
+ trace: chalk$1.rgb(108, 108, 255),
933
+ debug: chalk$1.rgb(255, 82, 246),
934
+ info: chalk$1.rgb(0, 255, 163),
935
+ warn: chalk$1.rgb(255, 231, 46),
936
+ error: chalk$1.rgb(255, 53, 91),
937
+ fatal: chalk$1.bgRgb(255, 53, 91).rgb(0, 255, 163)
938
+ },
939
+ logIdColor: chalk$1.rgb(187, 134, 252),
940
+ dataValueColor: chalk$1.rgb(255, 255, 255),
941
+ dataKeyColor: chalk$1.rgb(0, 255, 240),
942
+ selectorColor: chalk$1.rgb(0, 255, 240)
943
+ },
944
+ detailedView: {
945
+ colors: {
946
+ trace: chalk$1.rgb(108, 108, 255),
947
+ debug: chalk$1.rgb(255, 82, 246),
948
+ info: chalk$1.rgb(0, 255, 163),
949
+ warn: chalk$1.rgb(255, 231, 46),
950
+ error: chalk$1.rgb(255, 53, 91),
951
+ fatal: chalk$1.bgRgb(255, 53, 91).rgb(0, 255, 163)
952
+ },
953
+ logIdColor: chalk$1.rgb(187, 134, 252),
954
+ dataValueColor: chalk$1.rgb(255, 255, 255),
955
+ dataKeyColor: chalk$1.rgb(0, 255, 240),
956
+ selectorColor: chalk$1.rgb(0, 255, 240),
957
+ headerColor: chalk$1.rgb(255, 82, 246),
958
+ labelColor: chalk$1.rgb(255, 82, 246).bold,
959
+ separatorColor: chalk$1.rgb(108, 108, 255),
960
+ jsonColors: {
961
+ keysColor: chalk$1.rgb(0, 255, 240),
962
+ dashColor: chalk$1.rgb(255, 255, 255),
963
+ numberColor: chalk$1.rgb(255, 82, 246),
964
+ stringColor: chalk$1.rgb(0, 255, 163),
965
+ multilineStringColor: chalk$1.rgb(0, 255, 163),
966
+ positiveNumberColor: chalk$1.rgb(0, 255, 163),
967
+ negativeNumberColor: chalk$1.rgb(255, 53, 91),
968
+ booleanColor: chalk$1.rgb(255, 231, 46),
969
+ nullUndefinedColor: chalk$1.rgb(108, 108, 255),
970
+ dateColor: chalk$1.rgb(187, 134, 252)
971
+ }
972
+ }
1006
973
  };
1007
- var nature = {
1008
- simpleView: {
1009
- colors: {
1010
- trace: chalk5.rgb(121, 85, 72),
1011
- // Wooden brown
1012
- debug: chalk5.rgb(46, 125, 50),
1013
- // Forest green
1014
- info: chalk5.rgb(0, 105, 92),
1015
- // Deep teal
1016
- warn: chalk5.rgb(175, 115, 0),
1017
- // Golden amber
1018
- error: chalk5.rgb(183, 28, 28),
1019
- // Autumn red
1020
- fatal: chalk5.bgRgb(183, 28, 28).rgb(255, 250, 240)
1021
- // Red bg with soft white
1022
- },
1023
- logIdColor: chalk5.rgb(93, 64, 55),
1024
- // Deep bark brown
1025
- dataValueColor: chalk5.rgb(27, 27, 27),
1026
- // Near black
1027
- dataKeyColor: chalk5.rgb(56, 142, 60),
1028
- // Leaf green
1029
- selectorColor: chalk5.rgb(0, 105, 92)
1030
- // Deep teal for natural feel
1031
- },
1032
- detailedView: {
1033
- colors: {
1034
- trace: chalk5.rgb(121, 85, 72),
1035
- debug: chalk5.rgb(46, 125, 50),
1036
- info: chalk5.rgb(0, 105, 92),
1037
- warn: chalk5.rgb(175, 115, 0),
1038
- error: chalk5.rgb(183, 28, 28),
1039
- fatal: chalk5.bgRgb(183, 28, 28).rgb(255, 250, 240)
1040
- },
1041
- logIdColor: chalk5.rgb(93, 64, 55),
1042
- dataValueColor: chalk5.rgb(27, 27, 27),
1043
- dataKeyColor: chalk5.rgb(56, 142, 60),
1044
- selectorColor: chalk5.rgb(0, 105, 92),
1045
- // Deep teal for natural feel
1046
- headerColor: chalk5.rgb(0, 77, 64),
1047
- labelColor: chalk5.rgb(0, 77, 64).bold,
1048
- separatorColor: chalk5.rgb(121, 85, 72),
1049
- jsonColors: {
1050
- keysColor: chalk5.rgb(56, 142, 60),
1051
- dashColor: chalk5.rgb(27, 27, 27),
1052
- numberColor: chalk5.rgb(230, 81, 0),
1053
- stringColor: chalk5.rgb(0, 105, 92),
1054
- multilineStringColor: chalk5.rgb(0, 105, 92),
1055
- positiveNumberColor: chalk5.rgb(46, 125, 50),
1056
- negativeNumberColor: chalk5.rgb(183, 28, 28),
1057
- booleanColor: chalk5.rgb(175, 115, 0),
1058
- nullUndefinedColor: chalk5.rgb(121, 85, 72),
1059
- dateColor: chalk5.rgb(123, 31, 162)
1060
- }
1061
- }
974
+ /**
975
+ * Nature - A light theme with organic, earthy colors
976
+ * Inspired by forest landscapes and natural elements
977
+ *
978
+ * Color Palette:
979
+ * - Primary: Deep forest greens and rich browns
980
+ * - Accents: Autumn reds and golden yellows
981
+ * - Background: Assumes light terminal (white or very light grey)
982
+ *
983
+ * Best used with:
984
+ * - Light terminal themes
985
+ * - Nature-inspired interfaces
986
+ * - Applications focusing on readability
987
+ */
988
+ const nature = {
989
+ simpleView: {
990
+ colors: {
991
+ trace: chalk$1.rgb(121, 85, 72),
992
+ debug: chalk$1.rgb(46, 125, 50),
993
+ info: chalk$1.rgb(0, 105, 92),
994
+ warn: chalk$1.rgb(175, 115, 0),
995
+ error: chalk$1.rgb(183, 28, 28),
996
+ fatal: chalk$1.bgRgb(183, 28, 28).rgb(255, 250, 240)
997
+ },
998
+ logIdColor: chalk$1.rgb(93, 64, 55),
999
+ dataValueColor: chalk$1.rgb(27, 27, 27),
1000
+ dataKeyColor: chalk$1.rgb(56, 142, 60),
1001
+ selectorColor: chalk$1.rgb(0, 105, 92)
1002
+ },
1003
+ detailedView: {
1004
+ colors: {
1005
+ trace: chalk$1.rgb(121, 85, 72),
1006
+ debug: chalk$1.rgb(46, 125, 50),
1007
+ info: chalk$1.rgb(0, 105, 92),
1008
+ warn: chalk$1.rgb(175, 115, 0),
1009
+ error: chalk$1.rgb(183, 28, 28),
1010
+ fatal: chalk$1.bgRgb(183, 28, 28).rgb(255, 250, 240)
1011
+ },
1012
+ logIdColor: chalk$1.rgb(93, 64, 55),
1013
+ dataValueColor: chalk$1.rgb(27, 27, 27),
1014
+ dataKeyColor: chalk$1.rgb(56, 142, 60),
1015
+ selectorColor: chalk$1.rgb(0, 105, 92),
1016
+ headerColor: chalk$1.rgb(0, 77, 64),
1017
+ labelColor: chalk$1.rgb(0, 77, 64).bold,
1018
+ separatorColor: chalk$1.rgb(121, 85, 72),
1019
+ jsonColors: {
1020
+ keysColor: chalk$1.rgb(56, 142, 60),
1021
+ dashColor: chalk$1.rgb(27, 27, 27),
1022
+ numberColor: chalk$1.rgb(230, 81, 0),
1023
+ stringColor: chalk$1.rgb(0, 105, 92),
1024
+ multilineStringColor: chalk$1.rgb(0, 105, 92),
1025
+ positiveNumberColor: chalk$1.rgb(46, 125, 50),
1026
+ negativeNumberColor: chalk$1.rgb(183, 28, 28),
1027
+ booleanColor: chalk$1.rgb(175, 115, 0),
1028
+ nullUndefinedColor: chalk$1.rgb(121, 85, 72),
1029
+ dateColor: chalk$1.rgb(123, 31, 162)
1030
+ }
1031
+ }
1062
1032
  };
1063
- var pastel = {
1064
- simpleView: {
1065
- colors: {
1066
- trace: chalk5.rgb(179, 189, 203),
1067
- // Soft slate blue
1068
- debug: chalk5.rgb(189, 178, 255),
1069
- // Gentle lavender
1070
- info: chalk5.rgb(170, 236, 205),
1071
- // Mint green
1072
- warn: chalk5.rgb(255, 223, 186),
1073
- // Peach
1074
- error: chalk5.rgb(255, 188, 188),
1075
- // Soft coral
1076
- fatal: chalk5.bgRgb(255, 188, 188).rgb(76, 40, 40)
1077
- // Coral bg with deep brown
1078
- },
1079
- logIdColor: chalk5.rgb(203, 195, 227),
1080
- // Dusty lavender
1081
- dataValueColor: chalk5.rgb(236, 236, 236),
1082
- // Soft white
1083
- dataKeyColor: chalk5.rgb(186, 207, 255),
1084
- // Baby blue
1085
- selectorColor: chalk5.rgb(189, 178, 255)
1086
- // Soft lavender for gentle highlighting
1087
- },
1088
- detailedView: {
1089
- colors: {
1090
- trace: chalk5.rgb(179, 189, 203),
1091
- debug: chalk5.rgb(189, 178, 255),
1092
- info: chalk5.rgb(170, 236, 205),
1093
- warn: chalk5.rgb(255, 223, 186),
1094
- error: chalk5.rgb(255, 188, 188),
1095
- fatal: chalk5.bgRgb(255, 188, 188).rgb(76, 40, 40)
1096
- },
1097
- logIdColor: chalk5.rgb(203, 195, 227),
1098
- dataValueColor: chalk5.rgb(236, 236, 236),
1099
- dataKeyColor: chalk5.rgb(186, 207, 255),
1100
- selectorColor: chalk5.rgb(189, 178, 255),
1101
- // Soft lavender for gentle highlighting
1102
- headerColor: chalk5.rgb(255, 198, 255),
1103
- // Cotton candy pink
1104
- labelColor: chalk5.rgb(255, 198, 255).bold,
1105
- separatorColor: chalk5.rgb(179, 189, 203),
1106
- jsonColors: {
1107
- keysColor: chalk5.rgb(186, 207, 255),
1108
- // Baby blue
1109
- dashColor: chalk5.rgb(236, 236, 236),
1110
- // Soft white
1111
- numberColor: chalk5.rgb(255, 198, 255),
1112
- // Cotton candy pink
1113
- stringColor: chalk5.rgb(170, 236, 205),
1114
- // Mint green
1115
- multilineStringColor: chalk5.rgb(170, 236, 205),
1116
- positiveNumberColor: chalk5.rgb(170, 236, 205),
1117
- negativeNumberColor: chalk5.rgb(255, 188, 188),
1118
- booleanColor: chalk5.rgb(255, 223, 186),
1119
- // Peach
1120
- nullUndefinedColor: chalk5.rgb(179, 189, 203),
1121
- dateColor: chalk5.rgb(189, 178, 255)
1122
- // Gentle lavender
1123
- }
1124
- }
1033
+ /**
1034
+ * Pastel - A soft, calming theme with gentle colors
1035
+ * Inspired by watercolor paintings and cotton candy skies
1036
+ *
1037
+ * Color Palette:
1038
+ * - Primary: Soft pinks and lavenders
1039
+ * - Accents: Mint greens and baby blues
1040
+ * - Background: Assumes dark terminal (black or very dark grey)
1041
+ *
1042
+ * Best used with:
1043
+ * - Dark terminal themes
1044
+ * - Long coding sessions where eye comfort is priority
1045
+ * - Environments where a gentler aesthetic is preferred
1046
+ * - Applications focusing on reduced visual stress
1047
+ */
1048
+ const pastel = {
1049
+ simpleView: {
1050
+ colors: {
1051
+ trace: chalk$1.rgb(179, 189, 203),
1052
+ debug: chalk$1.rgb(189, 178, 255),
1053
+ info: chalk$1.rgb(170, 236, 205),
1054
+ warn: chalk$1.rgb(255, 223, 186),
1055
+ error: chalk$1.rgb(255, 188, 188),
1056
+ fatal: chalk$1.bgRgb(255, 188, 188).rgb(76, 40, 40)
1057
+ },
1058
+ logIdColor: chalk$1.rgb(203, 195, 227),
1059
+ dataValueColor: chalk$1.rgb(236, 236, 236),
1060
+ dataKeyColor: chalk$1.rgb(186, 207, 255),
1061
+ selectorColor: chalk$1.rgb(189, 178, 255)
1062
+ },
1063
+ detailedView: {
1064
+ colors: {
1065
+ trace: chalk$1.rgb(179, 189, 203),
1066
+ debug: chalk$1.rgb(189, 178, 255),
1067
+ info: chalk$1.rgb(170, 236, 205),
1068
+ warn: chalk$1.rgb(255, 223, 186),
1069
+ error: chalk$1.rgb(255, 188, 188),
1070
+ fatal: chalk$1.bgRgb(255, 188, 188).rgb(76, 40, 40)
1071
+ },
1072
+ logIdColor: chalk$1.rgb(203, 195, 227),
1073
+ dataValueColor: chalk$1.rgb(236, 236, 236),
1074
+ dataKeyColor: chalk$1.rgb(186, 207, 255),
1075
+ selectorColor: chalk$1.rgb(189, 178, 255),
1076
+ headerColor: chalk$1.rgb(255, 198, 255),
1077
+ labelColor: chalk$1.rgb(255, 198, 255).bold,
1078
+ separatorColor: chalk$1.rgb(179, 189, 203),
1079
+ jsonColors: {
1080
+ keysColor: chalk$1.rgb(186, 207, 255),
1081
+ dashColor: chalk$1.rgb(236, 236, 236),
1082
+ numberColor: chalk$1.rgb(255, 198, 255),
1083
+ stringColor: chalk$1.rgb(170, 236, 205),
1084
+ multilineStringColor: chalk$1.rgb(170, 236, 205),
1085
+ positiveNumberColor: chalk$1.rgb(170, 236, 205),
1086
+ negativeNumberColor: chalk$1.rgb(255, 188, 188),
1087
+ booleanColor: chalk$1.rgb(255, 223, 186),
1088
+ nullUndefinedColor: chalk$1.rgb(179, 189, 203),
1089
+ dateColor: chalk$1.rgb(189, 178, 255)
1090
+ }
1091
+ }
1125
1092
  };
1126
1093
 
1127
- // src/UIManager.ts
1128
- import chalk6 from "chalk";
1129
- import keypress from "keypress";
1094
+ //#endregion
1095
+ //#region src/UIManager.ts
1096
+ /**
1097
+ * Manages UI state and user interaction.
1098
+ * Acts as the controller between the storage and rendering layers.
1099
+ * Handles all keyboard input and view transitions.
1100
+ */
1130
1101
  var UIManager = class {
1131
- /**
1132
- * Creates a new UIManager instance.
1133
- * Sets up keyboard handling and cleanup handlers.
1134
- *
1135
- * @param renderer - Instance for handling log display
1136
- * @param storage - Instance for log persistence
1137
- * @param disableInteractiveMode - Whether to disable interactive mode
1138
- */
1139
- constructor(renderer, storage, disableInteractiveMode = false) {
1140
- this.renderer = renderer;
1141
- this.storage = storage;
1142
- this.isInteractiveDisabled = disableInteractiveMode;
1143
- if (!this.isInteractiveDisabled) {
1144
- this.setupKeyboardHandling();
1145
- }
1146
- process.stdout.on("resize", () => {
1147
- this.termWidth = process.stdout.columns || 80;
1148
- if (this.isDetailView) {
1149
- const entry = this.logs[this.selectedIndex];
1150
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1151
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1152
- this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos());
1153
- } else if (this.isSelectionMode) {
1154
- this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1155
- }
1156
- });
1157
- }
1158
- /** Whether the UI is in log selection mode */
1159
- isSelectionMode = false;
1160
- /** Whether the UI is showing detailed log view */
1161
- isDetailView = false;
1162
- /** Whether log streaming is paused */
1163
- isPaused = false;
1164
- /** Whether interactive mode is disabled */
1165
- isInteractiveDisabled;
1166
- /** Buffer for logs received while paused */
1167
- pauseBuffer = [];
1168
- /** Buffer for new logs in selection mode */
1169
- selectionBuffer = [];
1170
- /** Index of currently selected log entry */
1171
- selectedIndex = 0;
1172
- /** Current filter text for log searching */
1173
- filterText = "";
1174
- /** Cached array of filtered log entries */
1175
- logs = [];
1176
- /** Polling interval for checking new logs in detail view */
1177
- detailViewPollInterval = null;
1178
- /** Polling interval for checking new logs in selection view */
1179
- selectionViewPollInterval = null;
1180
- /** Current terminal width in characters */
1181
- termWidth = process.stdout.columns || 80;
1182
- /**
1183
- * Sets up keyboard input handling and cleanup.
1184
- * Configures raw mode for immediate key processing.
1185
- * Sets up process exit handlers for cleanup.
1186
- * @private
1187
- */
1188
- setupKeyboardHandling() {
1189
- keypress(process.stdin);
1190
- process.stdin.setRawMode(true);
1191
- process.stdin.on("keypress", this.handleKeypress.bind(this));
1192
- const cleanup = () => {
1193
- if (this.detailViewPollInterval) {
1194
- clearInterval(this.detailViewPollInterval);
1195
- }
1196
- if (this.selectionViewPollInterval) {
1197
- clearInterval(this.selectionViewPollInterval);
1198
- }
1199
- process.stdin.setRawMode(false);
1200
- process.stdin.removeAllListeners("keypress");
1201
- console.clear();
1202
- this.storage.close();
1203
- process.exit(0);
1204
- };
1205
- process.on("SIGINT", cleanup);
1206
- process.on("SIGTERM", cleanup);
1207
- }
1208
- /**
1209
- * Starts polling for new logs in selection view
1210
- * @private
1211
- */
1212
- startSelectionViewPolling() {
1213
- if (this.selectionViewPollInterval) {
1214
- clearInterval(this.selectionViewPollInterval);
1215
- }
1216
- this.selectionViewPollInterval = setInterval(() => {
1217
- if (!this.isSelectionMode) {
1218
- clearInterval(this.selectionViewPollInterval);
1219
- this.selectionViewPollInterval = null;
1220
- return;
1221
- }
1222
- if (this.selectionBuffer.length > 0) {
1223
- this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1224
- }
1225
- }, 2e3);
1226
- }
1227
- /**
1228
- * Starts polling for new logs in detail view
1229
- * @private
1230
- */
1231
- startDetailViewPolling() {
1232
- if (this.detailViewPollInterval) {
1233
- clearInterval(this.detailViewPollInterval);
1234
- }
1235
- this.detailViewPollInterval = setInterval(() => {
1236
- if (!this.isDetailView) {
1237
- clearInterval(this.detailViewPollInterval);
1238
- this.detailViewPollInterval = null;
1239
- return;
1240
- }
1241
- const totalCount = this.filterText ? this.storage.getFilteredLogCount(this.filterText) : this.storage.getLogCount();
1242
- if (totalCount !== this.logs.length) {
1243
- const storedLogs = this.filterText ? this.storage.searchLogs(this.filterText) : this.storage.getAllLogs();
1244
- this.logs = storedLogs;
1245
- const entry = this.logs[this.selectedIndex];
1246
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1247
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1248
- this.renderer.renderDetailView(
1249
- entry,
1250
- prevEntry,
1251
- nextEntry,
1252
- this.renderer.getDetailViewScrollPos(),
1253
- this.selectedIndex + 1,
1254
- totalCount,
1255
- this.filterText
1256
- );
1257
- }
1258
- }, 2e3);
1259
- }
1260
- /**
1261
- * Updates the filtered list of logs based on search text.
1262
- * Handles both full list and filtered views.
1263
- * Maintains selection state when filter changes.
1264
- * @private
1265
- */
1266
- updateFilteredLogs() {
1267
- const totalCount = this.filterText ? this.storage.getFilteredLogCount(this.filterText) : this.storage.getLogCount();
1268
- const effectiveCount = totalCount - this.selectionBuffer.length;
1269
- if (effectiveCount !== this.logs.length) {
1270
- const storedLogs = this.filterText ? this.storage.searchLogs(this.filterText) : this.storage.getAllLogs();
1271
- this.logs = storedLogs.slice(0, storedLogs.length - this.selectionBuffer.length);
1272
- if (this.logs.length > 0) {
1273
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.logs.length - 1));
1274
- } else {
1275
- this.selectedIndex = 0;
1276
- }
1277
- }
1278
- }
1279
- updateNewLogsNotification() {
1280
- if (!this.isSelectionMode || !this.selectionBuffer.length) return;
1281
- process.stdout.write(`\x1B[2A\r${" ".repeat(this.termWidth)}\r`);
1282
- const notification = chalk6.dim(
1283
- `${this.selectionBuffer.length} new log${this.selectionBuffer.length === 1 ? "" : "s"} available (press \u2193 to view)`
1284
- );
1285
- process.stdout.write(`${notification}
1286
-
1287
- `);
1288
- }
1289
- /**
1290
- * Handles new log entries from the transport.
1291
- * Stores the log and updates the display if needed.
1292
- *
1293
- * @param entry - New log entry to process
1294
- */
1295
- handleNewLog(entry) {
1296
- this.storage.store(entry);
1297
- if (this.isInteractiveDisabled) {
1298
- this.renderer.renderLogLine(entry);
1299
- return;
1300
- }
1301
- if (this.isSelectionMode) {
1302
- this.selectionBuffer.push(entry);
1303
- this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1304
- return;
1305
- }
1306
- if (!this.isDetailView) {
1307
- if (this.isPaused) {
1308
- this.pauseBuffer.push(entry);
1309
- process.stdout.write(`\r${chalk6.yellow(`\u23F8 Paused (${this.pauseBuffer.length} new logs)`)}${" ".repeat(20)}`);
1310
- } else {
1311
- this.renderer.renderLogLine(entry);
1312
- }
1313
- }
1314
- }
1315
- /**
1316
- * Processes keyboard input and manages UI state.
1317
- * Handles navigation, mode switching, and filtering.
1318
- *
1319
- * Key mappings:
1320
- * - CTRL+C: Exit application
1321
- * - TAB: Toggle selection mode
1322
- * - Up/Down: Navigate logs
1323
- * - Enter: View log details
1324
- * - Backspace: Edit filter
1325
- * - P: Toggle pause in simple view
1326
- * - Other keys: Add to filter
1327
- *
1328
- * @param ch - Character input if available
1329
- * @param key - Key event information
1330
- * @private
1331
- */
1332
- handleKeypress(ch, key) {
1333
- if (key?.ctrl && key?.name === "c") {
1334
- process.stdin.setRawMode(false);
1335
- process.stdin.removeAllListeners("keypress");
1336
- console.clear();
1337
- this.storage.close();
1338
- process.exit(0);
1339
- return;
1340
- }
1341
- if (!this.isSelectionMode && !this.isDetailView && ch === "p") {
1342
- this.isPaused = !this.isPaused;
1343
- if (!this.isPaused && this.pauseBuffer.length > 0) {
1344
- process.stdout.write(`\r${" ".repeat(50)}\r`);
1345
- for (const entry of this.pauseBuffer) {
1346
- this.renderer.renderLogLine(entry);
1347
- }
1348
- this.pauseBuffer = [];
1349
- } else if (this.isPaused) {
1350
- process.stdout.write(`\r${chalk6.yellow("\u23F8 Paused (0 new logs)")}${" ".repeat(20)}`);
1351
- }
1352
- return;
1353
- }
1354
- if (!this.isSelectionMode && !this.isDetailView && (key?.name === "up" || key?.name === "down")) {
1355
- this.isSelectionMode = true;
1356
- this.filterText = "";
1357
- this.isPaused = true;
1358
- const storedLogs = this.storage.getAllLogs();
1359
- this.logs = storedLogs;
1360
- this.selectedIndex = key.name === "up" ? Math.max(0, this.logs.length - 1) : Math.max(0, this.logs.length - 1);
1361
- this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1362
- this.startSelectionViewPolling();
1363
- return;
1364
- }
1365
- if (!this.isSelectionMode && !this.isDetailView && ch === "c") {
1366
- const newMode = this.renderer.cycleViewMode();
1367
- console.clear();
1368
- const logs = this.storage.getAllLogs();
1369
- for (const entry of logs) {
1370
- this.renderer.renderLogLine(entry);
1371
- }
1372
- const modeDescriptions = {
1373
- full: "Full view enabled (all data shown without truncation)",
1374
- truncated: "Truncated view enabled (timestamp, ID, level, message, truncated data)",
1375
- condensed: "Condensed view enabled (timestamp, level and message only)"
1376
- };
1377
- process.stdout.write(`\r${chalk6.cyan(`\u2139 ${modeDescriptions[newMode]}`)}${" ".repeat(20)}`);
1378
- setTimeout(() => {
1379
- process.stdout.write(`\r${" ".repeat(100)}\r`);
1380
- }, 3e3);
1381
- return;
1382
- }
1383
- if (this.isSelectionMode) {
1384
- if (key) {
1385
- switch (key.name) {
1386
- case "up": {
1387
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
1388
- this.renderer.renderSelectionView(
1389
- this.logs,
1390
- this.selectedIndex,
1391
- this.filterText,
1392
- this.selectionBuffer.length
1393
- );
1394
- return;
1395
- }
1396
- case "down": {
1397
- if (this.selectedIndex === this.logs.length - 1 && this.selectionBuffer.length > 0) {
1398
- this.logs.push(...this.selectionBuffer);
1399
- this.selectionBuffer = [];
1400
- }
1401
- this.selectedIndex = Math.min(this.logs.length - 1, this.selectedIndex + 1);
1402
- this.renderer.renderSelectionView(
1403
- this.logs,
1404
- this.selectedIndex,
1405
- this.filterText,
1406
- this.selectionBuffer.length
1407
- );
1408
- return;
1409
- }
1410
- case "return": {
1411
- if (this.logs.length > 0) {
1412
- this.isSelectionMode = false;
1413
- this.isDetailView = true;
1414
- console.clear();
1415
- const selectedEntry = this.logs[this.selectedIndex];
1416
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1417
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1418
- this.renderer.renderDetailView(
1419
- selectedEntry,
1420
- prevEntry,
1421
- nextEntry,
1422
- 0,
1423
- this.selectedIndex + 1,
1424
- this.logs.length,
1425
- this.filterText
1426
- );
1427
- this.startDetailViewPolling();
1428
- }
1429
- return;
1430
- }
1431
- case "backspace": {
1432
- if (this.filterText.length > 0) {
1433
- this.filterText = this.filterText.slice(0, -1);
1434
- this.updateFilteredLogs();
1435
- this.renderer.renderSelectionView(
1436
- this.logs,
1437
- this.selectedIndex,
1438
- this.filterText,
1439
- this.selectionBuffer.length
1440
- );
1441
- }
1442
- return;
1443
- }
1444
- case "tab": {
1445
- this.isSelectionMode = false;
1446
- this.isPaused = false;
1447
- if (this.selectionViewPollInterval) {
1448
- clearInterval(this.selectionViewPollInterval);
1449
- this.selectionViewPollInterval = null;
1450
- }
1451
- this.filterText = "";
1452
- console.clear();
1453
- const logs = this.storage.getAllLogs();
1454
- for (const entry of logs) {
1455
- this.renderer.renderLogLine(entry);
1456
- }
1457
- if (this.selectionBuffer.length > 0) {
1458
- for (const entry of this.selectionBuffer) {
1459
- this.renderer.renderLogLine(entry);
1460
- }
1461
- this.selectionBuffer = [];
1462
- }
1463
- return;
1464
- }
1465
- }
1466
- }
1467
- if (ch && (!key || !key.ctrl && !key.meta) && ch.length === 1) {
1468
- this.filterText += ch;
1469
- this.updateFilteredLogs();
1470
- this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1471
- return;
1472
- }
1473
- }
1474
- if (this.isDetailView) {
1475
- if (key?.name) {
1476
- switch (key.name) {
1477
- case "up": {
1478
- this.renderer.scrollDetailView(-1);
1479
- const entry = this.logs[this.selectedIndex];
1480
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1481
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1482
- this.renderer.renderDetailView(
1483
- entry,
1484
- prevEntry,
1485
- nextEntry,
1486
- this.renderer.getDetailViewScrollPos(),
1487
- this.selectedIndex + 1,
1488
- this.logs.length,
1489
- this.filterText
1490
- );
1491
- break;
1492
- }
1493
- case "down": {
1494
- this.renderer.scrollDetailView(1);
1495
- const entry = this.logs[this.selectedIndex];
1496
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1497
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1498
- this.renderer.renderDetailView(
1499
- entry,
1500
- prevEntry,
1501
- nextEntry,
1502
- this.renderer.getDetailViewScrollPos(),
1503
- this.selectedIndex + 1,
1504
- this.logs.length,
1505
- this.filterText
1506
- );
1507
- break;
1508
- }
1509
- case "left": {
1510
- if (this.selectedIndex > 0) {
1511
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
1512
- const entry = this.logs[this.selectedIndex];
1513
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1514
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1515
- this.renderer.renderDetailView(
1516
- entry,
1517
- prevEntry,
1518
- nextEntry,
1519
- 0,
1520
- this.selectedIndex + 1,
1521
- this.logs.length,
1522
- this.filterText
1523
- );
1524
- }
1525
- break;
1526
- }
1527
- case "right": {
1528
- if (this.selectedIndex < this.logs.length - 1) {
1529
- this.selectedIndex = Math.min(this.logs.length - 1, this.selectedIndex + 1);
1530
- const entry = this.logs[this.selectedIndex];
1531
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1532
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1533
- this.renderer.renderDetailView(
1534
- entry,
1535
- prevEntry,
1536
- nextEntry,
1537
- 0,
1538
- this.selectedIndex + 1,
1539
- this.logs.length,
1540
- this.filterText
1541
- );
1542
- }
1543
- break;
1544
- }
1545
- case "tab": {
1546
- if (this.renderer.isInJsonView()) {
1547
- this.renderer.toggleJsonView();
1548
- const entry = this.logs[this.selectedIndex];
1549
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1550
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1551
- this.renderer.renderDetailView(
1552
- entry,
1553
- prevEntry,
1554
- nextEntry,
1555
- this.renderer.getDetailViewScrollPos(),
1556
- this.selectedIndex + 1,
1557
- this.logs.length,
1558
- this.filterText
1559
- );
1560
- break;
1561
- }
1562
- this.isDetailView = false;
1563
- if (this.detailViewPollInterval) {
1564
- clearInterval(this.detailViewPollInterval);
1565
- this.detailViewPollInterval = null;
1566
- }
1567
- this.isSelectionMode = true;
1568
- this.updateFilteredLogs();
1569
- this.renderer.renderSelectionView(
1570
- this.logs,
1571
- this.selectedIndex,
1572
- this.filterText,
1573
- this.selectionBuffer.length
1574
- );
1575
- break;
1576
- }
1577
- }
1578
- }
1579
- if (ch) {
1580
- switch (ch.toLowerCase()) {
1581
- case "w": {
1582
- this.renderer.scrollDetailView(process.stdout.rows - 8);
1583
- const entry = this.logs[this.selectedIndex];
1584
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1585
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1586
- this.renderer.renderDetailView(
1587
- entry,
1588
- prevEntry,
1589
- nextEntry,
1590
- this.renderer.getDetailViewScrollPos(),
1591
- this.selectedIndex + 1,
1592
- this.logs.length
1593
- );
1594
- break;
1595
- }
1596
- case "q": {
1597
- this.renderer.scrollDetailView(-process.stdout.rows + 8);
1598
- const entry = this.logs[this.selectedIndex];
1599
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1600
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1601
- this.renderer.renderDetailView(
1602
- entry,
1603
- prevEntry,
1604
- nextEntry,
1605
- this.renderer.getDetailViewScrollPos(),
1606
- this.selectedIndex + 1,
1607
- this.logs.length
1608
- );
1609
- break;
1610
- }
1611
- case "a": {
1612
- if (this.selectedIndex !== 0) {
1613
- this.selectedIndex = 0;
1614
- const entry = this.logs[this.selectedIndex];
1615
- const prevEntry = null;
1616
- const nextEntry = this.logs.length > 1 ? this.logs[1] : null;
1617
- this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, 1, this.logs.length);
1618
- }
1619
- break;
1620
- }
1621
- case "s": {
1622
- const lastIndex = this.logs.length - 1;
1623
- if (this.selectedIndex !== lastIndex) {
1624
- this.selectedIndex = lastIndex;
1625
- const entry = this.logs[this.selectedIndex];
1626
- const prevEntry = lastIndex > 0 ? this.logs[lastIndex - 1] : null;
1627
- const nextEntry = null;
1628
- this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, this.logs.length, this.logs.length);
1629
- }
1630
- break;
1631
- }
1632
- case "c": {
1633
- this.renderer.toggleArrayCollapse();
1634
- const entry = this.logs[this.selectedIndex];
1635
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1636
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1637
- this.renderer.renderDetailView(
1638
- entry,
1639
- prevEntry,
1640
- nextEntry,
1641
- this.renderer.getDetailViewScrollPos(),
1642
- this.selectedIndex + 1,
1643
- this.logs.length
1644
- );
1645
- break;
1646
- }
1647
- case "j": {
1648
- this.renderer.toggleJsonView();
1649
- const entry = this.logs[this.selectedIndex];
1650
- const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1651
- const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1652
- this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, this.selectedIndex + 1, this.logs.length);
1653
- break;
1654
- }
1655
- }
1656
- }
1657
- }
1658
- }
1102
+ /** Whether the UI is in log selection mode */
1103
+ isSelectionMode = false;
1104
+ /** Whether the UI is showing detailed log view */
1105
+ isDetailView = false;
1106
+ /** Whether log streaming is paused */
1107
+ isPaused = false;
1108
+ /** Whether interactive mode is disabled */
1109
+ isInteractiveDisabled;
1110
+ /** Buffer for logs received while paused */
1111
+ pauseBuffer = [];
1112
+ /** Buffer for new logs in selection mode */
1113
+ selectionBuffer = [];
1114
+ /** Index of currently selected log entry */
1115
+ selectedIndex = 0;
1116
+ /** Current filter text for log searching */
1117
+ filterText = "";
1118
+ /** Cached array of filtered log entries */
1119
+ logs = [];
1120
+ /** Polling interval for checking new logs in detail view */
1121
+ detailViewPollInterval = null;
1122
+ /** Polling interval for checking new logs in selection view */
1123
+ selectionViewPollInterval = null;
1124
+ /** Current terminal width in characters */
1125
+ termWidth = process.stdout.columns || 80;
1126
+ /**
1127
+ * Creates a new UIManager instance.
1128
+ * Sets up keyboard handling and cleanup handlers.
1129
+ *
1130
+ * @param renderer - Instance for handling log display
1131
+ * @param storage - Instance for log persistence
1132
+ * @param disableInteractiveMode - Whether to disable interactive mode
1133
+ */
1134
+ constructor(renderer, storage, disableInteractiveMode = false) {
1135
+ this.renderer = renderer;
1136
+ this.storage = storage;
1137
+ this.isInteractiveDisabled = disableInteractiveMode;
1138
+ if (!this.isInteractiveDisabled) this.setupKeyboardHandling();
1139
+ process.stdout.on("resize", () => {
1140
+ this.termWidth = process.stdout.columns || 80;
1141
+ if (this.isDetailView) {
1142
+ const entry = this.logs[this.selectedIndex];
1143
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1144
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1145
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos());
1146
+ } else if (this.isSelectionMode) this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1147
+ });
1148
+ }
1149
+ /**
1150
+ * Sets up keyboard input handling and cleanup.
1151
+ * Configures raw mode for immediate key processing.
1152
+ * Sets up process exit handlers for cleanup.
1153
+ * @private
1154
+ */
1155
+ setupKeyboardHandling() {
1156
+ keypress(process.stdin);
1157
+ process.stdin.setRawMode(true);
1158
+ process.stdin.on("keypress", this.handleKeypress.bind(this));
1159
+ const cleanup = () => {
1160
+ if (this.detailViewPollInterval) clearInterval(this.detailViewPollInterval);
1161
+ if (this.selectionViewPollInterval) clearInterval(this.selectionViewPollInterval);
1162
+ process.stdin.setRawMode(false);
1163
+ process.stdin.removeAllListeners("keypress");
1164
+ console.clear();
1165
+ this.storage.close();
1166
+ process.exit(0);
1167
+ };
1168
+ process.on("SIGINT", cleanup);
1169
+ process.on("SIGTERM", cleanup);
1170
+ }
1171
+ /**
1172
+ * Starts polling for new logs in selection view
1173
+ * @private
1174
+ */
1175
+ startSelectionViewPolling() {
1176
+ if (this.selectionViewPollInterval) clearInterval(this.selectionViewPollInterval);
1177
+ this.selectionViewPollInterval = setInterval(() => {
1178
+ if (!this.isSelectionMode) {
1179
+ clearInterval(this.selectionViewPollInterval);
1180
+ this.selectionViewPollInterval = null;
1181
+ return;
1182
+ }
1183
+ if (this.selectionBuffer.length > 0) this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1184
+ }, 2e3);
1185
+ }
1186
+ /**
1187
+ * Starts polling for new logs in detail view
1188
+ * @private
1189
+ */
1190
+ startDetailViewPolling() {
1191
+ if (this.detailViewPollInterval) clearInterval(this.detailViewPollInterval);
1192
+ this.detailViewPollInterval = setInterval(() => {
1193
+ if (!this.isDetailView) {
1194
+ clearInterval(this.detailViewPollInterval);
1195
+ this.detailViewPollInterval = null;
1196
+ return;
1197
+ }
1198
+ const totalCount = this.filterText ? this.storage.getFilteredLogCount(this.filterText) : this.storage.getLogCount();
1199
+ if (totalCount !== this.logs.length) {
1200
+ this.logs = this.filterText ? this.storage.searchLogs(this.filterText) : this.storage.getAllLogs();
1201
+ const entry = this.logs[this.selectedIndex];
1202
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1203
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1204
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, totalCount, this.filterText);
1205
+ }
1206
+ }, 2e3);
1207
+ }
1208
+ /**
1209
+ * Updates the filtered list of logs based on search text.
1210
+ * Handles both full list and filtered views.
1211
+ * Maintains selection state when filter changes.
1212
+ * @private
1213
+ */
1214
+ updateFilteredLogs() {
1215
+ if ((this.filterText ? this.storage.getFilteredLogCount(this.filterText) : this.storage.getLogCount()) - this.selectionBuffer.length !== this.logs.length) {
1216
+ const storedLogs = this.filterText ? this.storage.searchLogs(this.filterText) : this.storage.getAllLogs();
1217
+ this.logs = storedLogs.slice(0, storedLogs.length - this.selectionBuffer.length);
1218
+ if (this.logs.length > 0) this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.logs.length - 1));
1219
+ else this.selectedIndex = 0;
1220
+ }
1221
+ }
1222
+ updateNewLogsNotification() {
1223
+ if (!this.isSelectionMode || !this.selectionBuffer.length) return;
1224
+ process.stdout.write(`\x1b[2A\r${" ".repeat(this.termWidth)}\r`);
1225
+ const notification = chalk$1.dim(`${this.selectionBuffer.length} new log${this.selectionBuffer.length === 1 ? "" : "s"} available (press ↓ to view)`);
1226
+ process.stdout.write(`${notification}\n\n`);
1227
+ }
1228
+ /**
1229
+ * Handles new log entries from the transport.
1230
+ * Stores the log and updates the display if needed.
1231
+ *
1232
+ * @param entry - New log entry to process
1233
+ */
1234
+ handleNewLog(entry) {
1235
+ this.storage.store(entry);
1236
+ if (this.isInteractiveDisabled) {
1237
+ this.renderer.renderLogLine(entry);
1238
+ return;
1239
+ }
1240
+ if (this.isSelectionMode) {
1241
+ this.selectionBuffer.push(entry);
1242
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1243
+ return;
1244
+ }
1245
+ if (!this.isDetailView) if (this.isPaused) {
1246
+ this.pauseBuffer.push(entry);
1247
+ process.stdout.write(`\r${chalk$1.yellow(`⏸ Paused (${this.pauseBuffer.length} new logs)`)}${" ".repeat(20)}`);
1248
+ } else this.renderer.renderLogLine(entry);
1249
+ }
1250
+ /**
1251
+ * Processes keyboard input and manages UI state.
1252
+ * Handles navigation, mode switching, and filtering.
1253
+ *
1254
+ * Key mappings:
1255
+ * - CTRL+C: Exit application
1256
+ * - TAB: Toggle selection mode
1257
+ * - Up/Down: Navigate logs
1258
+ * - Enter: View log details
1259
+ * - Backspace: Edit filter
1260
+ * - P: Toggle pause in simple view
1261
+ * - Other keys: Add to filter
1262
+ *
1263
+ * @param ch - Character input if available
1264
+ * @param key - Key event information
1265
+ * @private
1266
+ */
1267
+ handleKeypress(ch, key) {
1268
+ if (key?.ctrl && key?.name === "c") {
1269
+ process.stdin.setRawMode(false);
1270
+ process.stdin.removeAllListeners("keypress");
1271
+ console.clear();
1272
+ this.storage.close();
1273
+ process.exit(0);
1274
+ return;
1275
+ }
1276
+ if (!this.isSelectionMode && !this.isDetailView && ch === "p") {
1277
+ this.isPaused = !this.isPaused;
1278
+ if (!this.isPaused && this.pauseBuffer.length > 0) {
1279
+ process.stdout.write(`\r${" ".repeat(50)}\r`);
1280
+ for (const entry of this.pauseBuffer) this.renderer.renderLogLine(entry);
1281
+ this.pauseBuffer = [];
1282
+ } else if (this.isPaused) process.stdout.write(`\r${chalk$1.yellow("⏸ Paused (0 new logs)")}${" ".repeat(20)}`);
1283
+ return;
1284
+ }
1285
+ if (!this.isSelectionMode && !this.isDetailView && (key?.name === "up" || key?.name === "down")) {
1286
+ this.isSelectionMode = true;
1287
+ this.filterText = "";
1288
+ this.isPaused = true;
1289
+ this.logs = this.storage.getAllLogs();
1290
+ this.selectedIndex = key.name === "up" ? Math.max(0, this.logs.length - 1) : Math.max(0, this.logs.length - 1);
1291
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1292
+ this.startSelectionViewPolling();
1293
+ return;
1294
+ }
1295
+ if (!this.isSelectionMode && !this.isDetailView && ch === "c") {
1296
+ const newMode = this.renderer.cycleViewMode();
1297
+ console.clear();
1298
+ const logs = this.storage.getAllLogs();
1299
+ for (const entry of logs) this.renderer.renderLogLine(entry);
1300
+ process.stdout.write(`\r${chalk$1.cyan(`ℹ ${{
1301
+ full: "Full view enabled (all data shown without truncation)",
1302
+ truncated: "Truncated view enabled (timestamp, ID, level, message, truncated data)",
1303
+ condensed: "Condensed view enabled (timestamp, level and message only)"
1304
+ }[newMode]}`)}${" ".repeat(20)}`);
1305
+ setTimeout(() => {
1306
+ process.stdout.write(`\r${" ".repeat(100)}\r`);
1307
+ }, 3e3);
1308
+ return;
1309
+ }
1310
+ if (this.isSelectionMode) {
1311
+ if (key) switch (key.name) {
1312
+ case "up":
1313
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
1314
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1315
+ return;
1316
+ case "down":
1317
+ if (this.selectedIndex === this.logs.length - 1 && this.selectionBuffer.length > 0) {
1318
+ this.logs.push(...this.selectionBuffer);
1319
+ this.selectionBuffer = [];
1320
+ }
1321
+ this.selectedIndex = Math.min(this.logs.length - 1, this.selectedIndex + 1);
1322
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1323
+ return;
1324
+ case "return":
1325
+ if (this.logs.length > 0) {
1326
+ this.isSelectionMode = false;
1327
+ this.isDetailView = true;
1328
+ console.clear();
1329
+ const selectedEntry = this.logs[this.selectedIndex];
1330
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1331
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1332
+ this.renderer.renderDetailView(selectedEntry, prevEntry, nextEntry, 0, this.selectedIndex + 1, this.logs.length, this.filterText);
1333
+ this.startDetailViewPolling();
1334
+ }
1335
+ return;
1336
+ case "backspace":
1337
+ if (this.filterText.length > 0) {
1338
+ this.filterText = this.filterText.slice(0, -1);
1339
+ this.updateFilteredLogs();
1340
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1341
+ }
1342
+ return;
1343
+ case "tab": {
1344
+ this.isSelectionMode = false;
1345
+ this.isPaused = false;
1346
+ if (this.selectionViewPollInterval) {
1347
+ clearInterval(this.selectionViewPollInterval);
1348
+ this.selectionViewPollInterval = null;
1349
+ }
1350
+ this.filterText = "";
1351
+ console.clear();
1352
+ const logs = this.storage.getAllLogs();
1353
+ for (const entry of logs) this.renderer.renderLogLine(entry);
1354
+ if (this.selectionBuffer.length > 0) {
1355
+ for (const entry of this.selectionBuffer) this.renderer.renderLogLine(entry);
1356
+ this.selectionBuffer = [];
1357
+ }
1358
+ return;
1359
+ }
1360
+ }
1361
+ if (ch && (!key || !key.ctrl && !key.meta) && ch.length === 1) {
1362
+ this.filterText += ch;
1363
+ this.updateFilteredLogs();
1364
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1365
+ return;
1366
+ }
1367
+ }
1368
+ if (this.isDetailView) {
1369
+ if (key?.name) switch (key.name) {
1370
+ case "up": {
1371
+ this.renderer.scrollDetailView(-1);
1372
+ const entry = this.logs[this.selectedIndex];
1373
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1374
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1375
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length, this.filterText);
1376
+ break;
1377
+ }
1378
+ case "down": {
1379
+ this.renderer.scrollDetailView(1);
1380
+ const entry = this.logs[this.selectedIndex];
1381
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1382
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1383
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length, this.filterText);
1384
+ break;
1385
+ }
1386
+ case "left":
1387
+ if (this.selectedIndex > 0) {
1388
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
1389
+ const entry = this.logs[this.selectedIndex];
1390
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1391
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1392
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, this.selectedIndex + 1, this.logs.length, this.filterText);
1393
+ }
1394
+ break;
1395
+ case "right":
1396
+ if (this.selectedIndex < this.logs.length - 1) {
1397
+ this.selectedIndex = Math.min(this.logs.length - 1, this.selectedIndex + 1);
1398
+ const entry = this.logs[this.selectedIndex];
1399
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1400
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1401
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, this.selectedIndex + 1, this.logs.length, this.filterText);
1402
+ }
1403
+ break;
1404
+ case "tab":
1405
+ if (this.renderer.isInJsonView()) {
1406
+ this.renderer.toggleJsonView();
1407
+ const entry = this.logs[this.selectedIndex];
1408
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1409
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1410
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length, this.filterText);
1411
+ break;
1412
+ }
1413
+ this.isDetailView = false;
1414
+ if (this.detailViewPollInterval) {
1415
+ clearInterval(this.detailViewPollInterval);
1416
+ this.detailViewPollInterval = null;
1417
+ }
1418
+ this.isSelectionMode = true;
1419
+ this.updateFilteredLogs();
1420
+ this.renderer.renderSelectionView(this.logs, this.selectedIndex, this.filterText, this.selectionBuffer.length);
1421
+ break;
1422
+ }
1423
+ if (ch) switch (ch.toLowerCase()) {
1424
+ case "w": {
1425
+ this.renderer.scrollDetailView(process.stdout.rows - 8);
1426
+ const entry = this.logs[this.selectedIndex];
1427
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1428
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1429
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length);
1430
+ break;
1431
+ }
1432
+ case "q": {
1433
+ this.renderer.scrollDetailView(-process.stdout.rows + 8);
1434
+ const entry = this.logs[this.selectedIndex];
1435
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1436
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1437
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length);
1438
+ break;
1439
+ }
1440
+ case "a":
1441
+ if (this.selectedIndex !== 0) {
1442
+ this.selectedIndex = 0;
1443
+ const entry = this.logs[this.selectedIndex];
1444
+ const prevEntry = null;
1445
+ const nextEntry = this.logs.length > 1 ? this.logs[1] : null;
1446
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, 1, this.logs.length);
1447
+ }
1448
+ break;
1449
+ case "s": {
1450
+ const lastIndex = this.logs.length - 1;
1451
+ if (this.selectedIndex !== lastIndex) {
1452
+ this.selectedIndex = lastIndex;
1453
+ const entry = this.logs[this.selectedIndex];
1454
+ const prevEntry = lastIndex > 0 ? this.logs[lastIndex - 1] : null;
1455
+ this.renderer.renderDetailView(entry, prevEntry, null, 0, this.logs.length, this.logs.length);
1456
+ }
1457
+ break;
1458
+ }
1459
+ case "c": {
1460
+ this.renderer.toggleArrayCollapse();
1461
+ const entry = this.logs[this.selectedIndex];
1462
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1463
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1464
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, this.renderer.getDetailViewScrollPos(), this.selectedIndex + 1, this.logs.length);
1465
+ break;
1466
+ }
1467
+ case "j": {
1468
+ this.renderer.toggleJsonView();
1469
+ const entry = this.logs[this.selectedIndex];
1470
+ const prevEntry = this.selectedIndex > 0 ? this.logs[this.selectedIndex - 1] : null;
1471
+ const nextEntry = this.selectedIndex < this.logs.length - 1 ? this.logs[this.selectedIndex + 1] : null;
1472
+ this.renderer.renderDetailView(entry, prevEntry, nextEntry, 0, this.selectedIndex + 1, this.logs.length);
1473
+ break;
1474
+ }
1475
+ }
1476
+ }
1477
+ }
1659
1478
  };
1660
1479
 
1661
- // src/PrettyTerminalTransport.ts
1662
- var PrettyTerminalTransport = class _PrettyTerminalTransport extends LoggerlessTransport {
1663
- /** Singleton instance of the transport */
1664
- static instance = null;
1665
- /** Handles all rendering and formatting of logs */
1666
- renderer;
1667
- /** Manages log persistence in SQLite database */
1668
- storage;
1669
- /** Manages user interaction and view state */
1670
- uiManager;
1671
- /** Configuration options */
1672
- config;
1673
- /**
1674
- * Creates a new PrettyTerminalTransport instance.
1675
- * This is a singleton class - only one instance can exist at a time.
1676
- * Use getInstance() instead of constructor directly.
1677
- *
1678
- * @param config - Configuration options for the transport
1679
- * @throws Error if attempting to create multiple instances
1680
- */
1681
- constructor(config = {}) {
1682
- if (_PrettyTerminalTransport.instance) {
1683
- throw new Error("PrettyTerminalTransport is a singleton. Use getPrettyTerminal() instead.");
1684
- }
1685
- super(config);
1686
- this.config = config;
1687
- if (config.enabled === false) {
1688
- return;
1689
- }
1690
- const maxInlineDepth = config.maxInlineDepth || 4;
1691
- const maxInlineLength = config.maxInlineLength || 120;
1692
- const theme = config.theme || moonlight;
1693
- const logFile = config.logFile;
1694
- const simpleViewConfig = {
1695
- colors: {
1696
- trace: chalk7.gray,
1697
- // Lowest level, used for verbose output
1698
- debug: chalk7.blue,
1699
- // Debug information
1700
- info: chalk7.green,
1701
- // Normal operation
1702
- warn: chalk7.yellow,
1703
- // Warning conditions
1704
- error: chalk7.red,
1705
- // Error conditions
1706
- fatal: chalk7.bgRed.white,
1707
- // Critical errors
1708
- ...theme.simpleView.colors
1709
- },
1710
- logIdColor: theme.simpleView.logIdColor || chalk7.dim,
1711
- dataValueColor: theme.simpleView.dataValueColor || chalk7.white,
1712
- dataKeyColor: theme.simpleView.dataKeyColor || chalk7.dim,
1713
- selectorColor: theme.simpleView.selectorColor || chalk7.cyan
1714
- };
1715
- const detailedViewConfig = {
1716
- colors: {
1717
- trace: chalk7.gray,
1718
- debug: chalk7.blue,
1719
- info: chalk7.green,
1720
- warn: chalk7.yellow,
1721
- error: chalk7.red,
1722
- fatal: chalk7.bgRed.white,
1723
- ...theme.detailedView?.colors
1724
- },
1725
- logIdColor: theme.detailedView?.logIdColor || chalk7.dim,
1726
- dataValueColor: theme.detailedView?.dataValueColor || chalk7.white,
1727
- dataKeyColor: theme.detailedView?.dataKeyColor || chalk7.dim,
1728
- selectorColor: theme.detailedView?.selectorColor || chalk7.cyan,
1729
- headerColor: theme.detailedView?.headerColor || chalk7.cyan,
1730
- labelColor: theme.detailedView?.labelColor || chalk7.cyan.bold,
1731
- separatorColor: theme.detailedView?.separatorColor || chalk7.dim,
1732
- // JSON formatting colors for detailed data view
1733
- jsonColors: {
1734
- keysColor: chalk7.yellow,
1735
- // Object property names
1736
- dashColor: chalk7.white,
1737
- // Array bullets
1738
- numberColor: chalk7.yellow,
1739
- // Default number color
1740
- stringColor: chalk7.white,
1741
- // Single-line strings
1742
- multilineStringColor: chalk7.white,
1743
- // Multi-line strings
1744
- positiveNumberColor: chalk7.green,
1745
- // Numbers > 0
1746
- negativeNumberColor: chalk7.red,
1747
- // Numbers < 0
1748
- booleanColor: chalk7.cyan,
1749
- // true/false values
1750
- nullUndefinedColor: chalk7.grey,
1751
- // null/undefined
1752
- dateColor: chalk7.magenta,
1753
- // Date objects
1754
- ...theme.detailedView?.jsonColors
1755
- }
1756
- };
1757
- this.storage = new LogStorage(logFile);
1758
- this.renderer = new LogRenderer(simpleViewConfig, detailedViewConfig, maxInlineDepth, maxInlineLength);
1759
- this.uiManager = new UIManager(this.renderer, this.storage, config.disableInteractiveMode);
1760
- _PrettyTerminalTransport.instance = this;
1761
- }
1762
- /**
1763
- * Gets or creates the singleton instance of PrettyTerminalTransport.
1764
- * This is the recommended way to obtain a transport instance.
1765
- *
1766
- * @param config - Configuration options for the transport
1767
- * @returns The singleton instance of PrettyTerminalTransport
1768
- */
1769
- static getInstance(config = {}) {
1770
- if (!_PrettyTerminalTransport.instance) {
1771
- _PrettyTerminalTransport.instance = new _PrettyTerminalTransport(config);
1772
- }
1773
- return _PrettyTerminalTransport.instance;
1774
- }
1775
- /**
1776
- * Generates a random ID for each log entry.
1777
- * Uses base36 encoding for compact, readable IDs.
1778
- *
1779
- * @returns A 6-character string ID
1780
- * @example
1781
- * "a1b2c3" // Example generated ID
1782
- */
1783
- generateId() {
1784
- return Math.random().toString(36).substring(2, 8);
1785
- }
1786
- /**
1787
- * Main transport method that receives logs from LogLayer.
1788
- * This method is called for each log event and handles:
1789
- * - Generating a unique ID for the log
1790
- * - Converting the log data to a storable format
1791
- * - Storing the log in the database
1792
- * - Rendering the log if not in selection mode
1793
- *
1794
- * @param logLevel - The severity level of the log
1795
- * @param messages - Array of message strings to be joined
1796
- * @param data - Additional structured data to be logged
1797
- * @param hasData - Whether the log includes additional data
1798
- * @returns The original messages array
1799
- */
1800
- shipToLogger({ logLevel, messages, data, hasData }) {
1801
- if (this.config.enabled === false) {
1802
- return messages;
1803
- }
1804
- const entry = {
1805
- id: this.generateId(),
1806
- timestamp: Date.now(),
1807
- level: logLevel,
1808
- message: messages.join(" "),
1809
- data: hasData ? JSON.stringify(data) : null
1810
- };
1811
- this.uiManager.handleNewLog(entry);
1812
- return messages;
1813
- }
1480
+ //#endregion
1481
+ //#region src/PrettyTerminalTransport.ts
1482
+ /**
1483
+ * Main transport class that handles pretty terminal output and interactive features.
1484
+ * This class coordinates between different components:
1485
+ * - LogStorage: Handles persistence of logs in SQLite
1486
+ * - LogRenderer: Manages all rendering and formatting
1487
+ * - UIManager: Handles user interaction and view state
1488
+ *
1489
+ * The transport supports two main view modes:
1490
+ * 1. Simple View: Real-time log output with basic formatting
1491
+ * 2. Interactive View: Full-screen mode with navigation and filtering
1492
+ *
1493
+ * Usage:
1494
+ * ```typescript
1495
+ * const transport = PrettyTerminalTransport.getInstance({
1496
+ * maxInlineDepth: 4,
1497
+ * maxInlineLength: 120,
1498
+ * theme: customTheme
1499
+ * });
1500
+ * ```
1501
+ */
1502
+ var PrettyTerminalTransport = class PrettyTerminalTransport extends LoggerlessTransport {
1503
+ /** Singleton instance of the transport */
1504
+ static instance = null;
1505
+ /** Handles all rendering and formatting of logs */
1506
+ renderer;
1507
+ /** Manages log persistence in SQLite database */
1508
+ storage;
1509
+ /** Manages user interaction and view state */
1510
+ uiManager;
1511
+ /** Configuration options */
1512
+ config;
1513
+ /**
1514
+ * Creates a new PrettyTerminalTransport instance.
1515
+ * This is a singleton class - only one instance can exist at a time.
1516
+ * Use getInstance() instead of constructor directly.
1517
+ *
1518
+ * @param config - Configuration options for the transport
1519
+ * @throws Error if attempting to create multiple instances
1520
+ */
1521
+ constructor(config = {}) {
1522
+ if (PrettyTerminalTransport.instance) throw new Error("PrettyTerminalTransport is a singleton. Use getPrettyTerminal() instead.");
1523
+ super(config);
1524
+ this.config = config;
1525
+ if (config.enabled === false) return;
1526
+ const maxInlineDepth = config.maxInlineDepth || 4;
1527
+ const maxInlineLength = config.maxInlineLength || 120;
1528
+ const theme = config.theme || moonlight;
1529
+ const logFile = config.logFile;
1530
+ const simpleViewConfig = {
1531
+ colors: {
1532
+ trace: chalk$1.gray,
1533
+ debug: chalk$1.blue,
1534
+ info: chalk$1.green,
1535
+ warn: chalk$1.yellow,
1536
+ error: chalk$1.red,
1537
+ fatal: chalk$1.bgRed.white,
1538
+ ...theme.simpleView.colors
1539
+ },
1540
+ logIdColor: theme.simpleView.logIdColor || chalk$1.dim,
1541
+ dataValueColor: theme.simpleView.dataValueColor || chalk$1.white,
1542
+ dataKeyColor: theme.simpleView.dataKeyColor || chalk$1.dim,
1543
+ selectorColor: theme.simpleView.selectorColor || chalk$1.cyan
1544
+ };
1545
+ const detailedViewConfig = {
1546
+ colors: {
1547
+ trace: chalk$1.gray,
1548
+ debug: chalk$1.blue,
1549
+ info: chalk$1.green,
1550
+ warn: chalk$1.yellow,
1551
+ error: chalk$1.red,
1552
+ fatal: chalk$1.bgRed.white,
1553
+ ...theme.detailedView?.colors
1554
+ },
1555
+ logIdColor: theme.detailedView?.logIdColor || chalk$1.dim,
1556
+ dataValueColor: theme.detailedView?.dataValueColor || chalk$1.white,
1557
+ dataKeyColor: theme.detailedView?.dataKeyColor || chalk$1.dim,
1558
+ selectorColor: theme.detailedView?.selectorColor || chalk$1.cyan,
1559
+ headerColor: theme.detailedView?.headerColor || chalk$1.cyan,
1560
+ labelColor: theme.detailedView?.labelColor || chalk$1.cyan.bold,
1561
+ separatorColor: theme.detailedView?.separatorColor || chalk$1.dim,
1562
+ jsonColors: {
1563
+ keysColor: chalk$1.yellow,
1564
+ dashColor: chalk$1.white,
1565
+ numberColor: chalk$1.yellow,
1566
+ stringColor: chalk$1.white,
1567
+ multilineStringColor: chalk$1.white,
1568
+ positiveNumberColor: chalk$1.green,
1569
+ negativeNumberColor: chalk$1.red,
1570
+ booleanColor: chalk$1.cyan,
1571
+ nullUndefinedColor: chalk$1.grey,
1572
+ dateColor: chalk$1.magenta,
1573
+ ...theme.detailedView?.jsonColors
1574
+ }
1575
+ };
1576
+ this.storage = new LogStorage(logFile);
1577
+ this.renderer = new LogRenderer(simpleViewConfig, detailedViewConfig, maxInlineDepth, maxInlineLength);
1578
+ this.uiManager = new UIManager(this.renderer, this.storage, config.disableInteractiveMode);
1579
+ PrettyTerminalTransport.instance = this;
1580
+ }
1581
+ /**
1582
+ * Gets or creates the singleton instance of PrettyTerminalTransport.
1583
+ * This is the recommended way to obtain a transport instance.
1584
+ *
1585
+ * @param config - Configuration options for the transport
1586
+ * @returns The singleton instance of PrettyTerminalTransport
1587
+ */
1588
+ static getInstance(config = {}) {
1589
+ if (!PrettyTerminalTransport.instance) PrettyTerminalTransport.instance = new PrettyTerminalTransport(config);
1590
+ return PrettyTerminalTransport.instance;
1591
+ }
1592
+ /**
1593
+ * Generates a random ID for each log entry.
1594
+ * Uses base36 encoding for compact, readable IDs.
1595
+ *
1596
+ * @returns A 6-character string ID
1597
+ * @example
1598
+ * "a1b2c3" // Example generated ID
1599
+ */
1600
+ generateId() {
1601
+ return Math.random().toString(36).substring(2, 8);
1602
+ }
1603
+ /**
1604
+ * Main transport method that receives logs from LogLayer.
1605
+ * This method is called for each log event and handles:
1606
+ * - Generating a unique ID for the log
1607
+ * - Converting the log data to a storable format
1608
+ * - Storing the log in the database
1609
+ * - Rendering the log if not in selection mode
1610
+ *
1611
+ * @param logLevel - The severity level of the log
1612
+ * @param messages - Array of message strings to be joined
1613
+ * @param data - Additional structured data to be logged
1614
+ * @param hasData - Whether the log includes additional data
1615
+ * @returns The original messages array
1616
+ */
1617
+ shipToLogger({ logLevel, messages, data, hasData }) {
1618
+ if (this.config.enabled === false) return messages;
1619
+ const entry = {
1620
+ id: this.generateId(),
1621
+ timestamp: Date.now(),
1622
+ level: logLevel,
1623
+ message: messages.join(" "),
1624
+ data: hasData ? JSON.stringify(data) : null
1625
+ };
1626
+ this.uiManager.handleNewLog(entry);
1627
+ return messages;
1628
+ }
1814
1629
  };
1815
1630
 
1816
- // src/index.ts
1817
- import * as chalk8 from "chalk";
1631
+ //#endregion
1632
+ //#region src/index.ts
1818
1633
  function getPrettyTerminal(config = {}) {
1819
- return PrettyTerminalTransport.getInstance(config);
1634
+ return PrettyTerminalTransport.getInstance(config);
1820
1635
  }
1821
- export {
1822
- PrettyTerminalTransport,
1823
- chalk8 as chalk,
1824
- getPrettyTerminal,
1825
- moonlight,
1826
- nature,
1827
- neon,
1828
- pastel,
1829
- sunlight
1830
- };
1636
+
1637
+ //#endregion
1638
+ export { PrettyTerminalTransport, chalk, getPrettyTerminal, moonlight, nature, neon, pastel, sunlight };
1831
1639
  //# sourceMappingURL=index.js.map