@mariozechner/pi-coding-agent 0.8.5 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.9.0] - 2025-11-21
4
+
5
+ ### Added
6
+
7
+ - **`/clear` Command**: New slash command to reset the conversation context and start a fresh session. Aborts any in-flight agent work, clears all messages, and creates a new session file. ([#48](https://github.com/badlogic/pi-mono/pull/48))
8
+ - **Model Cycling with Thinking Levels**: The `--models` flag now supports thinking level syntax (e.g., `--models sonnet:high,haiku:low`). When cycling models with `Ctrl+P`, the associated thinking level is automatically applied. The first model in the scope is used as the initial model when starting a new session. Both model and thinking level changes are now saved to session and settings for persistence. ([#47](https://github.com/badlogic/pi-mono/pull/47))
9
+ - **`--thinking` Flag**: New CLI flag to set thinking level directly (e.g., `--thinking high`). Valid values: `off`, `minimal`, `low`, `medium`, `high`. Takes highest priority over all other thinking level sources. ([#45](https://github.com/badlogic/pi-mono/issues/45))
10
+
11
+ ### Breaking
12
+
13
+ - **Interactive Mode with Initial Prompt**: Passing a prompt on the command line (e.g., `pi "List files"`) now starts interactive mode with the prompt pre-submitted, instead of exiting after completion. Use `--print` or `-p` to get the previous non-interactive behavior (e.g., `pi -p "List files"`). This matches Claude CLI (`-p`) and Codex (`exec`) behavior. ([#46](https://github.com/badlogic/pi-mono/issues/46))
14
+
15
+ ### Fixed
16
+
17
+ - **Slash Command Autocomplete**: Fixed issue where pressing Enter on a highlighted slash command suggestion (e.g., typing `/mod` with `/model` highlighted) would submit the partial text instead of executing the selected command. Now Enter applies the completion and submits in one action. ([#49](https://github.com/badlogic/pi-mono/issues/49))
18
+ - **Model Matching Priority**: The `--models` flag now prioritizes exact matches over partial matches. Supports `provider/modelId` format (e.g., `openrouter/openai/gpt-5.1-codex`) for precise selection. Exact ID matches are tried before partial matching, so `--models gpt-5.1-codex` correctly selects `gpt-5.1-codex` instead of `openai/gpt-5.1-codex-mini`.
19
+ - **Markdown Link Rendering**: Fixed links with identical text and href (e.g., `https://github.com/badlogic/pi-mono/pull/48/files`) being rendered twice. Now correctly compares raw text instead of styled text (which contains ANSI codes) when determining if link text matches href.
4
20
 
5
21
  ## [0.8.5] - 2025-11-21
6
22
 
package/README.md CHANGED
@@ -250,10 +250,11 @@ You can add custom HTTP headers to bypass Cloudflare bot detection, add authenti
250
250
  When starting `pi`, models are selected in this order:
251
251
 
252
252
  1. **CLI args**: `--provider` and `--model` flags
253
- 2. **Restored from session**: If using `--continue` or `--resume`
254
- 3. **Saved default**: From `~/.pi/agent/settings.json` (set when you select a model with `/model`)
255
- 4. **First available**: First model with a valid API key
256
- 5. **None**: Allowed in interactive mode (shows error on message submission)
253
+ 2. **First from `--models` scope**: If `--models` is provided (skipped when using `--continue` or `--resume`)
254
+ 3. **Restored from session**: If using `--continue` or `--resume`
255
+ 4. **Saved default**: From `~/.pi/agent/settings.json` (set when you select a model with `/model`)
256
+ 5. **First available**: First model with a valid API key
257
+ 6. **None**: Allowed in interactive mode (shows error on message submission)
257
258
 
258
259
  ### Provider Defaults
259
260
 
@@ -445,6 +446,16 @@ Logout from OAuth providers:
445
446
 
446
447
  Shows a list of logged-in providers to logout from.
447
448
 
449
+ ### /clear
450
+
451
+ Clear the conversation context and start a fresh session:
452
+
453
+ ```
454
+ /clear
455
+ ```
456
+
457
+ Aborts any in-flight agent work, clears all messages, and creates a new session file.
458
+
448
459
  ## Editor Features
449
460
 
450
461
  The interactive input editor includes several productivity features:
@@ -642,11 +653,14 @@ Custom system prompt. Can be:
642
653
  If the argument is a valid file path, the file contents will be used as the system prompt. Otherwise, the text is used directly. Project context files and datetime are automatically appended.
643
654
 
644
655
  **--mode <mode>**
645
- Output mode for non-interactive usage. Options:
656
+ Output mode for non-interactive usage (implies `--print`). Options:
646
657
  - `text` (default): Output only the final assistant message text
647
658
  - `json`: Stream all agent events as JSON (one event per line). Events are emitted by `@mariozechner/pi-agent` and include message updates, tool executions, and completions
648
659
  - `rpc`: JSON mode plus stdin listener for headless operation. Send JSON commands on stdin: `{"type":"prompt","message":"..."}` or `{"type":"abort"}`. See [test/rpc-example.ts](test/rpc-example.ts) for a complete example
649
660
 
661
+ **--print, -p**
662
+ Non-interactive mode: process the prompt(s) and exit. Without this flag, passing a prompt starts interactive mode with the prompt pre-submitted. Similar to Claude's `-p` flag and Codex's `exec` command.
663
+
650
664
  **--no-session**
651
665
  Don't save session (ephemeral mode)
652
666
 
@@ -660,12 +674,27 @@ Continue the most recent session
660
674
  Select a session to resume (opens interactive selector)
661
675
 
662
676
  **--models <patterns>**
663
- Comma-separated model patterns for quick cycling with `Ctrl+P`. Patterns match against model IDs and names (case-insensitive). When multiple versions exist, prefers aliases over dated versions (e.g., `claude-sonnet-4-5` over `claude-sonnet-4-5-20250929`). Without this flag, `Ctrl+P` cycles through all available models.
677
+ Comma-separated model patterns for quick cycling with `Ctrl+P`. Matching priority:
678
+ 1. `provider/modelId` exact match (e.g., `openrouter/openai/gpt-5.1-codex`)
679
+ 2. Exact model ID match (e.g., `gpt-5.1-codex`)
680
+ 3. Partial match against model IDs and names (case-insensitive)
681
+
682
+ When multiple partial matches exist, prefers aliases over dated versions (e.g., `claude-sonnet-4-5` over `claude-sonnet-4-5-20250929`). Without this flag, `Ctrl+P` cycles through all available models.
683
+
684
+ Each pattern can optionally include a thinking level suffix: `pattern:level` where level is one of `off`, `minimal`, `low`, `medium`, or `high`. When cycling models, the associated thinking level is automatically applied. The first model in the list is used as the initial model when starting a new session.
685
+
686
+ Examples:
687
+ - `--models openrouter/openai/gpt-5.1-codex` - Exact provider/model match
688
+ - `--models gpt-5.1-codex` - Exact ID match (not `openai/gpt-5.1-codex-mini`)
689
+ - `--models sonnet:high,haiku:low` - Sonnet with high thinking, Haiku with low thinking
690
+ - `--models sonnet,haiku` - Partial match for any model containing "sonnet" or "haiku"
691
+
692
+ **--thinking <level>**
693
+ Set thinking level for reasoning-capable models. Valid values: `off`, `minimal`, `low`, `medium`, `high`. Takes highest priority over all other thinking level sources (saved settings, `--models` pattern levels, session restore).
664
694
 
665
695
  Examples:
666
- - `--models claude-sonnet,gpt-4o` - Scope to Claude Sonnet and GPT-4o
667
- - `--models sonnet,haiku` - Match any model containing "sonnet" or "haiku"
668
- - `--models gemini` - All Gemini models
696
+ - `--thinking high` - Start with high thinking level
697
+ - `--thinking off` - Disable thinking even if saved setting was different
669
698
 
670
699
  **--help, -h**
671
700
  Show help message
@@ -676,10 +705,13 @@ Show help message
676
705
  # Start interactive mode
677
706
  pi
678
707
 
679
- # Single message mode (text output)
708
+ # Interactive mode with initial prompt (stays running after completion)
680
709
  pi "List all .ts files in src/"
681
710
 
682
- # JSON mode - stream all agent events
711
+ # Non-interactive mode (process prompt and exit)
712
+ pi -p "List all .ts files in src/"
713
+
714
+ # JSON mode - stream all agent events (non-interactive)
683
715
  pi --mode json "List all .ts files in src/"
684
716
 
685
717
  # RPC mode - headless operation (see test/rpc-example.ts)
@@ -697,6 +729,13 @@ pi --provider openai --model gpt-4o "Help me refactor this code"
697
729
  # Limit model cycling to specific models
698
730
  pi --models claude-sonnet,claude-haiku,gpt-4o
699
731
  # Now Ctrl+P cycles only through those models
732
+
733
+ # Model cycling with thinking levels
734
+ pi --models sonnet:high,haiku:low
735
+ # Starts with sonnet at high thinking, Ctrl+P switches to haiku at low thinking
736
+
737
+ # Start with specific thinking level
738
+ pi --thinking high "Solve this complex algorithm problem"
700
739
  ```
701
740
 
702
741
  ## Tools
@@ -1,4 +1,4 @@
1
- import type { AgentState } from "@mariozechner/pi-agent";
1
+ import type { AgentState } from "@mariozechner/pi-agent-core";
2
2
  import type { SessionManager } from "./session-manager.js";
3
3
  /**
4
4
  * Export session to a self-contained HTML file matching TUI visual style
@@ -1 +1 @@
1
- {"version":3,"file":"export-html.d.ts","sourceRoot":"","sources":["../src/export-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAMzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA0S3D;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA0ZlH","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message, ToolResultMessage, UserMessage } from \"@mariozechner/pi-ai\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { SessionManager } from \"./session-manager.js\";\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst VERSION = packageJson.version;\n\n/**\n * TUI Color scheme (matching exact RGB values from TUI components)\n */\nconst COLORS = {\n\t// Backgrounds\n\tuserMessageBg: \"rgb(52, 53, 65)\", // Dark slate\n\ttoolPendingBg: \"rgb(40, 40, 50)\", // Dark blue-gray\n\ttoolSuccessBg: \"rgb(40, 50, 40)\", // Dark green\n\ttoolErrorBg: \"rgb(60, 40, 40)\", // Dark red\n\tbodyBg: \"rgb(24, 24, 30)\", // Very dark background\n\tcontainerBg: \"rgb(30, 30, 36)\", // Slightly lighter container\n\n\t// Text colors (matching chalk colors)\n\ttext: \"rgb(229, 229, 231)\", // Light gray (close to white)\n\ttextDim: \"rgb(161, 161, 170)\", // Dimmed gray\n\tcyan: \"rgb(103, 232, 249)\", // Cyan for paths\n\tgreen: \"rgb(34, 197, 94)\", // Green for success\n\tred: \"rgb(239, 68, 68)\", // Red for errors\n\tyellow: \"rgb(234, 179, 8)\", // Yellow for warnings\n\titalic: \"rgb(161, 161, 170)\", // Gray italic for thinking\n};\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\n/**\n * Shorten path with tilde notation\n */\nfunction shortenPath(path: string): string {\n\tconst home = homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with 3 spaces\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Format tool execution matching TUI ToolExecutionComponent\n */\nfunction formatToolExecution(\n\ttoolName: string,\n\targs: any,\n\tresult?: ToolResultMessage,\n): { html: string; bgColor: string } {\n\tlet html = \"\";\n\tconst isError = result?.isError || false;\n\tconst bgColor = result ? (isError ? COLORS.toolErrorBg : COLORS.toolSuccessBg) : COLORS.toolPendingBg;\n\n\t// Get text output from result\n\tconst getTextOutput = (): string => {\n\t\tif (!result) return \"\";\n\t\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c: any) => c.text).join(\"\\n\");\n\t};\n\n\t// Format based on tool type (matching TUI logic exactly)\n\tif (toolName === \"bash\") {\n\t\tconst command = args?.command || \"\";\n\t\thtml = `<div class=\"tool-command\">$ ${escapeHtml(command || \"...\")}</div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 5;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t// Truncated output - make it expandable\n\t\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t} else {\n\t\t\t\t\t// Short output - show all\n\t\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"read\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">read</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tconst lines = output.split(\"\\n\");\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"write\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\tconst fileContent = args?.content || \"\";\n\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\tconst totalLines = lines.length;\n\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">write</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span>`;\n\t\tif (totalLines > 10) {\n\t\t\thtml += ` <span class=\"line-count\">(${totalLines} lines)</span>`;\n\t\t}\n\t\thtml += \"</div>\";\n\n\t\tif (fileContent) {\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"edit\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">edit</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\t// Show diff if available from result.details.diff\n\t\tif (result?.details?.diff) {\n\t\t\tconst diffLines = result.details.diff.split(\"\\n\");\n\t\t\thtml += '<div class=\"tool-diff\">';\n\t\t\tfor (const line of diffLines) {\n\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-new\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-old\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else {\n\t\t\t\t\thtml += `<div class=\"diff-line-context\">${escapeHtml(line)}</div>`;\n\t\t\t\t}\n\t\t\t}\n\t\t\thtml += \"</div>\";\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Generic tool\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">${escapeHtml(toolName)}</span></div>`;\n\t\thtml += `<div class=\"tool-output\"><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { html, bgColor };\n}\n\n/**\n * Format a message as HTML (matching TUI component styling)\n */\nfunction formatMessage(message: Message, toolResultsMap: Map<string, ToolResultMessage>): string {\n\tlet html = \"\";\n\n\tif (message.role === \"user\") {\n\t\tconst userMsg = message as UserMessage;\n\t\tlet textContent = \"\";\n\n\t\tif (typeof userMsg.content === \"string\") {\n\t\t\ttextContent = userMsg.content;\n\t\t} else {\n\t\t\tconst textBlocks = userMsg.content.filter((c) => c.type === \"text\");\n\t\t\ttextContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t}\n\n\t\tif (textContent.trim()) {\n\t\t\thtml += `<div class=\"user-message\">${escapeHtml(textContent).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t}\n\t} else if (message.role === \"assistant\") {\n\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t// Render text and thinking content\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\thtml += `<div class=\"assistant-text\">${escapeHtml(content.text.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\thtml += `<div class=\"thinking-text\">${escapeHtml(content.thinking.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Render tool calls with their results\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\tconst toolResult = toolResultsMap.get(content.id);\n\t\t\t\tconst { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult);\n\t\t\t\thtml += `<div class=\"tool-execution\" style=\"background-color: ${bgColor}\">${toolHtml}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Show error/abort status if no tool calls\n\t\tconst hasToolCalls = assistantMsg.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (assistantMsg.stopReason === \"aborted\") {\n\t\t\t\thtml += '<div class=\"error-text\">Aborted</div>';\n\t\t\t} else if (assistantMsg.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = assistantMsg.errorMessage || \"Unknown error\";\n\t\t\t\thtml += `<div class=\"error-text\">Error: ${escapeHtml(errorMsg)}</div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn html;\n}\n\n/**\n * Export session to a self-contained HTML file matching TUI visual style\n */\nexport function exportSessionToHtml(sessionManager: SessionManager, state: AgentState, outputPath?: string): string {\n\tconst sessionFile = sessionManager.getSessionFile();\n\tconst timestamp = new Date().toISOString();\n\n\t// Use session filename + .html if no output path provided\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${sessionBasename}.html`;\n\t}\n\n\t// Read and parse session data\n\tconst sessionContent = readFileSync(sessionFile, \"utf8\");\n\tconst lines = sessionContent.trim().split(\"\\n\");\n\n\tlet sessionHeader: any = null;\n\tconst messages: Message[] = [];\n\tconst toolResultsMap = new Map<string, ToolResultMessage>();\n\n\tfor (const line of lines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line);\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\tsessionHeader = entry;\n\t\t\t} else if (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t\t// Build map of tool call ID to result\n\t\t\t\tif (entry.message.role === \"toolResult\") {\n\t\t\t\t\ttoolResultsMap.set(entry.message.toolCallId, entry.message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\t// Calculate message stats (matching session command)\n\tconst userMessages = messages.filter((m) => m.role === \"user\").length;\n\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\").length;\n\tconst toolResultMessages = messages.filter((m) => m.role === \"toolResult\").length;\n\tconst totalMessages = messages.length;\n\n\t// Count tool calls from assistant messages\n\tlet toolCallsCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\ttoolCallsCount += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t}\n\n\t// Generate messages HTML\n\tlet messagesHtml = \"\";\n\tfor (const message of messages) {\n\t\tif (message.role !== \"toolResult\") {\n\t\t\t// Skip toolResult messages as they're rendered with their tool calls\n\t\t\tmessagesHtml += formatMessage(message, toolResultsMap);\n\t\t}\n\t}\n\n\t// Generate HTML (matching TUI aesthetic)\n\tconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Session Export - ${basename(sessionFile)}</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.6;\n color: ${COLORS.text};\n background: ${COLORS.bodyBg};\n padding: 24px;\n }\n\n .container {\n max-width: 700px;\n margin: 0 auto;\n }\n\n .header {\n margin-bottom: 24px;\n padding: 16px;\n background: ${COLORS.containerBg};\n border-radius: 4px;\n }\n\n .header h1 {\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 12px;\n color: ${COLORS.cyan};\n }\n\n .header-info {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 13px;\n }\n\n .info-item {\n color: ${COLORS.textDim};\n display: flex;\n align-items: baseline;\n }\n\n .info-label {\n font-weight: 600;\n margin-right: 8px;\n min-width: 80px;\n }\n\n .info-value {\n color: ${COLORS.text};\n flex: 1;\n }\n\n .messages {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n /* User message - matching TUI UserMessageComponent */\n .user-message {\n background: ${COLORS.userMessageBg};\n padding: 12px 16px;\n border-radius: 4px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Assistant text - matching TUI AssistantMessageComponent */\n .assistant-text {\n padding: 12px 16px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Thinking text - gray italic */\n .thinking-text {\n padding: 12px 16px;\n color: ${COLORS.italic};\n font-style: italic;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Tool execution - matching TUI ToolExecutionComponent */\n .tool-execution {\n padding: 12px 16px;\n border-radius: 4px;\n margin-top: 8px;\n }\n\n .tool-header {\n font-weight: bold;\n }\n\n .tool-name {\n font-weight: bold;\n }\n\n .tool-path {\n color: ${COLORS.cyan};\n }\n\n .line-count {\n color: ${COLORS.textDim};\n }\n\n .tool-command {\n font-weight: bold;\n }\n\n .tool-output {\n margin-top: 12px;\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n font-family: inherit;\n }\n\n .tool-output > div {\n line-height: 1.4;\n }\n\n .tool-output pre {\n margin: 0;\n font-family: inherit;\n color: inherit;\n }\n\n /* Expandable tool output */\n .tool-output.expandable {\n cursor: pointer;\n }\n\n .tool-output.expandable:hover {\n opacity: 0.9;\n }\n\n .tool-output.expandable .output-full {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-preview {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-full {\n display: block;\n }\n\n .expand-hint {\n color: ${COLORS.cyan};\n font-style: italic;\n margin-top: 4px;\n }\n\n /* System prompt section */\n .system-prompt {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .system-prompt-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .system-prompt-content {\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n font-size: 13px;\n }\n\n .tools-list {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .tools-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .tools-content {\n color: ${COLORS.textDim};\n font-size: 13px;\n }\n\n .tool-item {\n margin: 4px 0;\n }\n\n .tool-item-name {\n font-weight: bold;\n color: ${COLORS.text};\n }\n\n /* Diff styling */\n .tool-diff {\n margin-top: 12px;\n font-size: 13px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n overflow-x: auto;\n max-width: 100%;\n }\n\n .diff-line-old {\n color: ${COLORS.red};\n white-space: pre;\n }\n\n .diff-line-new {\n color: ${COLORS.green};\n white-space: pre;\n }\n\n .diff-line-context {\n color: ${COLORS.textDim};\n white-space: pre;\n }\n\n /* Error text */\n .error-text {\n color: ${COLORS.red};\n padding: 12px 16px;\n }\n\n .footer {\n margin-top: 48px;\n padding: 20px;\n text-align: center;\n color: ${COLORS.textDim};\n font-size: 12px;\n }\n\n @media print {\n body {\n background: white;\n color: black;\n }\n .tool-execution {\n border: 1px solid #ddd;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>pi v${VERSION}</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">Session:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.id || \"unknown\")}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Date:</span>\n <span class=\"info-value\">${sessionHeader?.timestamp ? new Date(sessionHeader.timestamp).toLocaleString() : timestamp}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Model:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.model || state.model.id)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"header\">\n <h1>Messages</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">User:</span>\n <span class=\"info-value\">${userMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Assistant:</span>\n <span class=\"info-value\">${assistantMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Calls:</span>\n <span class=\"info-value\">${toolCallsCount}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Results:</span>\n <span class=\"info-value\">${toolResultMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total:</span>\n <span class=\"info-value\">${totalMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Directory:</span>\n <span class=\"info-value\">${escapeHtml(shortenPath(sessionHeader?.cwd || process.cwd()))}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Thinking:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.thinkingLevel || state.thinkingLevel)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"system-prompt\">\n <div class=\"system-prompt-header\">System Prompt</div>\n <div class=\"system-prompt-content\">${escapeHtml(sessionHeader?.systemPrompt || state.systemPrompt)}</div>\n </div>\n\n <div class=\"tools-list\">\n <div class=\"tools-header\">Available Tools</div>\n <div class=\"tools-content\">\n ${state.tools\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t(tool) =>\n\t\t\t\t\t\t\t\t\t`<div class=\"tool-item\"><span class=\"tool-item-name\">${escapeHtml(tool.name)}</span> - ${escapeHtml(tool.description)}</div>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join(\"\")}\n </div>\n </div>\n\n <div class=\"messages\">\n ${messagesHtml}\n </div>\n\n <div class=\"footer\">\n Generated by pi coding-agent on ${new Date().toLocaleString()}\n </div>\n </div>\n</body>\n</html>`;\n\n\t// Write HTML file\n\twriteFileSync(outputPath, html, \"utf8\");\n\n\treturn outputPath;\n}\n"]}
1
+ {"version":3,"file":"export-html.d.ts","sourceRoot":"","sources":["../src/export-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAM9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA0S3D;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA0ZlH","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage, Message, ToolResultMessage, UserMessage } from \"@mariozechner/pi-ai\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { SessionManager } from \"./session-manager.js\";\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst VERSION = packageJson.version;\n\n/**\n * TUI Color scheme (matching exact RGB values from TUI components)\n */\nconst COLORS = {\n\t// Backgrounds\n\tuserMessageBg: \"rgb(52, 53, 65)\", // Dark slate\n\ttoolPendingBg: \"rgb(40, 40, 50)\", // Dark blue-gray\n\ttoolSuccessBg: \"rgb(40, 50, 40)\", // Dark green\n\ttoolErrorBg: \"rgb(60, 40, 40)\", // Dark red\n\tbodyBg: \"rgb(24, 24, 30)\", // Very dark background\n\tcontainerBg: \"rgb(30, 30, 36)\", // Slightly lighter container\n\n\t// Text colors (matching chalk colors)\n\ttext: \"rgb(229, 229, 231)\", // Light gray (close to white)\n\ttextDim: \"rgb(161, 161, 170)\", // Dimmed gray\n\tcyan: \"rgb(103, 232, 249)\", // Cyan for paths\n\tgreen: \"rgb(34, 197, 94)\", // Green for success\n\tred: \"rgb(239, 68, 68)\", // Red for errors\n\tyellow: \"rgb(234, 179, 8)\", // Yellow for warnings\n\titalic: \"rgb(161, 161, 170)\", // Gray italic for thinking\n};\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\n/**\n * Shorten path with tilde notation\n */\nfunction shortenPath(path: string): string {\n\tconst home = homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with 3 spaces\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Format tool execution matching TUI ToolExecutionComponent\n */\nfunction formatToolExecution(\n\ttoolName: string,\n\targs: any,\n\tresult?: ToolResultMessage,\n): { html: string; bgColor: string } {\n\tlet html = \"\";\n\tconst isError = result?.isError || false;\n\tconst bgColor = result ? (isError ? COLORS.toolErrorBg : COLORS.toolSuccessBg) : COLORS.toolPendingBg;\n\n\t// Get text output from result\n\tconst getTextOutput = (): string => {\n\t\tif (!result) return \"\";\n\t\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c: any) => c.text).join(\"\\n\");\n\t};\n\n\t// Format based on tool type (matching TUI logic exactly)\n\tif (toolName === \"bash\") {\n\t\tconst command = args?.command || \"\";\n\t\thtml = `<div class=\"tool-command\">$ ${escapeHtml(command || \"...\")}</div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 5;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t// Truncated output - make it expandable\n\t\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t} else {\n\t\t\t\t\t// Short output - show all\n\t\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"read\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">read</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tconst lines = output.split(\"\\n\");\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"write\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\tconst fileContent = args?.content || \"\";\n\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\tconst totalLines = lines.length;\n\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">write</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span>`;\n\t\tif (totalLines > 10) {\n\t\t\thtml += ` <span class=\"line-count\">(${totalLines} lines)</span>`;\n\t\t}\n\t\thtml += \"</div>\";\n\n\t\tif (fileContent) {\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"edit\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">edit</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\t// Show diff if available from result.details.diff\n\t\tif (result?.details?.diff) {\n\t\t\tconst diffLines = result.details.diff.split(\"\\n\");\n\t\t\thtml += '<div class=\"tool-diff\">';\n\t\t\tfor (const line of diffLines) {\n\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-new\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-old\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else {\n\t\t\t\t\thtml += `<div class=\"diff-line-context\">${escapeHtml(line)}</div>`;\n\t\t\t\t}\n\t\t\t}\n\t\t\thtml += \"</div>\";\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Generic tool\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">${escapeHtml(toolName)}</span></div>`;\n\t\thtml += `<div class=\"tool-output\"><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { html, bgColor };\n}\n\n/**\n * Format a message as HTML (matching TUI component styling)\n */\nfunction formatMessage(message: Message, toolResultsMap: Map<string, ToolResultMessage>): string {\n\tlet html = \"\";\n\n\tif (message.role === \"user\") {\n\t\tconst userMsg = message as UserMessage;\n\t\tlet textContent = \"\";\n\n\t\tif (typeof userMsg.content === \"string\") {\n\t\t\ttextContent = userMsg.content;\n\t\t} else {\n\t\t\tconst textBlocks = userMsg.content.filter((c) => c.type === \"text\");\n\t\t\ttextContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t}\n\n\t\tif (textContent.trim()) {\n\t\t\thtml += `<div class=\"user-message\">${escapeHtml(textContent).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t}\n\t} else if (message.role === \"assistant\") {\n\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t// Render text and thinking content\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\thtml += `<div class=\"assistant-text\">${escapeHtml(content.text.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\thtml += `<div class=\"thinking-text\">${escapeHtml(content.thinking.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Render tool calls with their results\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\tconst toolResult = toolResultsMap.get(content.id);\n\t\t\t\tconst { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult);\n\t\t\t\thtml += `<div class=\"tool-execution\" style=\"background-color: ${bgColor}\">${toolHtml}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Show error/abort status if no tool calls\n\t\tconst hasToolCalls = assistantMsg.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (assistantMsg.stopReason === \"aborted\") {\n\t\t\t\thtml += '<div class=\"error-text\">Aborted</div>';\n\t\t\t} else if (assistantMsg.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = assistantMsg.errorMessage || \"Unknown error\";\n\t\t\t\thtml += `<div class=\"error-text\">Error: ${escapeHtml(errorMsg)}</div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn html;\n}\n\n/**\n * Export session to a self-contained HTML file matching TUI visual style\n */\nexport function exportSessionToHtml(sessionManager: SessionManager, state: AgentState, outputPath?: string): string {\n\tconst sessionFile = sessionManager.getSessionFile();\n\tconst timestamp = new Date().toISOString();\n\n\t// Use session filename + .html if no output path provided\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${sessionBasename}.html`;\n\t}\n\n\t// Read and parse session data\n\tconst sessionContent = readFileSync(sessionFile, \"utf8\");\n\tconst lines = sessionContent.trim().split(\"\\n\");\n\n\tlet sessionHeader: any = null;\n\tconst messages: Message[] = [];\n\tconst toolResultsMap = new Map<string, ToolResultMessage>();\n\n\tfor (const line of lines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line);\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\tsessionHeader = entry;\n\t\t\t} else if (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t\t// Build map of tool call ID to result\n\t\t\t\tif (entry.message.role === \"toolResult\") {\n\t\t\t\t\ttoolResultsMap.set(entry.message.toolCallId, entry.message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\t// Calculate message stats (matching session command)\n\tconst userMessages = messages.filter((m) => m.role === \"user\").length;\n\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\").length;\n\tconst toolResultMessages = messages.filter((m) => m.role === \"toolResult\").length;\n\tconst totalMessages = messages.length;\n\n\t// Count tool calls from assistant messages\n\tlet toolCallsCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\ttoolCallsCount += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t}\n\n\t// Generate messages HTML\n\tlet messagesHtml = \"\";\n\tfor (const message of messages) {\n\t\tif (message.role !== \"toolResult\") {\n\t\t\t// Skip toolResult messages as they're rendered with their tool calls\n\t\t\tmessagesHtml += formatMessage(message, toolResultsMap);\n\t\t}\n\t}\n\n\t// Generate HTML (matching TUI aesthetic)\n\tconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Session Export - ${basename(sessionFile)}</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.6;\n color: ${COLORS.text};\n background: ${COLORS.bodyBg};\n padding: 24px;\n }\n\n .container {\n max-width: 700px;\n margin: 0 auto;\n }\n\n .header {\n margin-bottom: 24px;\n padding: 16px;\n background: ${COLORS.containerBg};\n border-radius: 4px;\n }\n\n .header h1 {\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 12px;\n color: ${COLORS.cyan};\n }\n\n .header-info {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 13px;\n }\n\n .info-item {\n color: ${COLORS.textDim};\n display: flex;\n align-items: baseline;\n }\n\n .info-label {\n font-weight: 600;\n margin-right: 8px;\n min-width: 80px;\n }\n\n .info-value {\n color: ${COLORS.text};\n flex: 1;\n }\n\n .messages {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n /* User message - matching TUI UserMessageComponent */\n .user-message {\n background: ${COLORS.userMessageBg};\n padding: 12px 16px;\n border-radius: 4px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Assistant text - matching TUI AssistantMessageComponent */\n .assistant-text {\n padding: 12px 16px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Thinking text - gray italic */\n .thinking-text {\n padding: 12px 16px;\n color: ${COLORS.italic};\n font-style: italic;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Tool execution - matching TUI ToolExecutionComponent */\n .tool-execution {\n padding: 12px 16px;\n border-radius: 4px;\n margin-top: 8px;\n }\n\n .tool-header {\n font-weight: bold;\n }\n\n .tool-name {\n font-weight: bold;\n }\n\n .tool-path {\n color: ${COLORS.cyan};\n }\n\n .line-count {\n color: ${COLORS.textDim};\n }\n\n .tool-command {\n font-weight: bold;\n }\n\n .tool-output {\n margin-top: 12px;\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n font-family: inherit;\n }\n\n .tool-output > div {\n line-height: 1.4;\n }\n\n .tool-output pre {\n margin: 0;\n font-family: inherit;\n color: inherit;\n }\n\n /* Expandable tool output */\n .tool-output.expandable {\n cursor: pointer;\n }\n\n .tool-output.expandable:hover {\n opacity: 0.9;\n }\n\n .tool-output.expandable .output-full {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-preview {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-full {\n display: block;\n }\n\n .expand-hint {\n color: ${COLORS.cyan};\n font-style: italic;\n margin-top: 4px;\n }\n\n /* System prompt section */\n .system-prompt {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .system-prompt-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .system-prompt-content {\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n font-size: 13px;\n }\n\n .tools-list {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .tools-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .tools-content {\n color: ${COLORS.textDim};\n font-size: 13px;\n }\n\n .tool-item {\n margin: 4px 0;\n }\n\n .tool-item-name {\n font-weight: bold;\n color: ${COLORS.text};\n }\n\n /* Diff styling */\n .tool-diff {\n margin-top: 12px;\n font-size: 13px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n overflow-x: auto;\n max-width: 100%;\n }\n\n .diff-line-old {\n color: ${COLORS.red};\n white-space: pre;\n }\n\n .diff-line-new {\n color: ${COLORS.green};\n white-space: pre;\n }\n\n .diff-line-context {\n color: ${COLORS.textDim};\n white-space: pre;\n }\n\n /* Error text */\n .error-text {\n color: ${COLORS.red};\n padding: 12px 16px;\n }\n\n .footer {\n margin-top: 48px;\n padding: 20px;\n text-align: center;\n color: ${COLORS.textDim};\n font-size: 12px;\n }\n\n @media print {\n body {\n background: white;\n color: black;\n }\n .tool-execution {\n border: 1px solid #ddd;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>pi v${VERSION}</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">Session:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.id || \"unknown\")}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Date:</span>\n <span class=\"info-value\">${sessionHeader?.timestamp ? new Date(sessionHeader.timestamp).toLocaleString() : timestamp}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Model:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.model || state.model.id)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"header\">\n <h1>Messages</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">User:</span>\n <span class=\"info-value\">${userMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Assistant:</span>\n <span class=\"info-value\">${assistantMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Calls:</span>\n <span class=\"info-value\">${toolCallsCount}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Results:</span>\n <span class=\"info-value\">${toolResultMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total:</span>\n <span class=\"info-value\">${totalMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Directory:</span>\n <span class=\"info-value\">${escapeHtml(shortenPath(sessionHeader?.cwd || process.cwd()))}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Thinking:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.thinkingLevel || state.thinkingLevel)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"system-prompt\">\n <div class=\"system-prompt-header\">System Prompt</div>\n <div class=\"system-prompt-content\">${escapeHtml(sessionHeader?.systemPrompt || state.systemPrompt)}</div>\n </div>\n\n <div class=\"tools-list\">\n <div class=\"tools-header\">Available Tools</div>\n <div class=\"tools-content\">\n ${state.tools\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t(tool) =>\n\t\t\t\t\t\t\t\t\t`<div class=\"tool-item\"><span class=\"tool-item-name\">${escapeHtml(tool.name)}</span> - ${escapeHtml(tool.description)}</div>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join(\"\")}\n </div>\n </div>\n\n <div class=\"messages\">\n ${messagesHtml}\n </div>\n\n <div class=\"footer\">\n Generated by pi coding-agent on ${new Date().toLocaleString()}\n </div>\n </div>\n</body>\n</html>`;\n\n\t// Write HTML file\n\twriteFileSync(outputPath, html, \"utf8\");\n\n\treturn outputPath;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"export-html.js","sourceRoot":"","sources":["../src/export-html.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent\";\nimport type { AssistantMessage, Message, ToolResultMessage, UserMessage } from \"@mariozechner/pi-ai\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { SessionManager } from \"./session-manager.js\";\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst VERSION = packageJson.version;\n\n/**\n * TUI Color scheme (matching exact RGB values from TUI components)\n */\nconst COLORS = {\n\t// Backgrounds\n\tuserMessageBg: \"rgb(52, 53, 65)\", // Dark slate\n\ttoolPendingBg: \"rgb(40, 40, 50)\", // Dark blue-gray\n\ttoolSuccessBg: \"rgb(40, 50, 40)\", // Dark green\n\ttoolErrorBg: \"rgb(60, 40, 40)\", // Dark red\n\tbodyBg: \"rgb(24, 24, 30)\", // Very dark background\n\tcontainerBg: \"rgb(30, 30, 36)\", // Slightly lighter container\n\n\t// Text colors (matching chalk colors)\n\ttext: \"rgb(229, 229, 231)\", // Light gray (close to white)\n\ttextDim: \"rgb(161, 161, 170)\", // Dimmed gray\n\tcyan: \"rgb(103, 232, 249)\", // Cyan for paths\n\tgreen: \"rgb(34, 197, 94)\", // Green for success\n\tred: \"rgb(239, 68, 68)\", // Red for errors\n\tyellow: \"rgb(234, 179, 8)\", // Yellow for warnings\n\titalic: \"rgb(161, 161, 170)\", // Gray italic for thinking\n};\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\n/**\n * Shorten path with tilde notation\n */\nfunction shortenPath(path: string): string {\n\tconst home = homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with 3 spaces\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Format tool execution matching TUI ToolExecutionComponent\n */\nfunction formatToolExecution(\n\ttoolName: string,\n\targs: any,\n\tresult?: ToolResultMessage,\n): { html: string; bgColor: string } {\n\tlet html = \"\";\n\tconst isError = result?.isError || false;\n\tconst bgColor = result ? (isError ? COLORS.toolErrorBg : COLORS.toolSuccessBg) : COLORS.toolPendingBg;\n\n\t// Get text output from result\n\tconst getTextOutput = (): string => {\n\t\tif (!result) return \"\";\n\t\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c: any) => c.text).join(\"\\n\");\n\t};\n\n\t// Format based on tool type (matching TUI logic exactly)\n\tif (toolName === \"bash\") {\n\t\tconst command = args?.command || \"\";\n\t\thtml = `<div class=\"tool-command\">$ ${escapeHtml(command || \"...\")}</div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 5;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t// Truncated output - make it expandable\n\t\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t} else {\n\t\t\t\t\t// Short output - show all\n\t\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"read\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">read</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tconst lines = output.split(\"\\n\");\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"write\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\tconst fileContent = args?.content || \"\";\n\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\tconst totalLines = lines.length;\n\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">write</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span>`;\n\t\tif (totalLines > 10) {\n\t\t\thtml += ` <span class=\"line-count\">(${totalLines} lines)</span>`;\n\t\t}\n\t\thtml += \"</div>\";\n\n\t\tif (fileContent) {\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"edit\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">edit</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\t// Show diff if available from result.details.diff\n\t\tif (result?.details?.diff) {\n\t\t\tconst diffLines = result.details.diff.split(\"\\n\");\n\t\t\thtml += '<div class=\"tool-diff\">';\n\t\t\tfor (const line of diffLines) {\n\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-new\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-old\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else {\n\t\t\t\t\thtml += `<div class=\"diff-line-context\">${escapeHtml(line)}</div>`;\n\t\t\t\t}\n\t\t\t}\n\t\t\thtml += \"</div>\";\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Generic tool\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">${escapeHtml(toolName)}</span></div>`;\n\t\thtml += `<div class=\"tool-output\"><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { html, bgColor };\n}\n\n/**\n * Format a message as HTML (matching TUI component styling)\n */\nfunction formatMessage(message: Message, toolResultsMap: Map<string, ToolResultMessage>): string {\n\tlet html = \"\";\n\n\tif (message.role === \"user\") {\n\t\tconst userMsg = message as UserMessage;\n\t\tlet textContent = \"\";\n\n\t\tif (typeof userMsg.content === \"string\") {\n\t\t\ttextContent = userMsg.content;\n\t\t} else {\n\t\t\tconst textBlocks = userMsg.content.filter((c) => c.type === \"text\");\n\t\t\ttextContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t}\n\n\t\tif (textContent.trim()) {\n\t\t\thtml += `<div class=\"user-message\">${escapeHtml(textContent).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t}\n\t} else if (message.role === \"assistant\") {\n\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t// Render text and thinking content\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\thtml += `<div class=\"assistant-text\">${escapeHtml(content.text.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\thtml += `<div class=\"thinking-text\">${escapeHtml(content.thinking.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Render tool calls with their results\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\tconst toolResult = toolResultsMap.get(content.id);\n\t\t\t\tconst { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult);\n\t\t\t\thtml += `<div class=\"tool-execution\" style=\"background-color: ${bgColor}\">${toolHtml}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Show error/abort status if no tool calls\n\t\tconst hasToolCalls = assistantMsg.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (assistantMsg.stopReason === \"aborted\") {\n\t\t\t\thtml += '<div class=\"error-text\">Aborted</div>';\n\t\t\t} else if (assistantMsg.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = assistantMsg.errorMessage || \"Unknown error\";\n\t\t\t\thtml += `<div class=\"error-text\">Error: ${escapeHtml(errorMsg)}</div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn html;\n}\n\n/**\n * Export session to a self-contained HTML file matching TUI visual style\n */\nexport function exportSessionToHtml(sessionManager: SessionManager, state: AgentState, outputPath?: string): string {\n\tconst sessionFile = sessionManager.getSessionFile();\n\tconst timestamp = new Date().toISOString();\n\n\t// Use session filename + .html if no output path provided\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${sessionBasename}.html`;\n\t}\n\n\t// Read and parse session data\n\tconst sessionContent = readFileSync(sessionFile, \"utf8\");\n\tconst lines = sessionContent.trim().split(\"\\n\");\n\n\tlet sessionHeader: any = null;\n\tconst messages: Message[] = [];\n\tconst toolResultsMap = new Map<string, ToolResultMessage>();\n\n\tfor (const line of lines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line);\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\tsessionHeader = entry;\n\t\t\t} else if (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t\t// Build map of tool call ID to result\n\t\t\t\tif (entry.message.role === \"toolResult\") {\n\t\t\t\t\ttoolResultsMap.set(entry.message.toolCallId, entry.message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\t// Calculate message stats (matching session command)\n\tconst userMessages = messages.filter((m) => m.role === \"user\").length;\n\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\").length;\n\tconst toolResultMessages = messages.filter((m) => m.role === \"toolResult\").length;\n\tconst totalMessages = messages.length;\n\n\t// Count tool calls from assistant messages\n\tlet toolCallsCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\ttoolCallsCount += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t}\n\n\t// Generate messages HTML\n\tlet messagesHtml = \"\";\n\tfor (const message of messages) {\n\t\tif (message.role !== \"toolResult\") {\n\t\t\t// Skip toolResult messages as they're rendered with their tool calls\n\t\t\tmessagesHtml += formatMessage(message, toolResultsMap);\n\t\t}\n\t}\n\n\t// Generate HTML (matching TUI aesthetic)\n\tconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Session Export - ${basename(sessionFile)}</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.6;\n color: ${COLORS.text};\n background: ${COLORS.bodyBg};\n padding: 24px;\n }\n\n .container {\n max-width: 700px;\n margin: 0 auto;\n }\n\n .header {\n margin-bottom: 24px;\n padding: 16px;\n background: ${COLORS.containerBg};\n border-radius: 4px;\n }\n\n .header h1 {\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 12px;\n color: ${COLORS.cyan};\n }\n\n .header-info {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 13px;\n }\n\n .info-item {\n color: ${COLORS.textDim};\n display: flex;\n align-items: baseline;\n }\n\n .info-label {\n font-weight: 600;\n margin-right: 8px;\n min-width: 80px;\n }\n\n .info-value {\n color: ${COLORS.text};\n flex: 1;\n }\n\n .messages {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n /* User message - matching TUI UserMessageComponent */\n .user-message {\n background: ${COLORS.userMessageBg};\n padding: 12px 16px;\n border-radius: 4px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Assistant text - matching TUI AssistantMessageComponent */\n .assistant-text {\n padding: 12px 16px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Thinking text - gray italic */\n .thinking-text {\n padding: 12px 16px;\n color: ${COLORS.italic};\n font-style: italic;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Tool execution - matching TUI ToolExecutionComponent */\n .tool-execution {\n padding: 12px 16px;\n border-radius: 4px;\n margin-top: 8px;\n }\n\n .tool-header {\n font-weight: bold;\n }\n\n .tool-name {\n font-weight: bold;\n }\n\n .tool-path {\n color: ${COLORS.cyan};\n }\n\n .line-count {\n color: ${COLORS.textDim};\n }\n\n .tool-command {\n font-weight: bold;\n }\n\n .tool-output {\n margin-top: 12px;\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n font-family: inherit;\n }\n\n .tool-output > div {\n line-height: 1.4;\n }\n\n .tool-output pre {\n margin: 0;\n font-family: inherit;\n color: inherit;\n }\n\n /* Expandable tool output */\n .tool-output.expandable {\n cursor: pointer;\n }\n\n .tool-output.expandable:hover {\n opacity: 0.9;\n }\n\n .tool-output.expandable .output-full {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-preview {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-full {\n display: block;\n }\n\n .expand-hint {\n color: ${COLORS.cyan};\n font-style: italic;\n margin-top: 4px;\n }\n\n /* System prompt section */\n .system-prompt {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .system-prompt-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .system-prompt-content {\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n font-size: 13px;\n }\n\n .tools-list {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .tools-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .tools-content {\n color: ${COLORS.textDim};\n font-size: 13px;\n }\n\n .tool-item {\n margin: 4px 0;\n }\n\n .tool-item-name {\n font-weight: bold;\n color: ${COLORS.text};\n }\n\n /* Diff styling */\n .tool-diff {\n margin-top: 12px;\n font-size: 13px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n overflow-x: auto;\n max-width: 100%;\n }\n\n .diff-line-old {\n color: ${COLORS.red};\n white-space: pre;\n }\n\n .diff-line-new {\n color: ${COLORS.green};\n white-space: pre;\n }\n\n .diff-line-context {\n color: ${COLORS.textDim};\n white-space: pre;\n }\n\n /* Error text */\n .error-text {\n color: ${COLORS.red};\n padding: 12px 16px;\n }\n\n .footer {\n margin-top: 48px;\n padding: 20px;\n text-align: center;\n color: ${COLORS.textDim};\n font-size: 12px;\n }\n\n @media print {\n body {\n background: white;\n color: black;\n }\n .tool-execution {\n border: 1px solid #ddd;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>pi v${VERSION}</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">Session:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.id || \"unknown\")}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Date:</span>\n <span class=\"info-value\">${sessionHeader?.timestamp ? new Date(sessionHeader.timestamp).toLocaleString() : timestamp}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Model:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.model || state.model.id)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"header\">\n <h1>Messages</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">User:</span>\n <span class=\"info-value\">${userMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Assistant:</span>\n <span class=\"info-value\">${assistantMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Calls:</span>\n <span class=\"info-value\">${toolCallsCount}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Results:</span>\n <span class=\"info-value\">${toolResultMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total:</span>\n <span class=\"info-value\">${totalMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Directory:</span>\n <span class=\"info-value\">${escapeHtml(shortenPath(sessionHeader?.cwd || process.cwd()))}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Thinking:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.thinkingLevel || state.thinkingLevel)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"system-prompt\">\n <div class=\"system-prompt-header\">System Prompt</div>\n <div class=\"system-prompt-content\">${escapeHtml(sessionHeader?.systemPrompt || state.systemPrompt)}</div>\n </div>\n\n <div class=\"tools-list\">\n <div class=\"tools-header\">Available Tools</div>\n <div class=\"tools-content\">\n ${state.tools\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t(tool) =>\n\t\t\t\t\t\t\t\t\t`<div class=\"tool-item\"><span class=\"tool-item-name\">${escapeHtml(tool.name)}</span> - ${escapeHtml(tool.description)}</div>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join(\"\")}\n </div>\n </div>\n\n <div class=\"messages\">\n ${messagesHtml}\n </div>\n\n <div class=\"footer\">\n Generated by pi coding-agent on ${new Date().toLocaleString()}\n </div>\n </div>\n</body>\n</html>`;\n\n\t// Write HTML file\n\twriteFileSync(outputPath, html, \"utf8\");\n\n\treturn outputPath;\n}\n"]}
1
+ {"version":3,"file":"export-html.js","sourceRoot":"","sources":["../src/export-html.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent-core\";\nimport type { AssistantMessage, Message, ToolResultMessage, UserMessage } from \"@mariozechner/pi-ai\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { SessionManager } from \"./session-manager.js\";\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../package.json\"), \"utf-8\"));\nconst VERSION = packageJson.version;\n\n/**\n * TUI Color scheme (matching exact RGB values from TUI components)\n */\nconst COLORS = {\n\t// Backgrounds\n\tuserMessageBg: \"rgb(52, 53, 65)\", // Dark slate\n\ttoolPendingBg: \"rgb(40, 40, 50)\", // Dark blue-gray\n\ttoolSuccessBg: \"rgb(40, 50, 40)\", // Dark green\n\ttoolErrorBg: \"rgb(60, 40, 40)\", // Dark red\n\tbodyBg: \"rgb(24, 24, 30)\", // Very dark background\n\tcontainerBg: \"rgb(30, 30, 36)\", // Slightly lighter container\n\n\t// Text colors (matching chalk colors)\n\ttext: \"rgb(229, 229, 231)\", // Light gray (close to white)\n\ttextDim: \"rgb(161, 161, 170)\", // Dimmed gray\n\tcyan: \"rgb(103, 232, 249)\", // Cyan for paths\n\tgreen: \"rgb(34, 197, 94)\", // Green for success\n\tred: \"rgb(239, 68, 68)\", // Red for errors\n\tyellow: \"rgb(234, 179, 8)\", // Yellow for warnings\n\titalic: \"rgb(161, 161, 170)\", // Gray italic for thinking\n};\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\");\n}\n\n/**\n * Shorten path with tilde notation\n */\nfunction shortenPath(path: string): string {\n\tconst home = homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with 3 spaces\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Format tool execution matching TUI ToolExecutionComponent\n */\nfunction formatToolExecution(\n\ttoolName: string,\n\targs: any,\n\tresult?: ToolResultMessage,\n): { html: string; bgColor: string } {\n\tlet html = \"\";\n\tconst isError = result?.isError || false;\n\tconst bgColor = result ? (isError ? COLORS.toolErrorBg : COLORS.toolSuccessBg) : COLORS.toolPendingBg;\n\n\t// Get text output from result\n\tconst getTextOutput = (): string => {\n\t\tif (!result) return \"\";\n\t\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\t\treturn textBlocks.map((c: any) => c.text).join(\"\\n\");\n\t};\n\n\t// Format based on tool type (matching TUI logic exactly)\n\tif (toolName === \"bash\") {\n\t\tconst command = args?.command || \"\";\n\t\thtml = `<div class=\"tool-command\">$ ${escapeHtml(command || \"...\")}</div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 5;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t// Truncated output - make it expandable\n\t\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t} else {\n\t\t\t\t\t// Short output - show all\n\t\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\t\thtml += `<div>${escapeHtml(line)}</div>`;\n\t\t\t\t\t}\n\t\t\t\t\thtml += \"</div>\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"read\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">read</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tconst lines = output.split(\"\\n\");\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"write\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\tconst fileContent = args?.content || \"\";\n\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\tconst totalLines = lines.length;\n\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">write</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span>`;\n\t\tif (totalLines > 10) {\n\t\t\thtml += ` <span class=\"line-count\">(${totalLines} lines)</span>`;\n\t\t}\n\t\thtml += \"</div>\";\n\n\t\tif (fileContent) {\n\t\t\tconst maxLines = 10;\n\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\tif (remaining > 0) {\n\t\t\t\t// Truncated output - make it expandable\n\t\t\t\thtml += '<div class=\"tool-output expandable\" onclick=\"this.classList.toggle(\\'expanded\\')\">';\n\t\t\t\thtml += '<div class=\"output-preview\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += `<div class=\"expand-hint\">... (${remaining} more lines) - click to expand</div>`;\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += '<div class=\"output-full\">';\n\t\t\t\tfor (const line of lines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t\thtml += \"</div>\";\n\t\t\t} else {\n\t\t\t\t// Short output - show all\n\t\t\t\thtml += '<div class=\"tool-output\">';\n\t\t\t\tfor (const line of displayLines) {\n\t\t\t\t\thtml += `<div>${escapeHtml(replaceTabs(line))}</div>`;\n\t\t\t\t}\n\t\t\t\thtml += \"</div>\";\n\t\t\t}\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else if (toolName === \"edit\") {\n\t\tconst path = shortenPath(args?.file_path || args?.path || \"\");\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">edit</span> <span class=\"tool-path\">${escapeHtml(path || \"...\")}</span></div>`;\n\n\t\t// Show diff if available from result.details.diff\n\t\tif (result?.details?.diff) {\n\t\t\tconst diffLines = result.details.diff.split(\"\\n\");\n\t\t\thtml += '<div class=\"tool-diff\">';\n\t\t\tfor (const line of diffLines) {\n\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-new\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\thtml += `<div class=\"diff-line-old\">${escapeHtml(line)}</div>`;\n\t\t\t\t} else {\n\t\t\t\t\thtml += `<div class=\"diff-line-context\">${escapeHtml(line)}</div>`;\n\t\t\t\t}\n\t\t\t}\n\t\t\thtml += \"</div>\";\n\t\t}\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput().trim();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Generic tool\n\t\thtml = `<div class=\"tool-header\"><span class=\"tool-name\">${escapeHtml(toolName)}</span></div>`;\n\t\thtml += `<div class=\"tool-output\"><pre>${escapeHtml(JSON.stringify(args, null, 2))}</pre></div>`;\n\n\t\tif (result) {\n\t\t\tconst output = getTextOutput();\n\t\t\tif (output) {\n\t\t\t\thtml += `<div class=\"tool-output\"><div>${escapeHtml(output)}</div></div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { html, bgColor };\n}\n\n/**\n * Format a message as HTML (matching TUI component styling)\n */\nfunction formatMessage(message: Message, toolResultsMap: Map<string, ToolResultMessage>): string {\n\tlet html = \"\";\n\n\tif (message.role === \"user\") {\n\t\tconst userMsg = message as UserMessage;\n\t\tlet textContent = \"\";\n\n\t\tif (typeof userMsg.content === \"string\") {\n\t\t\ttextContent = userMsg.content;\n\t\t} else {\n\t\t\tconst textBlocks = userMsg.content.filter((c) => c.type === \"text\");\n\t\t\ttextContent = textBlocks.map((c: any) => c.text).join(\"\");\n\t\t}\n\n\t\tif (textContent.trim()) {\n\t\t\thtml += `<div class=\"user-message\">${escapeHtml(textContent).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t}\n\t} else if (message.role === \"assistant\") {\n\t\tconst assistantMsg = message as AssistantMessage;\n\n\t\t// Render text and thinking content\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\thtml += `<div class=\"assistant-text\">${escapeHtml(content.text.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\thtml += `<div class=\"thinking-text\">${escapeHtml(content.thinking.trim()).replace(/\\n/g, \"<br>\")}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Render tool calls with their results\n\t\tfor (const content of assistantMsg.content) {\n\t\t\tif (content.type === \"toolCall\") {\n\t\t\t\tconst toolResult = toolResultsMap.get(content.id);\n\t\t\t\tconst { html: toolHtml, bgColor } = formatToolExecution(content.name, content.arguments, toolResult);\n\t\t\t\thtml += `<div class=\"tool-execution\" style=\"background-color: ${bgColor}\">${toolHtml}</div>`;\n\t\t\t}\n\t\t}\n\n\t\t// Show error/abort status if no tool calls\n\t\tconst hasToolCalls = assistantMsg.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (assistantMsg.stopReason === \"aborted\") {\n\t\t\t\thtml += '<div class=\"error-text\">Aborted</div>';\n\t\t\t} else if (assistantMsg.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = assistantMsg.errorMessage || \"Unknown error\";\n\t\t\t\thtml += `<div class=\"error-text\">Error: ${escapeHtml(errorMsg)}</div>`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn html;\n}\n\n/**\n * Export session to a self-contained HTML file matching TUI visual style\n */\nexport function exportSessionToHtml(sessionManager: SessionManager, state: AgentState, outputPath?: string): string {\n\tconst sessionFile = sessionManager.getSessionFile();\n\tconst timestamp = new Date().toISOString();\n\n\t// Use session filename + .html if no output path provided\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${sessionBasename}.html`;\n\t}\n\n\t// Read and parse session data\n\tconst sessionContent = readFileSync(sessionFile, \"utf8\");\n\tconst lines = sessionContent.trim().split(\"\\n\");\n\n\tlet sessionHeader: any = null;\n\tconst messages: Message[] = [];\n\tconst toolResultsMap = new Map<string, ToolResultMessage>();\n\n\tfor (const line of lines) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line);\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\tsessionHeader = entry;\n\t\t\t} else if (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t\t// Build map of tool call ID to result\n\t\t\t\tif (entry.message.role === \"toolResult\") {\n\t\t\t\t\ttoolResultsMap.set(entry.message.toolCallId, entry.message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\t// Calculate message stats (matching session command)\n\tconst userMessages = messages.filter((m) => m.role === \"user\").length;\n\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\").length;\n\tconst toolResultMessages = messages.filter((m) => m.role === \"toolResult\").length;\n\tconst totalMessages = messages.length;\n\n\t// Count tool calls from assistant messages\n\tlet toolCallsCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"assistant\") {\n\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\ttoolCallsCount += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t}\n\t}\n\n\t// Generate messages HTML\n\tlet messagesHtml = \"\";\n\tfor (const message of messages) {\n\t\tif (message.role !== \"toolResult\") {\n\t\t\t// Skip toolResult messages as they're rendered with their tool calls\n\t\t\tmessagesHtml += formatMessage(message, toolResultsMap);\n\t\t}\n\t}\n\n\t// Generate HTML (matching TUI aesthetic)\n\tconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Session Export - ${basename(sessionFile)}</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.6;\n color: ${COLORS.text};\n background: ${COLORS.bodyBg};\n padding: 24px;\n }\n\n .container {\n max-width: 700px;\n margin: 0 auto;\n }\n\n .header {\n margin-bottom: 24px;\n padding: 16px;\n background: ${COLORS.containerBg};\n border-radius: 4px;\n }\n\n .header h1 {\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 12px;\n color: ${COLORS.cyan};\n }\n\n .header-info {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 13px;\n }\n\n .info-item {\n color: ${COLORS.textDim};\n display: flex;\n align-items: baseline;\n }\n\n .info-label {\n font-weight: 600;\n margin-right: 8px;\n min-width: 80px;\n }\n\n .info-value {\n color: ${COLORS.text};\n flex: 1;\n }\n\n .messages {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n /* User message - matching TUI UserMessageComponent */\n .user-message {\n background: ${COLORS.userMessageBg};\n padding: 12px 16px;\n border-radius: 4px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Assistant text - matching TUI AssistantMessageComponent */\n .assistant-text {\n padding: 12px 16px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Thinking text - gray italic */\n .thinking-text {\n padding: 12px 16px;\n color: ${COLORS.italic};\n font-style: italic;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n /* Tool execution - matching TUI ToolExecutionComponent */\n .tool-execution {\n padding: 12px 16px;\n border-radius: 4px;\n margin-top: 8px;\n }\n\n .tool-header {\n font-weight: bold;\n }\n\n .tool-name {\n font-weight: bold;\n }\n\n .tool-path {\n color: ${COLORS.cyan};\n }\n\n .line-count {\n color: ${COLORS.textDim};\n }\n\n .tool-command {\n font-weight: bold;\n }\n\n .tool-output {\n margin-top: 12px;\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n font-family: inherit;\n }\n\n .tool-output > div {\n line-height: 1.4;\n }\n\n .tool-output pre {\n margin: 0;\n font-family: inherit;\n color: inherit;\n }\n\n /* Expandable tool output */\n .tool-output.expandable {\n cursor: pointer;\n }\n\n .tool-output.expandable:hover {\n opacity: 0.9;\n }\n\n .tool-output.expandable .output-full {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-preview {\n display: none;\n }\n\n .tool-output.expandable.expanded .output-full {\n display: block;\n }\n\n .expand-hint {\n color: ${COLORS.cyan};\n font-style: italic;\n margin-top: 4px;\n }\n\n /* System prompt section */\n .system-prompt {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .system-prompt-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .system-prompt-content {\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n font-size: 13px;\n }\n\n .tools-list {\n background: rgb(60, 55, 40);\n padding: 12px 16px;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n\n .tools-header {\n font-weight: bold;\n color: ${COLORS.yellow};\n margin-bottom: 8px;\n }\n\n .tools-content {\n color: ${COLORS.textDim};\n font-size: 13px;\n }\n\n .tool-item {\n margin: 4px 0;\n }\n\n .tool-item-name {\n font-weight: bold;\n color: ${COLORS.text};\n }\n\n /* Diff styling */\n .tool-diff {\n margin-top: 12px;\n font-size: 13px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n overflow-x: auto;\n max-width: 100%;\n }\n\n .diff-line-old {\n color: ${COLORS.red};\n white-space: pre;\n }\n\n .diff-line-new {\n color: ${COLORS.green};\n white-space: pre;\n }\n\n .diff-line-context {\n color: ${COLORS.textDim};\n white-space: pre;\n }\n\n /* Error text */\n .error-text {\n color: ${COLORS.red};\n padding: 12px 16px;\n }\n\n .footer {\n margin-top: 48px;\n padding: 20px;\n text-align: center;\n color: ${COLORS.textDim};\n font-size: 12px;\n }\n\n @media print {\n body {\n background: white;\n color: black;\n }\n .tool-execution {\n border: 1px solid #ddd;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>pi v${VERSION}</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">Session:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.id || \"unknown\")}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Date:</span>\n <span class=\"info-value\">${sessionHeader?.timestamp ? new Date(sessionHeader.timestamp).toLocaleString() : timestamp}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Model:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.model || state.model.id)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"header\">\n <h1>Messages</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">User:</span>\n <span class=\"info-value\">${userMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Assistant:</span>\n <span class=\"info-value\">${assistantMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Calls:</span>\n <span class=\"info-value\">${toolCallsCount}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Tool Results:</span>\n <span class=\"info-value\">${toolResultMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total:</span>\n <span class=\"info-value\">${totalMessages}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Directory:</span>\n <span class=\"info-value\">${escapeHtml(shortenPath(sessionHeader?.cwd || process.cwd()))}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Thinking:</span>\n <span class=\"info-value\">${escapeHtml(sessionHeader?.thinkingLevel || state.thinkingLevel)}</span>\n </div>\n </div>\n </div>\n\n <div class=\"system-prompt\">\n <div class=\"system-prompt-header\">System Prompt</div>\n <div class=\"system-prompt-content\">${escapeHtml(sessionHeader?.systemPrompt || state.systemPrompt)}</div>\n </div>\n\n <div class=\"tools-list\">\n <div class=\"tools-header\">Available Tools</div>\n <div class=\"tools-content\">\n ${state.tools\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t(tool) =>\n\t\t\t\t\t\t\t\t\t`<div class=\"tool-item\"><span class=\"tool-item-name\">${escapeHtml(tool.name)}</span> - ${escapeHtml(tool.description)}</div>`,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.join(\"\")}\n </div>\n </div>\n\n <div class=\"messages\">\n ${messagesHtml}\n </div>\n\n <div class=\"footer\">\n Generated by pi coding-agent on ${new Date().toLocaleString()}\n </div>\n </div>\n</body>\n</html>`;\n\n\t// Write HTML file\n\twriteFileSync(outputPath, html, \"utf8\");\n\n\treturn outputPath;\n}\n"]}