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