trace-engine-cli 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +360 -2
  3. package/dist/bin/trace-cli.d.ts +2 -0
  4. package/dist/bin/trace-cli.js +19 -0
  5. package/dist/src/cli.d.ts +1 -0
  6. package/dist/src/cli.js +477 -0
  7. package/dist/src/engine/adapter.d.ts +13 -0
  8. package/dist/src/engine/adapter.js +51 -0
  9. package/dist/src/engine/extractors/diff.d.ts +12 -0
  10. package/dist/src/engine/extractors/diff.js +100 -0
  11. package/dist/src/engine/extractors/events.d.ts +27 -0
  12. package/dist/src/engine/extractors/events.js +84 -0
  13. package/dist/src/engine/extractors/find.d.ts +12 -0
  14. package/dist/src/engine/extractors/find.js +100 -0
  15. package/dist/src/engine/extractors/insights.d.ts +15 -0
  16. package/dist/src/engine/extractors/insights.js +153 -0
  17. package/dist/src/engine/extractors/interactions.d.ts +7 -0
  18. package/dist/src/engine/extractors/interactions.js +55 -0
  19. package/dist/src/engine/extractors/navigations.d.ts +10 -0
  20. package/dist/src/engine/extractors/navigations.js +82 -0
  21. package/dist/src/engine/extractors/network.d.ts +10 -0
  22. package/dist/src/engine/extractors/network.js +105 -0
  23. package/dist/src/engine/extractors/overview.d.ts +11 -0
  24. package/dist/src/engine/extractors/overview.js +232 -0
  25. package/dist/src/engine/extractors/profile.d.ts +12 -0
  26. package/dist/src/engine/extractors/profile.js +83 -0
  27. package/dist/src/engine/extractors/scan.d.ts +37 -0
  28. package/dist/src/engine/extractors/scan.js +218 -0
  29. package/dist/src/engine/extractors/scheduling.d.ts +10 -0
  30. package/dist/src/engine/extractors/scheduling.js +89 -0
  31. package/dist/src/engine/extractors/shifts.d.ts +11 -0
  32. package/dist/src/engine/extractors/shifts.js +118 -0
  33. package/dist/src/engine/extractors/tasks.d.ts +13 -0
  34. package/dist/src/engine/extractors/tasks.js +156 -0
  35. package/dist/src/engine/helpers.d.ts +157 -0
  36. package/dist/src/engine/helpers.js +95 -0
  37. package/dist/src/engine/loader.d.ts +34 -0
  38. package/dist/src/engine/loader.js +142 -0
  39. package/dist/src/engine/types.d.ts +452 -0
  40. package/dist/src/engine/types.js +14 -0
  41. package/dist/src/formatters/index.d.ts +6 -0
  42. package/dist/src/formatters/index.js +66 -0
  43. package/dist/src/formatters/markdown.d.ts +15 -0
  44. package/dist/src/formatters/markdown.js +474 -0
  45. package/dist/src/formatters/toon.d.ts +1 -0
  46. package/dist/src/formatters/toon.js +17 -0
  47. package/dist/src/utils/filter.d.ts +47 -0
  48. package/dist/src/utils/filter.js +136 -0
  49. package/dist/src/utils/units.d.ts +24 -0
  50. package/dist/src/utils/units.js +57 -0
  51. package/package.json +45 -4
@@ -0,0 +1,142 @@
1
+ // Copyright 2026 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // https://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import zlib from 'node:zlib';
17
+ import { loadTraceEngine } from './adapter.js';
18
+ /**
19
+ * Checks if the buffer starts with the gzip magic header bytes (0x1f, 0x8b).
20
+ */
21
+ export function isGzipBuffer(buffer) {
22
+ return buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b;
23
+ }
24
+ /**
25
+ * Decompresses a trace buffer if it is gzip-compressed or has a .gz extension.
26
+ */
27
+ export function decompressTraceBuffer(buffer, filePath) {
28
+ const isGz = Boolean(filePath?.endsWith('.gz')) || isGzipBuffer(buffer);
29
+ if (isGz) {
30
+ return zlib.gunzipSync(buffer).toString('utf-8');
31
+ }
32
+ return buffer.toString('utf-8');
33
+ }
34
+ /**
35
+ * Parses raw JSON string into an array of trace events.
36
+ * Accepts both raw arrays `[...]` and wrapped objects `{"traceEvents": [...]}`.
37
+ */
38
+ export function parseTraceJson(jsonString) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(jsonString);
42
+ }
43
+ catch (err) {
44
+ throw new Error(`Failed to parse trace JSON: ${err instanceof Error ? err.message : String(err)}`);
45
+ }
46
+ if (Array.isArray(parsed)) {
47
+ return parsed;
48
+ }
49
+ if (typeof parsed === 'object' &&
50
+ parsed !== null &&
51
+ 'traceEvents' in parsed &&
52
+ Array.isArray(parsed.traceEvents)) {
53
+ return parsed.traceEvents;
54
+ }
55
+ throw new Error('Unrecognized trace format: JSON root must be an array of trace events or an object containing a "traceEvents" array.');
56
+ }
57
+ function isLanternError(arg) {
58
+ if (!arg)
59
+ return false;
60
+ if (typeof arg === 'object' && arg !== null && ('name' in arg || 'constructor' in arg)) {
61
+ const errorObj = arg;
62
+ if (errorObj.name === 'LanternError' || errorObj.constructor?.name === 'LanternError') {
63
+ return true;
64
+ }
65
+ if (errorObj.message?.includes('NO_LCP')) {
66
+ return true;
67
+ }
68
+ }
69
+ if (typeof arg === 'string' && (arg.includes('LanternError') || arg.includes('NO_LCP'))) {
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+ /**
75
+ * Loads, decompresses, and parses a Chromium performance trace file using DevTools Trace Engine.
76
+ *
77
+ * Capabilities:
78
+ * - Transparently gunzips files with `.gz` extensions or gzip magic bytes (`0x1f 0x8b`).
79
+ * - Supports raw event arrays (`[...]`) and standard Chrome wrapper objects (`{ traceEvents: [...] }`).
80
+ * - Normalizes `parsedTrace` retrieval across different DevTools engine versions (`parsedTrace(0)` vs `traceParsedData(0)`).
81
+ * - Filters internal non-fatal Lantern simulation errors during model parsing.
82
+ *
83
+ * @param filePath - Relative or absolute path to the trace file.
84
+ * @param customEnginePath - Optional custom Trace Engine bundle path.
85
+ * @returns Object containing the TraceEngine instance, parsedTrace data, and rawEvents.
86
+ * @throws {Error} If the file does not exist, is invalid JSON, or lacks trace events.
87
+ */
88
+ export async function loadTraceFromFile(filePath, customEnginePath) {
89
+ const absolutePath = path.resolve(process.cwd(), filePath);
90
+ let rawBuffer;
91
+ try {
92
+ rawBuffer = fs.readFileSync(absolutePath);
93
+ }
94
+ catch (err) {
95
+ if (err.code === 'ENOENT') {
96
+ throw new Error(`Trace file not found: ${absolutePath}`);
97
+ }
98
+ if (err.code === 'EISDIR') {
99
+ throw new Error(`'${filePath}' is a directory. Trace engine commands accept a single trace file. To search across multiple files or directories, use: trace-cli scan "${filePath}" [options]`);
100
+ }
101
+ throw err;
102
+ }
103
+ const jsonContent = decompressTraceBuffer(rawBuffer, absolutePath);
104
+ const rawEvents = parseTraceJson(jsonContent);
105
+ const engine = await loadTraceEngine(customEnginePath);
106
+ const model = engine.TraceModel.Model.createWithAllHandlers();
107
+ const origWarn = console.warn;
108
+ const origError = console.error;
109
+ try {
110
+ console.warn = (...args) => {
111
+ if (args.length > 0 && isLanternError(args[0]))
112
+ return;
113
+ origWarn(...args);
114
+ };
115
+ console.error = (...args) => {
116
+ if (args.length > 0 && isLanternError(args[0]))
117
+ return;
118
+ origError(...args);
119
+ };
120
+ // biome-ignore lint/suspicious/noExplicitAny: trace engine parse events typing
121
+ await model.parse(rawEvents);
122
+ }
123
+ finally {
124
+ console.warn = origWarn;
125
+ console.error = origError;
126
+ }
127
+ // Normalize parsedTrace across devtools engine versions
128
+ const parsedTrace = typeof model.parsedTrace === 'function'
129
+ ? model.parsedTrace(0)
130
+ : typeof model.traceParsedData ===
131
+ 'function'
132
+ ? model.traceParsedData(0)
133
+ : null;
134
+ if (!parsedTrace) {
135
+ throw new Error('Failed to retrieve parsed trace data from TraceEngine model.');
136
+ }
137
+ return {
138
+ engine,
139
+ parsedTrace,
140
+ rawEvents,
141
+ };
142
+ }
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Core Web Vitals rating category.
3
+ */
4
+ export type CwvRating = 'good' | 'needs-improvement' | 'poor';
5
+ /**
6
+ * High-level summary of trace bounds, Core Web Vitals, and main thread category distribution.
7
+ * All duration and timestamp properties are normalized to milliseconds (ms).
8
+ */
9
+ export interface OverviewResult {
10
+ bounds: {
11
+ minMs: number;
12
+ maxMs: number;
13
+ durationMs: number;
14
+ };
15
+ mainUrl: string | null;
16
+ navigationStartMs: number | null;
17
+ coreWebVitals: {
18
+ fcp: {
19
+ valueMs: number;
20
+ rating: CwvRating;
21
+ } | null;
22
+ lcp: {
23
+ valueMs: number;
24
+ rating: CwvRating;
25
+ element?: string;
26
+ url?: string;
27
+ } | null;
28
+ cls: {
29
+ value: number;
30
+ rating: CwvRating;
31
+ worstClusterScore?: number;
32
+ } | null;
33
+ inp: {
34
+ valueMs: number;
35
+ rating: CwvRating;
36
+ interactionType?: string;
37
+ } | null;
38
+ };
39
+ mainThreadBreakdown: {
40
+ scriptingMs: number;
41
+ layoutMs: number;
42
+ renderingMs: number;
43
+ gcMs: number;
44
+ systemMs: number;
45
+ idleMs: number;
46
+ totalMs: number;
47
+ };
48
+ warnings: string[];
49
+ }
50
+ /**
51
+ * Detailed breakdown of Largest Contentful Paint subparts.
52
+ */
53
+ export interface LcpBreakdownInsight {
54
+ totalLcpMs: number;
55
+ ttfbMs: number;
56
+ loadDelayMs: number;
57
+ loadDurationMs: number;
58
+ renderDelayMs: number;
59
+ lcpElement?: string;
60
+ lcpUrl?: string;
61
+ }
62
+ /**
63
+ * Detailed breakdown of Interaction to Next Paint longest interaction.
64
+ */
65
+ export interface InpBreakdownInsight {
66
+ totalDurationMs: number;
67
+ inputDelayMs: number;
68
+ processingDurationMs: number;
69
+ presentationDelayMs: number;
70
+ eventType: string;
71
+ target?: string;
72
+ }
73
+ /**
74
+ * Diagnostic layout shift culprit information.
75
+ */
76
+ export interface ClsCulpritInsight {
77
+ score: number;
78
+ timestampMs: number;
79
+ culpritNodes: string[];
80
+ }
81
+ /**
82
+ * Render-blocking stylesheet or script resource.
83
+ */
84
+ export interface RenderBlockingInsight {
85
+ url: string;
86
+ durationMs: number;
87
+ transferSizeBytes: number;
88
+ }
89
+ /**
90
+ * Third-party entity resource usage and main thread CPU execution time.
91
+ */
92
+ export interface ThirdPartyEntityInsight {
93
+ entityName: string;
94
+ transferSizeBytes: number;
95
+ mainThreadTimeMs: number;
96
+ }
97
+ /**
98
+ * Diagnostics and metric breakdowns for CWV bottlenecks.
99
+ */
100
+ export interface InsightsResult {
101
+ navigationId?: string;
102
+ lcpBreakdown?: LcpBreakdownInsight | null;
103
+ inpBreakdown?: InpBreakdownInsight | null;
104
+ clsCulprits?: ClsCulpritInsight[];
105
+ renderBlocking?: RenderBlockingInsight[];
106
+ thirdParties?: ThirdPartyEntityInsight[];
107
+ warnings?: string[];
108
+ }
109
+ /**
110
+ * Executed task or aggregated call tree node on the main thread.
111
+ */
112
+ export interface TaskItem {
113
+ startTimeMs: number;
114
+ durationMs: number;
115
+ selfTimeMs: number;
116
+ functionName: string;
117
+ url?: string;
118
+ lineNumber?: number;
119
+ columnNumber?: number;
120
+ }
121
+ /**
122
+ * Aggregated main thread task execution details.
123
+ */
124
+ export interface TasksResult {
125
+ mode: 'list' | 'top-down' | 'bottom-up';
126
+ tasks: TaskItem[];
127
+ }
128
+ /**
129
+ * Individual network request details and timing waterfall.
130
+ */
131
+ export interface NetworkRequestItem {
132
+ requestId?: string;
133
+ url: string;
134
+ mimeType: string;
135
+ priority: string;
136
+ timing: {
137
+ queueingMs: number;
138
+ dnsMs: number;
139
+ sslMs: number;
140
+ ttfbMs: number;
141
+ downloadMs: number;
142
+ totalDurationMs: number;
143
+ };
144
+ transferSizeBytes: number;
145
+ renderBlocking?: boolean;
146
+ }
147
+ /**
148
+ * Single node in a network request initiator chain.
149
+ */
150
+ export interface InitiatorChainItem {
151
+ url: string;
152
+ initiatorUrl?: string;
153
+ type?: string;
154
+ }
155
+ /**
156
+ * Network requests waterfall and initiator analysis result.
157
+ */
158
+ export interface NetworkResult {
159
+ initiatorChain?: InitiatorChainItem[];
160
+ requests: NetworkRequestItem[];
161
+ }
162
+ /**
163
+ * User interaction timing details.
164
+ */
165
+ export interface InteractionItem {
166
+ eventType: string;
167
+ startTimeMs: number;
168
+ durationMs: number;
169
+ inputDelayMs: number;
170
+ processingDurationMs: number;
171
+ presentationDelayMs: number;
172
+ targetElement?: string;
173
+ interactionId?: number;
174
+ }
175
+ /**
176
+ * User interaction timeline result.
177
+ */
178
+ export interface InteractionsResult {
179
+ interactions: InteractionItem[];
180
+ }
181
+ /**
182
+ * DOM node identified as a layout shift culprit.
183
+ */
184
+ export interface ShiftCulprit {
185
+ nodeId?: number;
186
+ tagName?: string;
187
+ selector?: string;
188
+ debugName?: string;
189
+ scoreDelta?: number;
190
+ }
191
+ /**
192
+ * Impacted DOM node in an individual layout shift event.
193
+ */
194
+ export interface ShiftImpactedNodeItem {
195
+ nodeId?: number;
196
+ tagName?: string;
197
+ selector?: string;
198
+ debugName?: string;
199
+ oldRect?: [number, number, number, number];
200
+ newRect?: [number, number, number, number];
201
+ }
202
+ /**
203
+ * Individual layout shift event within a cluster.
204
+ */
205
+ export interface ShiftIndividualEvent {
206
+ timestampMs: number;
207
+ score: number;
208
+ weightedScoreDelta?: number;
209
+ navigationId?: string;
210
+ hadRecentInput?: boolean;
211
+ impactedNodes?: ShiftImpactedNodeItem[];
212
+ }
213
+ /**
214
+ * Layout shift session window cluster.
215
+ */
216
+ export interface ShiftClusterItem {
217
+ timestampMs: number;
218
+ durationMs: number;
219
+ cumulativeScore: number;
220
+ navigationId?: string;
221
+ culprits: ShiftCulprit[];
222
+ events?: ShiftIndividualEvent[];
223
+ }
224
+ /**
225
+ * Layout shifts analysis result.
226
+ */
227
+ export interface ShiftsResult {
228
+ clusters: ShiftClusterItem[];
229
+ }
230
+ /**
231
+ * Individual invocation instance of a searched function.
232
+ */
233
+ export interface FindCallInstance {
234
+ timestampMs: number;
235
+ durationMs: number;
236
+ url?: string;
237
+ lineNumber?: number;
238
+ columnNumber?: number;
239
+ }
240
+ /**
241
+ * Temporal breakdown interval for function call distribution.
242
+ */
243
+ export interface FindIntervalItem {
244
+ startMs: number;
245
+ endMs: number;
246
+ calls: number;
247
+ totalDurationMs: number;
248
+ }
249
+ /**
250
+ * Result of searching for a specific function's executions across a trace.
251
+ */
252
+ export interface FindResult {
253
+ functionQuery: string;
254
+ timeRange?: {
255
+ startMs: number;
256
+ endMs: number;
257
+ };
258
+ totalCalls: number;
259
+ totalDurationMs: number;
260
+ avgDurationMs: number;
261
+ firstCallMs?: number;
262
+ lastCallMs?: number;
263
+ calls: FindCallInstance[];
264
+ intervals?: FindIntervalItem[];
265
+ }
266
+ /**
267
+ * Single time bucket summary in profile progression.
268
+ */
269
+ export interface ProfileBucketItem {
270
+ startMs: number;
271
+ endMs: number;
272
+ totalActiveMs: number;
273
+ topFunctions: Array<{
274
+ name: string;
275
+ selfTimeMs: number;
276
+ selfTimePct: number;
277
+ }>;
278
+ }
279
+ /**
280
+ * Time-bucketed CPU profiling result.
281
+ */
282
+ export interface ProfileResult {
283
+ timeRange: {
284
+ startMs: number;
285
+ endMs: number;
286
+ };
287
+ bucketSizeMs: number;
288
+ buckets: ProfileBucketItem[];
289
+ }
290
+ /**
291
+ * Single function comparison entry in a differential profile.
292
+ */
293
+ export interface DiffEntryItem {
294
+ functionName: string;
295
+ url?: string;
296
+ selfTimeMsA: number;
297
+ selfTimePctA: number;
298
+ selfTimeMsB: number;
299
+ selfTimePctB: number;
300
+ deltaPct: number;
301
+ }
302
+ /**
303
+ * Differential profile result comparing two trace intervals.
304
+ */
305
+ export interface DiffResult {
306
+ windowA: {
307
+ startMs: number;
308
+ endMs: number;
309
+ durationMs: number;
310
+ };
311
+ windowB: {
312
+ startMs: number;
313
+ endMs: number;
314
+ durationMs: number;
315
+ };
316
+ entries: DiffEntryItem[];
317
+ }
318
+ /**
319
+ * Lifecycle metrics for scheduled async primitives (rAF, Timers).
320
+ */
321
+ export interface SchedulingStatItem {
322
+ scheduledCount: number;
323
+ canceledCount: number;
324
+ executedCount: number;
325
+ cancellationRatePct: number;
326
+ }
327
+ /**
328
+ * Async task and animation frame scheduling lifecycle result.
329
+ */
330
+ export interface SchedulingResult {
331
+ timeRange?: {
332
+ startMs: number;
333
+ endMs: number;
334
+ };
335
+ animationFrames: SchedulingStatItem;
336
+ timers: SchedulingStatItem;
337
+ churnDetected: boolean;
338
+ warnings: string[];
339
+ }
340
+ /**
341
+ * Raw trace event dictionary from Chromium trace JSON payload.
342
+ */
343
+ export interface RawTraceEvent {
344
+ name?: string;
345
+ cat?: string;
346
+ ph?: string;
347
+ ts?: number;
348
+ dur?: number;
349
+ pid?: number;
350
+ tid?: number;
351
+ args?: Record<string, unknown>;
352
+ }
353
+ /**
354
+ * Raw trace event entry.
355
+ */
356
+ export interface RawTraceEventItem {
357
+ /** Event name (e.g. 'LayoutShift', 'RunTask'). */
358
+ name: string;
359
+ /** Normalized timestamp in milliseconds relative to trace start. */
360
+ tsMs: number;
361
+ /** Normalized event duration in milliseconds. */
362
+ durMs?: number;
363
+ /** Trace event category. */
364
+ cat?: string;
365
+ /** Chromium event phase code (e.g. 'X', 'I', 'B', 'E'). */
366
+ ph?: string;
367
+ /** Process ID. */
368
+ pid?: number;
369
+ /** Thread ID. */
370
+ tid?: number;
371
+ /** Raw event payload arguments. */
372
+ args?: Record<string, unknown>;
373
+ /** Extracted property values mapped from dot-notation query fields. */
374
+ selectedFields?: Record<string, unknown>;
375
+ }
376
+ /**
377
+ * Raw trace event filtering result.
378
+ */
379
+ export interface EventsResult {
380
+ /** Total events matching the filter criteria across the trace. */
381
+ totalMatched: number;
382
+ /** Number of events returned in the events array (capped by limit). */
383
+ returned: number;
384
+ /** Array of matched and projected event items. */
385
+ events: RawTraceEventItem[];
386
+ }
387
+ /**
388
+ * Navigation entry from trace metadata.
389
+ */
390
+ export interface NavigationItem {
391
+ /** Navigation identifier from trace event data (args.data.navigationId). */
392
+ navigationId: string;
393
+ /** Corresponding Trace Engine InsightSet identifier (e.g. 'NAVIGATION_0'). */
394
+ insightSetId?: string;
395
+ /** Frame identifier where navigation occurred. */
396
+ frameId?: string;
397
+ /** Navigation URL or loader URL. */
398
+ url: string;
399
+ /** Timestamp in milliseconds relative to trace start. */
400
+ timestampMs: number;
401
+ /** Whether the navigation belongs to the primary main frame. */
402
+ isMainFrame: boolean;
403
+ /** Whether an InsightSet exists for this navigation. */
404
+ hasInsights: boolean;
405
+ }
406
+ /**
407
+ * Navigation list result.
408
+ */
409
+ export interface NavigationsResult {
410
+ /** Total number of navigations detected. */
411
+ total: number;
412
+ /** Ordered list of navigation entries. */
413
+ navigations: NavigationItem[];
414
+ /** Unique InsightSet identifiers present in the parsed trace. */
415
+ insightSetIds: string[];
416
+ }
417
+ /**
418
+ * Telemetry and match summary for a single trace file matched during a scan.
419
+ */
420
+ export interface ScanMatchedFile {
421
+ /** Relative or normalized path to the trace file. */
422
+ file: string;
423
+ /** Total number of events matching query criteria in this file. */
424
+ matchCount: number;
425
+ /** Unique categories associated with the matched events. */
426
+ categories: string[];
427
+ /** Name of the first matching event found in the trace. */
428
+ firstEventName?: string;
429
+ /** Raw absolute timestamp of the first matching event in milliseconds (trace bounds uncalculated). */
430
+ firstEventTsMs?: number;
431
+ }
432
+ /**
433
+ * Result of scanning multiple trace files or directories.
434
+ */
435
+ export interface ScanResult {
436
+ /** Search filters applied to the scan. */
437
+ query: {
438
+ names?: string[];
439
+ category?: string;
440
+ phase?: string;
441
+ grep?: string;
442
+ fields?: string[];
443
+ };
444
+ /** Total trace files discovered and inspected. */
445
+ totalFilesScanned: number;
446
+ /** Total trace files that contained at least one match. */
447
+ totalFilesMatched: number;
448
+ /** Total matching events across all inspected files. */
449
+ totalEventsMatched: number;
450
+ /** Ordered list of matched files, sorted by match count descending. */
451
+ matchedFiles: ScanMatchedFile[];
452
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright 2026 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // https://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ export {};
@@ -0,0 +1,6 @@
1
+ export interface FormatOptions {
2
+ format: 'toon' | 'md';
3
+ type?: 'overview' | 'insights' | 'tasks' | 'network' | 'interactions' | 'shifts' | 'find' | 'profile' | 'diff' | 'scheduling' | 'events' | 'navigations' | 'scan';
4
+ includeHint?: boolean;
5
+ }
6
+ export declare function formatResult(data: unknown, options: FormatOptions): string;
@@ -0,0 +1,66 @@
1
+ // Copyright 2026 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // https://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { formatMarkdown } from './markdown.js';
15
+ import { formatToon } from './toon.js';
16
+ const NEXT_STEPS_HINT_TOON = `
17
+ # Drill-down commands:
18
+ # - trace-cli insights <file> [--category <lcp|inp|cls|render-blocking|third-parties>] [--navigation <id>]
19
+ # - trace-cli tasks <file> [--min-duration <ms>] [--tree <bottom-up|top-down>] [--top <n>]
20
+ # - trace-cli find <file> --function <name> [--time-range <start,end>]
21
+ # - trace-cli profile <file> [--time-range <start,end>] [--bucket <ms>]
22
+ # - trace-cli diff <file> --window-a <start,end> --window-b <start,end>
23
+ # - trace-cli scheduling <file> [--time-range <start,end>]
24
+ # - trace-cli network <file> [--render-blocking] [--slowest <n>] [--initiator-chain <url>]
25
+ # - trace-cli interactions <file>
26
+ # - trace-cli shifts <file> [--events]
27
+ # - trace-cli events <file> [--name <names>] [--cat <category>] [--fields <fields>]
28
+ # - trace-cli navigations <file>
29
+ #
30
+ # Multi-file search:
31
+ # - trace-cli scan <dir-or-glob> [--name <names>] [--cat <category>] [--grep <pattern>]
32
+ `.trim();
33
+ const NEXT_STEPS_HINT_MD = `
34
+ ---
35
+ ### Drill-down Commands
36
+ - \`trace-cli insights <file> [--category <lcp|inp|cls|render-blocking|third-parties>] [--navigation <id>]\`
37
+ - \`trace-cli tasks <file> [--min-duration <ms>] [--tree <bottom-up|top-down>] [--top <n>]\`
38
+ - \`trace-cli find <file> --function <name> [--time-range <start,end>]\`
39
+ - \`trace-cli profile <file> [--time-range <start,end>] [--bucket <ms>]\`
40
+ - \`trace-cli diff <file> --window-a <start,end> --window-b <start,end>\`
41
+ - \`trace-cli scheduling <file> [--time-range <start,end>]\`
42
+ - \`trace-cli network <file> [--render-blocking] [--slowest <n>] [--initiator-chain <url>]\`
43
+ - \`trace-cli interactions <file>\`
44
+ - \`trace-cli shifts <file> [--events]\`
45
+ - \`trace-cli events <file> [--name <names>] [--cat <category>] [--fields <fields>]\`
46
+ - \`trace-cli navigations <file>\`
47
+
48
+ ### Multi-file Search
49
+ - \`trace-cli scan <dir-or-glob> [--name <names>] [--cat <category>] [--grep <pattern>]\`
50
+ `.trim();
51
+ export function formatResult(data, options) {
52
+ let output = '';
53
+ if (options.format === 'md') {
54
+ output = formatMarkdown(data, options.type);
55
+ if (options.includeHint) {
56
+ output = `${output}\n\n${NEXT_STEPS_HINT_MD}`;
57
+ }
58
+ }
59
+ else {
60
+ output = formatToon(data);
61
+ if (options.includeHint) {
62
+ output = `${output}\n\n${NEXT_STEPS_HINT_TOON}`;
63
+ }
64
+ }
65
+ return output;
66
+ }
@@ -0,0 +1,15 @@
1
+ import type { DiffResult, EventsResult, FindResult, InsightsResult, InteractionsResult, NavigationsResult, NetworkResult, OverviewResult, ProfileResult, ScanResult, SchedulingResult, ShiftsResult, TasksResult } from '../engine/types.js';
2
+ export declare function formatOverviewMarkdown(overview: OverviewResult): string;
3
+ export declare function formatInsightsMarkdown(insights: InsightsResult): string;
4
+ export declare function formatTasksMarkdown(tasksResult: TasksResult): string;
5
+ export declare function formatNetworkMarkdown(network: NetworkResult): string;
6
+ export declare function formatInteractionsMarkdown(interactions: InteractionsResult): string;
7
+ export declare function formatShiftsMarkdown(shifts: ShiftsResult): string;
8
+ export declare function formatFindMarkdown(findResult: FindResult): string;
9
+ export declare function formatProfileMarkdown(profileResult: ProfileResult): string;
10
+ export declare function formatDiffMarkdown(diffResult: DiffResult): string;
11
+ export declare function formatSchedulingMarkdown(sched: SchedulingResult): string;
12
+ export declare function formatEventsMarkdown(result: EventsResult): string;
13
+ export declare function formatNavigationsMarkdown(result: NavigationsResult): string;
14
+ export declare function formatScanMarkdown(result: ScanResult): string;
15
+ export declare function formatMarkdown(data: unknown, type?: string): string;