@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.1-dev.pr156.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.
@@ -1,465 +1,257 @@
1
1
  import Table from 'cli-table3';
2
2
  import { formatOutput } from '#utils/output_formatter.js';
3
- /**
4
- * Format duration in human-readable format
5
- */
6
- export function formatDuration(ms) {
7
- if (ms < 1000) {
8
- return `${ms}ms`;
9
- }
10
- if (ms < 60000) {
11
- return `${(ms / 1000).toFixed(2)}s`;
12
- }
13
- const minutes = Math.floor(ms / 60000);
14
- const seconds = ((ms % 60000) / 1000).toFixed(0);
15
- return `${minutes}m ${seconds}s`;
16
- }
17
- /**
18
- * Format error for display
19
- */
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- function formatError(error) {
22
- if (typeof error === 'string') {
23
- return error;
24
- }
25
- if (error.message) {
26
- return error.message;
27
- }
28
- return JSON.stringify(error);
29
- }
30
- /**
31
- * Truncate long values for display
32
- */
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- function truncateValue(value, maxLength = 50) {
35
- const str = typeof value === 'string' ? value : JSON.stringify(value);
36
- if (str.length <= maxLength) {
37
- return str;
38
- }
39
- return str.substring(0, maxLength) + '...';
40
- }
41
- /**
42
- * Get a readable name for an event
43
- */
44
- function getEventName(event) {
45
- if (event.kind === 'workflow') {
46
- return `Workflow: ${event.workflowName}`;
47
- }
48
- if (event.kind === 'activity') {
49
- return `Activity: ${event.details?.activityName || 'unknown'}`;
50
- }
51
- if (event.kind === 'step') {
52
- return `Step: ${event.details?.stepName || event.details?.name || 'unknown'}`;
53
- }
54
- return event.kind || 'Unknown Event';
55
- }
56
- /**
57
- * Format the phase
58
- */
59
- function formatPhase(phase) {
60
- switch (phase) {
61
- case 'start':
62
- return '[START]';
63
- case 'end':
64
- return '[END]';
65
- case 'error':
66
- return '[ERROR]';
67
- default:
68
- return phase;
69
- }
70
- }
71
- /**
72
- * Format details for table display
73
- */
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- function formatDetails(details) {
76
- if (!details) {
77
- return '-';
78
- }
79
- if (typeof details === 'string') {
80
- return details;
81
- }
82
- // Extract key information
83
- const info = [];
84
- if (details.input) {
85
- info.push(`Input: ${truncateValue(details.input)}`);
86
- }
87
- if (details.output) {
88
- info.push(`Output: ${truncateValue(details.output)}`);
89
- }
90
- if (details.activityName) {
91
- info.push(`Activity: ${details.activityName}`);
92
- }
93
- if (details.stepName || details.name) {
94
- info.push(`Step: ${details.stepName || details.name}`);
95
- }
96
- return info.length > 0 ? info.join(', ') : JSON.stringify(details).substring(0, 50) + '...';
97
- }
98
- /**
99
- * Format details for tree display
100
- */
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- function formatTreeDetails(details, depth) {
103
- const lines = [];
104
- const indent = ' '.repeat(depth);
105
- // Only show key details
106
- if (details.input && details.input !== null) {
107
- lines.push(`${indent}Input: ${truncateValue(details.input)}`);
108
- }
109
- if (details.output && details.output !== null) {
110
- lines.push(`${indent}Output: ${truncateValue(details.output)}`);
111
- }
112
- return lines.join('\n');
113
- }
114
- /**
115
- * Format the header with workflow information
116
- */
117
- function formatHeader(root) {
118
- const lines = [];
119
- lines.push('═'.repeat(60));
120
- lines.push(`Workflow: ${root.workflowName}`);
121
- lines.push(`Workflow ID: ${root.workflowId}`);
122
- lines.push(`Start Time: ${new Date(root.timestamp).toISOString()}`);
123
- if (root.duration) {
124
- lines.push(`Duration: ${formatDuration(root.duration)}`);
125
- }
126
- if (root.phase === 'error' && root.error) {
127
- lines.push('Status: Failed');
128
- lines.push(`Error: ${formatError(root.error)}`);
129
- }
130
- else if (root.phase === 'end') {
131
- lines.push('Status: Completed');
132
- }
133
- else {
134
- lines.push('Status: In Progress');
135
- }
136
- lines.push('═'.repeat(60));
137
- return lines.join('\n');
138
- }
139
- /**
140
- * Format events as a timeline table
141
- */
142
- function formatEventsTable(events) {
143
- const table = new Table({
144
- head: ['Time', 'Event', 'Phase', 'Duration', 'Details'],
145
- style: {
146
- head: ['cyan']
147
- },
148
- colWidths: [20, 25, 10, 12, null],
149
- wordWrap: true
150
- });
151
- for (const event of events) {
152
- const time = new Date(event.timestamp).toISOString().substring(11, 23);
153
- const eventName = getEventName(event);
154
- const phase = formatPhase(event.phase);
155
- const duration = event.duration ? formatDuration(event.duration) : '-';
156
- const details = formatDetails(event.details);
157
- table.push([time, eventName, phase, duration, details]);
158
- }
159
- return table.toString();
160
- }
161
- /**
162
- * Format trace as a tree structure
163
- */
164
- function formatTree(node, depth) {
165
- const lines = [];
166
- const indent = ' '.repeat(depth);
167
- const marker = depth === 0 ? '' : '├─';
168
- // Format current node
169
- const nodeName = getEventName(node);
170
- const phase = formatPhase(node.phase);
171
- const duration = node.duration ? ` (${formatDuration(node.duration)})` : '';
172
- lines.push(`${indent}${marker} ${nodeName} ${phase}${duration}`);
173
- // Add error details if present
174
- if (node.error) {
175
- lines.push(`${indent} └─ ERROR: ${formatError(node.error)}`);
176
- }
177
- // Add important details
178
- if (node.details && typeof node.details === 'object') {
179
- const detailLines = formatTreeDetails(node.details, depth + 1);
180
- if (detailLines) {
181
- lines.push(detailLines);
3
+ export class TraceFormatter {
4
+ /**
5
+ * Format trace data based on the requested format
6
+ */
7
+ format(traceData, format = 'text') {
8
+ // Parse if string
9
+ const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
10
+ if (format === 'json') {
11
+ return formatOutput(trace, 'json');
182
12
  }
183
- }
184
- // Process children
185
- if (node.children && node.children.length > 0) {
186
- for (const child of node.children) {
187
- lines.push(formatTree(child, depth + 1));
13
+ // Format as human-readable text
14
+ return this.formatAsText(trace);
15
+ }
16
+ /**
17
+ * Format trace as human-readable text with tree structure
18
+ */
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ formatAsText(trace) {
21
+ const output = [];
22
+ // Add header with workflow info
23
+ if (trace.root) {
24
+ output.push(this.formatHeader(trace.root));
25
+ output.push('');
188
26
  }
189
- }
190
- return lines.join('\n');
191
- }
192
- /**
193
- * Format trace as human-readable text with tree structure
194
- */
195
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
- function formatAsText(trace) {
197
- const output = [];
198
- // Add header with workflow info
199
- if (trace.root) {
200
- output.push(formatHeader(trace.root));
201
- output.push('');
202
- }
203
- // Create execution timeline table
204
- if (trace.events && trace.events.length > 0) {
205
- output.push('Execution Timeline:');
206
- output.push(formatEventsTable(trace.events));
207
- output.push('');
208
- }
209
- // Show tree structure
210
- if (trace.root) {
211
- output.push('Execution Tree:');
212
- output.push(formatTree(trace.root, 0));
213
- }
214
- return output.join('\n');
215
- }
216
- /**
217
- * Format trace data based on the requested format
218
- */
219
- export function format(traceData, outputFormat = 'text') {
220
- // Parse if string
221
- const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
222
- if (outputFormat === 'json') {
223
- return formatOutput(trace, 'json');
224
- }
225
- // Format as human-readable text
226
- return formatAsText(trace);
227
- }
228
- /**
229
- * Get summary statistics from trace
230
- */
231
- export function getSummary(traceData) {
232
- const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
233
- const stats = {
234
- totalDuration: trace.root?.duration || 0,
235
- totalEvents: trace.events?.length || 0,
236
- totalSteps: 0,
237
- totalActivities: 0,
238
- hasErrors: false
239
- };
240
- if (trace.events) {
241
- for (const event of trace.events) {
242
- if (event.kind === 'step') {
243
- stats.totalSteps++;
244
- }
245
- if (event.kind === 'activity') {
246
- stats.totalActivities++;
27
+ // Create execution timeline table
28
+ if (trace.events && trace.events.length > 0) {
29
+ output.push('Execution Timeline:');
30
+ output.push(this.formatEventsTable(trace.events));
31
+ output.push('');
32
+ }
33
+ // Show tree structure
34
+ if (trace.root) {
35
+ output.push('Execution Tree:');
36
+ output.push(this.formatTree(trace.root, 0));
37
+ }
38
+ return output.join('\n');
39
+ }
40
+ /**
41
+ * Format the header with workflow information
42
+ */
43
+ formatHeader(root) {
44
+ const lines = [];
45
+ lines.push(''.repeat(60));
46
+ lines.push(`Workflow: ${root.workflowName}`);
47
+ lines.push(`Workflow ID: ${root.workflowId}`);
48
+ lines.push(`Start Time: ${new Date(root.timestamp).toISOString()}`);
49
+ if (root.duration) {
50
+ lines.push(`Duration: ${this.formatDuration(root.duration)}`);
51
+ }
52
+ if (root.phase === 'error' && root.error) {
53
+ lines.push('Status: ❌ Failed');
54
+ lines.push(`Error: ${this.formatError(root.error)}`);
55
+ }
56
+ else if (root.phase === 'end') {
57
+ lines.push('Status: Completed');
58
+ }
59
+ else {
60
+ lines.push('Status: 🔄 In Progress');
61
+ }
62
+ lines.push('═'.repeat(60));
63
+ return lines.join('\n');
64
+ }
65
+ /**
66
+ * Format events as a timeline table
67
+ */
68
+ formatEventsTable(events) {
69
+ const table = new Table({
70
+ head: ['Time', 'Event', 'Phase', 'Duration', 'Details'],
71
+ style: {
72
+ head: ['cyan']
73
+ },
74
+ colWidths: [20, 25, 10, 12, null],
75
+ wordWrap: true
76
+ });
77
+ for (const event of events) {
78
+ const time = new Date(event.timestamp).toISOString().substring(11, 23);
79
+ const eventName = this.getEventName(event);
80
+ const phase = this.formatPhase(event.phase);
81
+ const duration = event.duration ? this.formatDuration(event.duration) : '-';
82
+ const details = this.formatDetails(event.details);
83
+ table.push([time, eventName, phase, duration, details]);
84
+ }
85
+ return table.toString();
86
+ }
87
+ /**
88
+ * Format trace as a tree structure
89
+ */
90
+ formatTree(node, depth) {
91
+ const lines = [];
92
+ const indent = ' '.repeat(depth);
93
+ const marker = depth === 0 ? '🌳' : '├─';
94
+ // Format current node
95
+ const nodeName = this.getEventName(node);
96
+ const phase = this.formatPhase(node.phase);
97
+ const duration = node.duration ? ` (${this.formatDuration(node.duration)})` : '';
98
+ lines.push(`${indent}${marker} ${nodeName} ${phase}${duration}`);
99
+ // Add error details if present
100
+ if (node.error) {
101
+ lines.push(`${indent} └─ ❌ ${this.formatError(node.error)}`);
102
+ }
103
+ // Add important details
104
+ if (node.details && typeof node.details === 'object') {
105
+ const detailLines = this.formatTreeDetails(node.details, depth + 1);
106
+ if (detailLines) {
107
+ lines.push(detailLines);
247
108
  }
248
- if (event.phase === 'error') {
249
- stats.hasErrors = true;
109
+ }
110
+ // Process children
111
+ if (node.children && node.children.length > 0) {
112
+ for (const child of node.children) {
113
+ lines.push(this.formatTree(child, depth + 1));
250
114
  }
251
115
  }
252
- }
253
- return stats;
254
- }
255
- /**
256
- * Get tree connector character
257
- */
258
- function getConnector(isLast) {
259
- return isLast ? '└─ ' : '├─ ';
260
- }
261
- /**
262
- * Format value for debug detail display
263
- */
264
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
265
- function formatDebugDetailValue(value) {
266
- if (value === null) {
267
- return 'null';
268
- }
269
- if (value === undefined) {
270
- return 'undefined';
271
- }
272
- if (typeof value === 'string') {
273
- // For short strings, show inline
274
- if (value.length <= 60) {
275
- return value;
116
+ return lines.join('\n');
117
+ }
118
+ /**
119
+ * Get a readable name for an event
120
+ */
121
+ getEventName(event) {
122
+ if (event.kind === 'workflow') {
123
+ return `Workflow: ${event.workflowName}`;
276
124
  }
277
- // For longer strings, truncate and suggest JSON format
278
- return `${value.substring(0, 60)}... (truncated)`;
279
- }
280
- if (typeof value === 'number' || typeof value === 'boolean') {
281
- return String(value);
282
- }
283
- if (Array.isArray(value)) {
284
- if (value.length === 0) {
285
- return '[]';
125
+ if (event.kind === 'activity') {
126
+ return `Activity: ${event.details?.activityName || 'unknown'}`;
286
127
  }
287
- if (value.length <= 3 && value.every(v => typeof v === 'string' || typeof v === 'number')) {
288
- return `[${value.join(', ')}]`;
128
+ if (event.kind === 'step') {
129
+ return `Step: ${event.details?.stepName || event.details?.name || 'unknown'}`;
130
+ }
131
+ return event.kind || 'Unknown Event';
132
+ }
133
+ /**
134
+ * Format the phase with icons
135
+ */
136
+ formatPhase(phase) {
137
+ switch (phase) {
138
+ case 'start':
139
+ return '▶️';
140
+ case 'end':
141
+ return '✅';
142
+ case 'error':
143
+ return '❌';
144
+ default:
145
+ return phase;
289
146
  }
290
- return `[Array with ${value.length} items]`;
291
147
  }
292
- if (typeof value === 'object') {
293
- const keys = Object.keys(value);
294
- if (keys.length === 0) {
295
- return '{}';
148
+ /**
149
+ * Format duration in human-readable format
150
+ */
151
+ formatDuration(ms) {
152
+ if (ms < 1000) {
153
+ return `${ms}ms`;
296
154
  }
297
- // For simple objects with few keys, show inline
298
- if (keys.length <= 2) {
299
- const pairs = keys.map(k => {
300
- const v = value[k];
301
- if (typeof v === 'string' && v.length > 30) {
302
- return `${k}: "${v.substring(0, 30)}..."`;
303
- }
304
- if (typeof v === 'object') {
305
- return `${k}: {...}`;
306
- }
307
- return `${k}: ${JSON.stringify(v)}`;
308
- });
309
- return `{ ${pairs.join(', ')} }`;
155
+ if (ms < 60000) {
156
+ return `${(ms / 1000).toFixed(2)}s`;
310
157
  }
311
- // For complex objects, just show key count
312
- return `{ ${keys.length} properties }`;
313
- }
314
- return String(value);
315
- }
316
- /**
317
- * Get debug node display information
318
- */
319
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
- function getDebugNodeInfo(node) {
321
- if (typeof node === 'string') {
322
- return node;
323
- }
324
- if (typeof node !== 'object') {
325
- return String(node);
326
- }
327
- const parts = [];
328
- // Look for common identifying properties
329
- if (node.kind) {
330
- parts.push(`[${node.kind}]`);
331
- }
332
- else if (node.type) {
333
- parts.push(`[${node.type}]`);
334
- }
335
- if (node.name) {
336
- parts.push(node.name);
337
- }
338
- else if (node.workflowName) {
339
- parts.push(node.workflowName);
340
- }
341
- else if (node.stepName) {
342
- parts.push(node.stepName);
343
- }
344
- else if (node.activityName) {
345
- parts.push(node.activityName);
346
- }
347
- // Add phase/status indicators
348
- if (node.phase === 'error' || node.status === 'failed') {
349
- parts.push('[FAILED]');
350
- }
351
- else if (node.phase === 'end' || node.status === 'completed') {
352
- parts.push('[COMPLETED]');
353
- }
354
- else if (node.status === 'running') {
355
- parts.push('[RUNNING]');
356
- }
357
- // If we have no meaningful parts, show a summary of the object
358
- if (parts.length === 0) {
359
- const keys = Object.keys(node).filter(k => k !== 'children' && k !== 'parent');
360
- if (keys.length > 0) {
361
- return `Node {${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', ...' : ''}}`;
158
+ const minutes = Math.floor(ms / 60000);
159
+ const seconds = ((ms % 60000) / 1000).toFixed(0);
160
+ return `${minutes}m ${seconds}s`;
161
+ }
162
+ /**
163
+ * Format error for display
164
+ */
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ formatError(error) {
167
+ if (typeof error === 'string') {
168
+ return error;
362
169
  }
363
- return 'Node';
364
- }
365
- return parts.join(' ');
366
- }
367
- /**
368
- * Add debug node details to lines array
369
- */
370
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
371
- function addDebugNodeDetails(node, prefix, lines) {
372
- if (typeof node !== 'object' || node === null) {
373
- return;
374
- }
375
- const details = [];
376
- // Add timing information
377
- if (node.startedAt || node.timestamp) {
378
- const startTime = node.startedAt || node.timestamp;
379
- const startDate = new Date(startTime);
380
- if (!isNaN(startDate.getTime())) {
381
- details.push(`${prefix}Started: ${startDate.toISOString()}`);
170
+ if (error.message) {
171
+ return error.message;
382
172
  }
383
- }
384
- if (node.endedAt) {
385
- const endDate = new Date(node.endedAt);
386
- if (!isNaN(endDate.getTime())) {
387
- details.push(`${prefix}Ended: ${endDate.toISOString()}`);
173
+ return JSON.stringify(error);
174
+ }
175
+ /**
176
+ * Format details for table display
177
+ */
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ formatDetails(details) {
180
+ if (!details) {
181
+ return '-';
388
182
  }
183
+ if (typeof details === 'string') {
184
+ return details;
185
+ }
186
+ // Extract key information
187
+ const info = [];
188
+ if (details.input) {
189
+ info.push(`Input: ${this.truncateValue(details.input)}`);
190
+ }
191
+ if (details.output) {
192
+ info.push(`Output: ${this.truncateValue(details.output)}`);
193
+ }
194
+ if (details.activityName) {
195
+ info.push(`Activity: ${details.activityName}`);
196
+ }
197
+ if (details.stepName || details.name) {
198
+ info.push(`Step: ${details.stepName || details.name}`);
199
+ }
200
+ return info.length > 0 ? info.join(', ') : JSON.stringify(details).substring(0, 50) + '...';
201
+ }
202
+ /**
203
+ * Format details for tree display
204
+ */
205
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
+ formatTreeDetails(details, depth) {
207
+ const lines = [];
208
+ const indent = ' '.repeat(depth);
209
+ // Only show key details
210
+ if (details.input && details.input !== null) {
211
+ lines.push(`${indent}📥 Input: ${this.truncateValue(details.input)}`);
212
+ }
213
+ if (details.output && details.output !== null) {
214
+ lines.push(`${indent}📤 Output: ${this.truncateValue(details.output)}`);
215
+ }
216
+ return lines.join('\n');
217
+ }
218
+ /**
219
+ * Truncate long values for display
220
+ */
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
+ truncateValue(value, maxLength = 50) {
223
+ const str = typeof value === 'string' ? value : JSON.stringify(value);
224
+ if (str.length <= maxLength) {
225
+ return str;
226
+ }
227
+ return str.substring(0, maxLength) + '...';
228
+ }
229
+ /**
230
+ * Get summary statistics from trace
231
+ */
232
+ getSummary(traceData) {
233
+ const trace = typeof traceData === 'string' ? JSON.parse(traceData) : traceData;
234
+ const stats = {
235
+ totalDuration: trace.root?.duration || 0,
236
+ totalEvents: trace.events?.length || 0,
237
+ totalSteps: 0,
238
+ totalActivities: 0,
239
+ hasErrors: false
240
+ };
241
+ if (trace.events) {
242
+ for (const event of trace.events) {
243
+ if (event.kind === 'step') {
244
+ stats.totalSteps++;
245
+ }
246
+ if (event.kind === 'activity') {
247
+ stats.totalActivities++;
248
+ }
249
+ if (event.phase === 'error') {
250
+ stats.hasErrors = true;
251
+ }
252
+ }
253
+ }
254
+ return stats;
389
255
  }
390
- // Calculate and show duration
391
- if (node.startedAt && node.endedAt) {
392
- const duration = node.endedAt - node.startedAt;
393
- details.push(`${prefix}Duration: ${formatDuration(duration)}`);
394
- }
395
- else if (node.duration) {
396
- details.push(`${prefix}Duration: ${formatDuration(node.duration)}`);
397
- }
398
- // Show input
399
- if (node.input !== undefined && node.input !== null) {
400
- const inputStr = formatDebugDetailValue(node.input);
401
- details.push(`${prefix}Input: ${inputStr}`);
402
- }
403
- // Show output
404
- if (node.output !== undefined && node.output !== null) {
405
- const outputStr = formatDebugDetailValue(node.output);
406
- details.push(`${prefix}Output: ${outputStr}`);
407
- }
408
- // Show error if present
409
- if (node.error) {
410
- const errorMsg = typeof node.error === 'string' ? node.error : (node.error.message || JSON.stringify(node.error));
411
- details.push(`${prefix}Error: ${errorMsg}`);
412
- }
413
- // Add all details to lines
414
- for (const detail of details) {
415
- lines.push(detail);
416
- }
417
- // Add spacing if we had details
418
- if (details.length > 0) {
419
- lines.push('');
420
- }
421
- }
422
- /**
423
- * Build debug tree lines recursively
424
- */
425
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
426
- function buildDebugTreeLines(node, depth, isLast, prefix, lines) {
427
- if (node === null || node === undefined) {
428
- return;
429
- }
430
- // Create the tree structure characters
431
- const isRoot = depth === 0;
432
- const connector = isRoot ? '' : getConnector(isLast);
433
- const indent = isRoot ? '' : prefix + connector;
434
- // Build the node display string
435
- const nodeInfo = getDebugNodeInfo(node);
436
- lines.push(indent + nodeInfo);
437
- // Display additional details with proper indentation
438
- const detailPrefix = isRoot ? ' ' : prefix + (isLast ? ' ' : '│ ');
439
- addDebugNodeDetails(node, detailPrefix, lines);
440
- // Update prefix for children
441
- const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
442
- // Process children if they exist
443
- if (node.children && Array.isArray(node.children)) {
444
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
445
- node.children.forEach((child, i) => {
446
- const isLastChild = i === node.children.length - 1;
447
- buildDebugTreeLines(child, depth + 1, isLastChild, childPrefix, lines);
448
- });
449
- }
450
- }
451
- /**
452
- * Display trace tree with debug command formatting style
453
- */
454
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
455
- export function displayDebugTree(node) {
456
- const lines = [];
457
- buildDebugTreeLines(node, 0, false, '', lines);
458
- return lines.join('\n');
459
256
  }
460
- // For backward compatibility, export an object with the main functions
461
- export const traceFormatter = {
462
- format,
463
- getSummary,
464
- displayDebugTree
465
- };
257
+ export const traceFormatter = new TraceFormatter();