tracelight 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/dist/apiTypes.d.ts +139 -0
  4. package/dist/apiTypes.d.ts.map +1 -0
  5. package/dist/apiTypes.js +11 -0
  6. package/dist/apiTypes.js.map +1 -0
  7. package/dist/cli.d.ts +13 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +73 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/model.d.ts +133 -0
  12. package/dist/model.d.ts.map +1 -0
  13. package/dist/model.js +20 -0
  14. package/dist/model.js.map +1 -0
  15. package/dist/parser/index.d.ts +27 -0
  16. package/dist/parser/index.d.ts.map +1 -0
  17. package/dist/parser/index.js +434 -0
  18. package/dist/parser/index.js.map +1 -0
  19. package/dist/parser/rawTypes.d.ts +149 -0
  20. package/dist/parser/rawTypes.d.ts.map +1 -0
  21. package/dist/parser/rawTypes.js +11 -0
  22. package/dist/parser/rawTypes.js.map +1 -0
  23. package/dist/pricing.d.ts +35 -0
  24. package/dist/pricing.d.ts.map +1 -0
  25. package/dist/pricing.js +73 -0
  26. package/dist/pricing.js.map +1 -0
  27. package/dist/projectLoader.d.ts +31 -0
  28. package/dist/projectLoader.d.ts.map +1 -0
  29. package/dist/projectLoader.js +335 -0
  30. package/dist/projectLoader.js.map +1 -0
  31. package/dist/scripts/summarize.d.ts +7 -0
  32. package/dist/scripts/summarize.d.ts.map +1 -0
  33. package/dist/scripts/summarize.js +153 -0
  34. package/dist/scripts/summarize.js.map +1 -0
  35. package/dist/server/index.d.ts +18 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +100 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/package.json +70 -0
  40. package/ui/dist/assets/index-Bnr6kWU4.js +62 -0
  41. package/ui/dist/assets/index-HoOM9YCS.css +1 -0
  42. package/ui/dist/index.html +13 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aneesh Venuturumilli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # tracelight
2
+
3
+ **See everything your Claude Code agent actually did.**
4
+
5
+ Your AI coding sessions are quietly saved to your laptop and never looked at again. tracelight turns that hidden history into a beautiful, searchable timeline you can actually read — every prompt, every thought, every tool call, every file edit, and exactly what it all cost.
6
+
7
+ One command. Runs on your machine. Nothing leaves it.
8
+
9
+ ```bash
10
+ npx tracelight
11
+ ```
12
+
13
+ <!-- Screenshot/GIF placeholders — record these and commit to docs/ before publishing. Run `node dist/cli.js` to open the app for capture. -->
14
+ ![tracelight trace view](docs/trace-view.gif)
15
+ <!-- docs/trace-view.gif: a screen capture of the trace view scrolling through a real session with tool calls and a diff visible -->
16
+
17
+ ---
18
+
19
+ ## Why you'll want it
20
+
21
+ Every time you use Claude Code, a complete recording of the session is written to a hidden folder on your machine. It's a goldmine — and it's unreadable, buried in raw log files nobody opens.
22
+
23
+ tracelight is the missing viewer. Type one command and your whole agent history opens in your browser, rendered the way it should have been all along.
24
+
25
+ - **Relive any session** as a clean, top-to-bottom timeline instead of a wall of JSON.
26
+ - **Open the black box.** Expand the agent's hidden reasoning, every tool call, and every command it ran.
27
+ - **Read file edits as real diffs** — green for added, red for removed — not escaped JSON blobs.
28
+ - **Follow the agent's helpers.** When it spins up sub-agents, their work nests right under the call that launched them.
29
+ - **Know what it cost.** Per-session token and dollar breakdowns, your priciest and longest turns, and which tools you lean on most.
30
+ - **Find anything instantly** with full-text search across messages, reasoning, and tool activity.
31
+
32
+ ## Private by design. Free to run.
33
+
34
+ No account. No API key. No upload. tracelight reads the files already on your disk and serves a local page in your browser — nothing is ever sent to a server, and there's no usage cost beyond your own laptop. Your conversations stay yours.
35
+
36
+ ## Get started in 30 seconds
37
+
38
+ ```bash
39
+ npx tracelight
40
+ ```
41
+
42
+ That's it. tracelight finds your sessions, starts a local server, and opens your browser automatically.
43
+
44
+ ```bash
45
+ npx tracelight --port 4000 # pick a specific port
46
+ npx tracelight --no-open # start without opening the browser
47
+ ```
48
+
49
+ Requires Node 18+. (Works with the standard Claude Code session folder at `~/.claude/projects/`.)
50
+
51
+ ## What's inside
52
+
53
+ **The session list.** Every project and session, newest first, each with at-a-glance chips: when it ran, how long it took, turns, tokens, and the models involved.
54
+
55
+ **The trace.** The main event — one session as a vertical timeline:
56
+
57
+ - Your messages, the agent's replies, and its (normally hidden) thinking, in the exact order they happened. Reasoning is tucked behind a toggle so it never overwhelms the view.
58
+ - Tool calls as tidy cards: the tool, its pretty-printed input, and the result — long outputs collapsed with a one-click expand.
59
+ - File edits rendered as proper colored diffs instead of raw JSON.
60
+ - Sub-agent runs nested, indented, and collapsible under the call that spawned them.
61
+ - Routine scaffolding dimmed and folded away so the real story stands out.
62
+
63
+ **Search.** A live filter across messages, reasoning, tool names, inputs, and results — with a running "showing X of Y turns" count.
64
+
65
+ **Analytics.** Flip to the analytics tab for cost-by-model, a full token breakdown, your most-used tools, and your longest and most expensive turns.
66
+
67
+ > **About the cost numbers:** dollar figures are estimates from a built-in pricing table (current as of 2026-06-13). Check [anthropic.com/pricing](https://www.anthropic.com/pricing) for exact current rates, and note that models tracelight doesn't recognize show token counts without a cost estimate.
68
+
69
+ ## Built to keep working
70
+
71
+ The Claude Code log format is undocumented and changes between versions. tracelight is built defensively: anything it doesn't recognize shows up as a clearly-marked block instead of crashing or silently vanishing — so your sessions stay readable even as the format evolves.
72
+
73
+ ## For developers
74
+
75
+ Want to hack on it?
76
+
77
+ ```bash
78
+ git clone https://github.com/aneeshv12/tracelight
79
+ cd tracelight
80
+ npm install
81
+ ```
82
+
83
+ Run the UI with hot reload and the API server side by side:
84
+
85
+ ```bash
86
+ npm run dev:ui # Vite dev server (UI)
87
+ npm run build:server && npm start # compile + run the API server
88
+ ```
89
+
90
+ Handy commands:
91
+
92
+ ```bash
93
+ npm run build # compile the server + build the UI bundle
94
+ npm test # parser test suite (vitest)
95
+ npm run typecheck # type-check server and UI
96
+ ```
97
+
98
+ **Architecture.** A strict two-layer design: `src/parser/` is the only code that understands the raw transcript format — it normalizes each line into a stable internal model. The server and UI consume only that model (via shared JSON types in `src/apiTypes.ts`) and never touch the parser. When the log format shifts, only the parser changes. That's also what makes "support other agent frameworks" a feature rather than a rewrite.
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,139 @@
1
+ /**
2
+ * JSON API response types shared between the Fastify server and the React UI.
3
+ *
4
+ * These are the only types the UI imports from src/. They are derived
5
+ * from model.ts but shaped for over-the-wire JSON (no ReadonlyArray, etc.).
6
+ * The server serializes SessionSummary into these; the UI renders these.
7
+ *
8
+ * Rule: the UI must compile without importing anything from src/parser/.
9
+ */
10
+ export interface TokenUsageSummary {
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ cacheCreationInputTokens: number;
14
+ cacheReadInputTokens: number;
15
+ }
16
+ export interface TextBlockJson {
17
+ type: "text";
18
+ text: string;
19
+ }
20
+ export interface ThinkingBlockJson {
21
+ type: "thinking";
22
+ /** Opaque signature field from the API is intentionally dropped here. */
23
+ thinking: string;
24
+ }
25
+ export interface ToolUseBlockJson {
26
+ type: "tool_use";
27
+ id: string;
28
+ name: string;
29
+ input: Record<string, unknown>;
30
+ }
31
+ export interface ToolResultBlockJson {
32
+ type: "tool_result";
33
+ tool_use_id: string;
34
+ content: string | TextBlockJson[];
35
+ is_error?: boolean;
36
+ }
37
+ export type ContentBlockJson = TextBlockJson | ThinkingBlockJson | ToolUseBlockJson | ToolResultBlockJson;
38
+ export interface ToolCallJson {
39
+ id: string;
40
+ name: string;
41
+ input: Record<string, unknown>;
42
+ result: string | undefined;
43
+ isError: boolean;
44
+ subagentId: string | undefined;
45
+ }
46
+ /**
47
+ * One normalized conversation round-trip, serialized for the API.
48
+ * Includes all turns (meta and sidechain) in parser order; the UI decides what to render.
49
+ */
50
+ export interface TurnJson {
51
+ turnIndex: number;
52
+ promptId: string | undefined;
53
+ timestamp: string | undefined;
54
+ userText: string | undefined;
55
+ isMeta: boolean;
56
+ assistantBlocks: ContentBlockJson[];
57
+ toolCalls: ToolCallJson[];
58
+ usage: TokenUsageSummary | undefined;
59
+ model: string | undefined;
60
+ durationMs: number | undefined;
61
+ isSidechain: boolean;
62
+ }
63
+ /**
64
+ * Lightweight per-session summary for the home view list.
65
+ * Avoids sending full turn content in the project list response.
66
+ */
67
+ export interface SessionListItem {
68
+ sessionId: string;
69
+ /** Path to the source .jsonl file on disk (for /api/sessions/:id lookups). */
70
+ filePath: string;
71
+ aiTitle: string | undefined;
72
+ startedAt: string | undefined;
73
+ endedAt: string | undefined;
74
+ durationMs: number | undefined;
75
+ /** Count of non-meta turns. */
76
+ turnCount: number;
77
+ totalUsage: TokenUsageSummary;
78
+ modelsUsed: string[];
79
+ unknownEventCount: number;
80
+ }
81
+ /**
82
+ * A Claude Code "project" — one slug directory under ~/.claude/projects/.
83
+ * The slug is the cwd path with slashes replaced by hyphens.
84
+ */
85
+ export interface ProjectListing {
86
+ /** Directory basename under ~/.claude/projects/, e.g. "-Users-alice-myproject". */
87
+ slug: string;
88
+ /** Human-readable project name derived from the slug. */
89
+ displayName: string;
90
+ sessions: SessionListItem[];
91
+ /** Timestamp of the most recent session start, for sorting. */
92
+ mostRecentSessionAt: string | undefined;
93
+ }
94
+ /** One subagent spawned during the session, with its own parsed turns. */
95
+ export interface SubagentTraceJson {
96
+ agentId: string;
97
+ agentType: string;
98
+ description: string;
99
+ /** The tool_use_id in the parent session that spawned this subagent. */
100
+ parentToolUseId: string;
101
+ turns: TurnJson[];
102
+ }
103
+ /** USD per 1 million tokens for one model family. */
104
+ export interface ModelPricingJson {
105
+ inputPer1M: number;
106
+ outputPer1M: number;
107
+ cacheWritePer1M: number;
108
+ cacheReadPer1M: number;
109
+ }
110
+ /** Full pricing table as sent over the wire. */
111
+ export interface PricingTableJson {
112
+ /** ISO date string: when the table was last verified against Anthropic's public prices. */
113
+ asOfDate: string;
114
+ /** Map from model-family prefix (e.g. "claude-sonnet") to pricing. */
115
+ models: Record<string, ModelPricingJson>;
116
+ }
117
+ /**
118
+ * Full session detail returned by /api/sessions/:id.
119
+ * Includes the full turns array so the trace-view UI can render the conversation.
120
+ */
121
+ export interface SessionDetail {
122
+ sessionId: string;
123
+ filePath: string;
124
+ aiTitle: string | undefined;
125
+ startedAt: string | undefined;
126
+ endedAt: string | undefined;
127
+ durationMs: number | undefined;
128
+ turnCount: number;
129
+ totalUsage: TokenUsageSummary;
130
+ modelsUsed: string[];
131
+ unknownEventCount: number;
132
+ /** All turns in parser order (meta and sidechain turns included, flagged by isMeta/isSidechain). */
133
+ turns: TurnJson[];
134
+ /** Subagent traces spawned during this session, each with their own turns. */
135
+ subagents: SubagentTraceJson[];
136
+ /** Pricing table snapshot included so the UI can compute cost estimates client-side. */
137
+ pricing: PricingTableJson;
138
+ }
139
+ //# sourceMappingURL=apiTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiTypes.d.ts","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * JSON API response types shared between the Fastify server and the React UI.
3
+ *
4
+ * These are the only types the UI imports from src/. They are derived
5
+ * from model.ts but shaped for over-the-wire JSON (no ReadonlyArray, etc.).
6
+ * The server serializes SessionSummary into these; the UI renders these.
7
+ *
8
+ * Rule: the UI must compile without importing anything from src/parser/.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=apiTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiTypes.js","sourceRoot":"","sources":["../src/apiTypes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tracelight CLI entry point.
4
+ *
5
+ * Usage:
6
+ * npx . — start server on a free port and open the browser
7
+ * npx . --port 3456 — use a specific port
8
+ * npx . --no-open — start server without opening the browser
9
+ *
10
+ * Plain argv parsing — no commander needed for this trivial interface.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
package/dist/cli.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tracelight CLI entry point.
4
+ *
5
+ * Usage:
6
+ * npx . — start server on a free port and open the browser
7
+ * npx . --port 3456 — use a specific port
8
+ * npx . --no-open — start server without opening the browser
9
+ *
10
+ * Plain argv parsing — no commander needed for this trivial interface.
11
+ */
12
+ import { createServer } from "./server/index.js";
13
+ import { createServer as createNetServer } from "net";
14
+ function findFreePort(startPort) {
15
+ return new Promise((resolve, reject) => {
16
+ const server = createNetServer();
17
+ server.listen(startPort, "127.0.0.1", () => {
18
+ const address = server.address();
19
+ const port = typeof address === "object" && address !== null ? address.port : startPort;
20
+ server.close(() => resolve(port));
21
+ });
22
+ server.on("error", () => {
23
+ // Port in use — try the next one
24
+ findFreePort(startPort + 1).then(resolve).catch(reject);
25
+ });
26
+ });
27
+ }
28
+ function parseArgs(argv) {
29
+ let port = null;
30
+ let openBrowser = true;
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+ if (arg === "--port" && i + 1 < argv.length) {
34
+ const parsedPort = parseInt(argv[i + 1], 10);
35
+ if (!isNaN(parsedPort) && parsedPort > 0 && parsedPort < 65536) {
36
+ port = parsedPort;
37
+ }
38
+ i++;
39
+ }
40
+ else if (arg === "--no-open") {
41
+ openBrowser = false;
42
+ }
43
+ }
44
+ return { port, openBrowser };
45
+ }
46
+ async function main() {
47
+ const args = parseArgs(process.argv.slice(2));
48
+ const port = args.port ?? (await findFreePort(7823));
49
+ console.log(`tracelight starting on port ${port}...`);
50
+ const server = await createServer({ port });
51
+ const url = await server.start();
52
+ console.log(`tracelight running at ${url}`);
53
+ if (args.openBrowser) {
54
+ // Dynamic import so the module loads only when needed
55
+ const { default: open } = await import("open");
56
+ await open(url);
57
+ }
58
+ // Keep the process running; Ctrl+C will stop it
59
+ process.on("SIGINT", async () => {
60
+ console.log("\nStopping tracelight...");
61
+ await server.stop();
62
+ process.exit(0);
63
+ });
64
+ process.on("SIGTERM", async () => {
65
+ await server.stop();
66
+ process.exit(0);
67
+ });
68
+ }
69
+ main().catch((error) => {
70
+ console.error("tracelight failed to start:", error);
71
+ process.exit(1);
72
+ });
73
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Stable internal model for tracelight.
3
+ *
4
+ * All types here are derived from observation of real Claude Code JSONL transcripts.
5
+ * The UI imports only from this file — never from src/parser/.
6
+ *
7
+ * Key observations from real transcripts:
8
+ * - Each JSONL line is a raw event with a `type` field.
9
+ * - Conversation lines have type "user" or "assistant" and carry a `message` object.
10
+ * - Multiple assistant lines can share the same API message id (streaming continuation).
11
+ * - Tool calls appear as content blocks with type "tool_use" inside an assistant message.
12
+ * - Tool results appear as content blocks with type "tool_result" inside a user message.
13
+ * - Subagents live in a separate JSONL file under subagents/<agentId>.jsonl.
14
+ * - Subagent spawning is visible as an "Agent" tool_use in the parent; the matching
15
+ * user-side tool_result carries toolUseResult.agentId linking to the subagent file.
16
+ * - Session metadata is scattered: "mode", "permission-mode", "file-history-snapshot",
17
+ * "ai-title", "last-prompt", "queue-operation", "attachment", "system" lines.
18
+ */
19
+ export interface TextBlock {
20
+ readonly type: "text";
21
+ readonly text: string;
22
+ }
23
+ export interface ThinkingBlock {
24
+ readonly type: "thinking";
25
+ readonly thinking: string;
26
+ /** Opaque signature from the API — preserved but not interpreted. */
27
+ readonly signature: string;
28
+ }
29
+ export interface ToolUseBlock {
30
+ readonly type: "tool_use";
31
+ readonly id: string;
32
+ readonly name: string;
33
+ readonly input: Record<string, unknown>;
34
+ }
35
+ export interface ToolResultBlock {
36
+ readonly type: "tool_result";
37
+ readonly tool_use_id: string;
38
+ /** Result content — string for most tools, array of text blocks for Agent tool. */
39
+ readonly content: string | ReadonlyArray<TextBlock>;
40
+ readonly is_error?: boolean;
41
+ }
42
+ export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
43
+ export interface TokenUsage {
44
+ readonly inputTokens: number;
45
+ readonly outputTokens: number;
46
+ readonly cacheCreationInputTokens: number;
47
+ readonly cacheReadInputTokens: number;
48
+ }
49
+ export interface ToolCall {
50
+ /** Matches tool_use block id */
51
+ readonly id: string;
52
+ readonly name: string;
53
+ readonly input: Record<string, unknown>;
54
+ /**
55
+ * Result populated when the matching tool_result line is parsed.
56
+ * Undefined until the result is seen (may stay undefined for incomplete traces).
57
+ */
58
+ readonly result: string | undefined;
59
+ readonly isError: boolean;
60
+ /**
61
+ * For "Agent" tool calls: the agentId of the spawned subagent.
62
+ * Populated from the user-side toolUseResult.agentId field.
63
+ */
64
+ readonly subagentId: string | undefined;
65
+ }
66
+ export interface SubagentTrace {
67
+ /** Unique agent id — matches filename under subagents/ directory. */
68
+ readonly agentId: string;
69
+ readonly description: string;
70
+ readonly agentType: string;
71
+ /** The tool_use_id in the parent that spawned this subagent. */
72
+ readonly parentToolUseId: string;
73
+ /** Full parsed turns from the subagent's own JSONL (populated in milestone 4). */
74
+ readonly turns: ReadonlyArray<Turn>;
75
+ }
76
+ /**
77
+ * A Turn groups one round-trip: a user message followed by all assistant response
78
+ * lines that belong to it (same promptId), plus any tool calls and results.
79
+ *
80
+ * Claude Code emits multiple assistant JSONL lines per logical turn because:
81
+ * 1. Streaming: individual text/thinking/tool_use blocks arrive as separate lines
82
+ * that share the same message.id.
83
+ * 2. Sidechain turns for subagent context are written before the main response.
84
+ *
85
+ * The parser merges same-message-id assistant lines into one Turn.
86
+ */
87
+ export interface Turn {
88
+ readonly turnIndex: number;
89
+ readonly promptId: string | undefined;
90
+ /** UUID of the user JSONL line that opened this turn. */
91
+ readonly userMessageUuid: string | undefined;
92
+ /** Timestamp of the user message that opened this turn. */
93
+ readonly timestamp: string | undefined;
94
+ /** Human-readable text from the user message (stripped of XML wrappers). */
95
+ readonly userText: string | undefined;
96
+ /** Whether this is a meta / internal message (slash commands, local-command output). */
97
+ readonly isMeta: boolean;
98
+ /** All content blocks from merged assistant lines, in order. */
99
+ readonly assistantBlocks: ReadonlyArray<ContentBlock>;
100
+ readonly toolCalls: ReadonlyArray<ToolCall>;
101
+ readonly usage: TokenUsage | undefined;
102
+ readonly model: string | undefined;
103
+ readonly durationMs: number | undefined;
104
+ /** True when this turn belongs to a subagent context (isSidechain flag on raw line). */
105
+ readonly isSidechain: boolean;
106
+ }
107
+ export interface UnknownEvent {
108
+ readonly kind: "unknown";
109
+ /** The raw `type` field value from the JSONL line, or "<missing>" if absent. */
110
+ readonly rawType: string;
111
+ /** The full parsed JSON object from the line. */
112
+ readonly rawPayload: Record<string, unknown>;
113
+ readonly lineIndex: number;
114
+ }
115
+ export interface SessionSummary {
116
+ readonly sessionId: string;
117
+ readonly aiTitle: string | undefined;
118
+ readonly startedAt: string | undefined;
119
+ readonly endedAt: string | undefined;
120
+ /** Duration in ms derived from first/last timestamps. */
121
+ readonly durationMs: number | undefined;
122
+ /** All normalized turns, in order. Meta turns are included but flagged. */
123
+ readonly turns: ReadonlyArray<Turn>;
124
+ /** Subagent traces referenced by this session (populated in milestone 4). */
125
+ readonly subagents: ReadonlyArray<SubagentTrace>;
126
+ /** Total tokens across all turns. */
127
+ readonly totalUsage: TokenUsage;
128
+ /** Distinct model ids used. */
129
+ readonly modelsUsed: ReadonlyArray<string>;
130
+ /** Lines the parser could not interpret — for UI display, never crashes. */
131
+ readonly unknownEvents: ReadonlyArray<UnknownEvent>;
132
+ }
133
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,aAAa,GACb,YAAY,GACZ,eAAe,CAAC;AAMpB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC;AAMD,MAAM,WAAW,QAAQ;IACvB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAMD,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;CACrC;AAMD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACtD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAMD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAMD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,yDAAyD;IACzD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IACjD,qCAAqC;IACrC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrD"}
package/dist/model.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Stable internal model for tracelight.
3
+ *
4
+ * All types here are derived from observation of real Claude Code JSONL transcripts.
5
+ * The UI imports only from this file — never from src/parser/.
6
+ *
7
+ * Key observations from real transcripts:
8
+ * - Each JSONL line is a raw event with a `type` field.
9
+ * - Conversation lines have type "user" or "assistant" and carry a `message` object.
10
+ * - Multiple assistant lines can share the same API message id (streaming continuation).
11
+ * - Tool calls appear as content blocks with type "tool_use" inside an assistant message.
12
+ * - Tool results appear as content blocks with type "tool_result" inside a user message.
13
+ * - Subagents live in a separate JSONL file under subagents/<agentId>.jsonl.
14
+ * - Subagent spawning is visible as an "Agent" tool_use in the parent; the matching
15
+ * user-side tool_result carries toolUseResult.agentId linking to the subagent file.
16
+ * - Session metadata is scattered: "mode", "permission-mode", "file-history-snapshot",
17
+ * "ai-title", "last-prompt", "queue-operation", "attachment", "system" lines.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Normalizing parser for Claude Code JSONL transcript files.
3
+ *
4
+ * Two-layer design: all knowledge of the Claude Code format lives here.
5
+ * The rest of the codebase imports only from src/model.ts.
6
+ *
7
+ * Parsing strategy:
8
+ * - Parse each line as JSON; any parse failure becomes an UnknownEvent.
9
+ * - Recognize known line types; unrecognized types become UnknownEvent.
10
+ * - Group conversation lines into Turns by promptId.
11
+ * - Multiple assistant lines sharing the same message.id are merged into
12
+ * a single Turn (Claude Code emits streaming chunks as separate lines).
13
+ * - Tool calls and their matching results are paired across turns.
14
+ */
15
+ import type { SessionSummary, Turn } from "../model.js";
16
+ export declare function parseSessionJsonl(jsonlText: string): SessionSummary;
17
+ /**
18
+ * Parses a subagent transcript's JSONL text into Turn[].
19
+ *
20
+ * Subagent .jsonl files use the same line format as the main transcript.
21
+ * All their turns have isSidechain true (that flag is on the raw lines themselves).
22
+ * This function reuses the core turn-building logic from parseLines; it extracts
23
+ * just the turns from the resulting SessionSummary rather than duplicating logic.
24
+ */
25
+ export declare function parseSubagentTurns(jsonlText: string): Turn[];
26
+ export declare function parseLines(lines: ReadonlyArray<string>): SessionSummary;
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,IAAI,EASL,MAAM,aAAa,CAAC;AA4CrB,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAGnE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,CAI5D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,cAAc,CAwTvE"}