@mariozechner/pi-coding-agent 0.10.0 → 0.10.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 +18 -0
- package/README.md +75 -13
- package/dist/export-html.d.ts.map +1 -1
- package/dist/export-html.js +213 -28
- package/dist/export-html.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +131 -8
- package/dist/main.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +6 -3
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +4 -4
- package/dist/tui/footer.d.ts +6 -0
- package/dist/tui/footer.d.ts.map +1 -1
- package/dist/tui/footer.js +37 -1
- package/dist/tui/footer.js.map +1 -1
- package/dist/tui/tui-renderer.d.ts.map +1 -1
- package/dist/tui/tui-renderer.js +2 -0
- package/dist/tui/tui-renderer.js.map +1 -1
- package/package.json +4 -4
package/dist/export-html.js.map
CHANGED
|
@@ -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-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, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\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, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\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 timestamp for display\n */\nfunction formatTimestamp(timestamp: number | string | undefined): string {\n\tif (!timestamp) return \"\";\n\tconst date = new Date(typeof timestamp === \"string\" ? timestamp : timestamp);\n\treturn date.toLocaleTimeString(undefined, { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\" });\n}\n\n/**\n * Format model change event\n */\nfunction formatModelChange(event: any): string {\n\tconst timestamp = formatTimestamp(event.timestamp);\n\tconst timestampHtml = timestamp ? `<div class=\"message-timestamp\">${timestamp}</div>` : \"\";\n\tconst modelInfo = `${event.provider}/${event.modelId}`;\n\treturn `<div class=\"model-change\">${timestampHtml}<div class=\"model-change-text\">Switched to model: <span class=\"model-name\">${escapeHtml(modelInfo)}</span></div></div>`;\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\tconst timestamp = (message as any).timestamp;\n\tconst timestampHtml = timestamp ? `<div class=\"message-timestamp\">${formatTimestamp(timestamp)}</div>` : \"\";\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\">${timestampHtml}${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\t\thtml += timestampHtml ? `<div class=\"assistant-message\">${timestampHtml}` : \"\";\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\n\t\t// Close the assistant message wrapper if we opened one\n\t\tif (timestampHtml) {\n\t\t\thtml += \"</div>\";\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\tconst sessionEvents: any[] = []; // Track all events including model changes\n\tconst modelsUsed = new Set<string>(); // Track unique models used\n\n\t// Cumulative token and cost stats\n\tconst tokenStats = {\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t};\n\tconst costStats = {\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t};\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\t// Track initial model from session header\n\t\t\t\tif (entry.modelId) {\n\t\t\t\t\tconst modelInfo = entry.provider ? `${entry.provider}/${entry.modelId}` : entry.modelId;\n\t\t\t\t\tmodelsUsed.add(modelInfo);\n\t\t\t\t}\n\t\t\t} else if (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t\tsessionEvents.push(entry);\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\t// Accumulate token and cost stats from assistant messages\n\t\t\t\tif (entry.message.role === \"assistant\" && entry.message.usage) {\n\t\t\t\t\tconst usage = entry.message.usage;\n\t\t\t\t\ttokenStats.input += usage.input || 0;\n\t\t\t\t\ttokenStats.output += usage.output || 0;\n\t\t\t\t\ttokenStats.cacheRead += usage.cacheRead || 0;\n\t\t\t\t\ttokenStats.cacheWrite += usage.cacheWrite || 0;\n\n\t\t\t\t\tif (usage.cost) {\n\t\t\t\t\t\tcostStats.input += usage.cost.input || 0;\n\t\t\t\t\t\tcostStats.output += usage.cost.output || 0;\n\t\t\t\t\t\tcostStats.cacheRead += usage.cost.cacheRead || 0;\n\t\t\t\t\t\tcostStats.cacheWrite += usage.cost.cacheWrite || 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (entry.type === \"model_change\") {\n\t\t\t\tsessionEvents.push(entry);\n\t\t\t\t// Track model from model change event\n\t\t\t\tif (entry.modelId) {\n\t\t\t\t\tconst modelInfo = entry.provider ? `${entry.provider}/${entry.modelId}` : entry.modelId;\n\t\t\t\t\tmodelsUsed.add(modelInfo);\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// Get last assistant message for context percentage calculation (skip aborted messages)\n\tconst lastAssistantMessage = messages\n\t\t.slice()\n\t\t.reverse()\n\t\t.find((m) => m.role === \"assistant\" && (m as AssistantMessage).stopReason !== \"aborted\") as\n\t\t| AssistantMessage\n\t\t| undefined;\n\n\t// Calculate context percentage from last message (input + output + cacheRead + cacheWrite)\n\tconst contextTokens = lastAssistantMessage\n\t\t? lastAssistantMessage.usage.input +\n\t\t\tlastAssistantMessage.usage.output +\n\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t: 0;\n\n\t// Get the model info from the last assistant message\n\tconst lastModel = lastAssistantMessage?.model || state.model?.id || \"unknown\";\n\tconst lastProvider = lastAssistantMessage?.provider || \"\";\n\tconst lastModelInfo = lastProvider ? `${lastProvider}/${lastModel}` : lastModel;\n\n\tconst contextWindow = state.model?.contextWindow || 0;\n\tconst contextPercent = contextWindow > 0 ? ((contextTokens / contextWindow) * 100).toFixed(1) : \"0.0\";\n\n\t// Generate messages HTML (including model changes in chronological order)\n\tlet messagesHtml = \"\";\n\tfor (const event of sessionEvents) {\n\t\tif (event.type === \"message\" && event.message.role !== \"toolResult\") {\n\t\t\t// Skip toolResult messages as they're rendered with their tool calls\n\t\t\tmessagesHtml += formatMessage(event.message, toolResultsMap);\n\t\t} else if (event.type === \"model_change\") {\n\t\t\tmessagesHtml += formatModelChange(event);\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: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;\n font-size: 12px;\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: 14px;\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: 3px;\n font-size: 11px;\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: 100px;\n }\n\n .info-value {\n color: ${COLORS.text};\n flex: 1;\n }\n\n .info-value.cost {\n font-family: 'SF Mono', monospace;\n }\n\n .messages {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n /* Message timestamp */\n .message-timestamp {\n font-size: 10px;\n color: ${COLORS.textDim};\n margin-bottom: 4px;\n opacity: 0.8;\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 overflow-wrap: break-word;\n word-break: break-word;\n }\n\n /* Assistant message wrapper */\n .assistant-message {\n padding: 0;\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 overflow-wrap: break-word;\n word-break: 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 overflow-wrap: break-word;\n word-break: break-word;\n }\n\n /* Model change */\n .model-change {\n padding: 8px 16px;\n background: rgb(40, 40, 50);\n border-radius: 4px;\n }\n\n .model-change-text {\n color: ${COLORS.textDim};\n font-size: 11px;\n }\n\n .model-name {\n color: ${COLORS.cyan};\n font-weight: bold;\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 word-break: break-all;\n }\n\n .line-count {\n color: ${COLORS.textDim};\n }\n\n .tool-command {\n font-weight: bold;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n word-break: break-word;\n }\n\n .tool-output {\n margin-top: 12px;\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n word-break: break-word;\n font-family: inherit;\n overflow-x: auto;\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 white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\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 overflow-wrap: break-word;\n word-break: break-word;\n font-size: 11px;\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: 11px;\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: 11px;\n font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;\n overflow-x: auto;\n max-width: 100%;\n }\n\n .diff-line-old {\n color: ${COLORS.red};\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n }\n\n .diff-line-new {\n color: ${COLORS.green};\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n }\n\n .diff-line-context {\n color: ${COLORS.textDim};\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\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: 10px;\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\">Models:</span>\n <span class=\"info-value\">${\n\t\t\t\t\t\t\t\tArray.from(modelsUsed)\n\t\t\t\t\t\t\t\t\t.map((m) => escapeHtml(m))\n\t\t\t\t\t\t\t\t\t.join(\", \") || escapeHtml(sessionHeader?.model || state.model.id)\n\t\t\t\t\t\t\t}</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>\n </div>\n\n <div class=\"header\">\n <h1>Tokens & Cost</h1>\n <div class=\"header-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">Input:</span>\n <span class=\"info-value\">${tokenStats.input.toLocaleString()} tokens</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Output:</span>\n <span class=\"info-value\">${tokenStats.output.toLocaleString()} tokens</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Cache Read:</span>\n <span class=\"info-value\">${tokenStats.cacheRead.toLocaleString()} tokens</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Cache Write:</span>\n <span class=\"info-value\">${tokenStats.cacheWrite.toLocaleString()} tokens</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total:</span>\n <span class=\"info-value\">${(tokenStats.input + tokenStats.output + tokenStats.cacheRead + tokenStats.cacheWrite).toLocaleString()} tokens</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Input Cost:</span>\n <span class=\"info-value cost\">$${costStats.input.toFixed(4)}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Output Cost:</span>\n <span class=\"info-value cost\">$${costStats.output.toFixed(4)}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Cache Read Cost:</span>\n <span class=\"info-value cost\">$${costStats.cacheRead.toFixed(4)}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Cache Write Cost:</span>\n <span class=\"info-value cost\">$${costStats.cacheWrite.toFixed(4)}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total Cost:</span>\n <span class=\"info-value cost\"><strong>$${(costStats.input + costStats.output + costStats.cacheRead + costStats.cacheWrite).toFixed(4)}</strong></span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Context Usage:</span>\n <span class=\"info-value\">${contextTokens.toLocaleString()} / ${contextWindow.toLocaleString()} tokens (${contextPercent}%) - ${escapeHtml(lastModelInfo)}</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"]}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAinBA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBA+XxC","sourcesContent":["import { Agent, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { Api, KnownProvider, Model } from \"@mariozechner/pi-ai\";\nimport { ProcessTerminal, TUI } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./changelog.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./model-config.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\nimport { initTheme } from \"./theme/theme.js\";\nimport { codingTools } from \"./tools/index.js\";\nimport { SessionSelectorComponent } from \"./tui/session-selector.js\";\nimport { TuiRenderer } from \"./tui/tui-renderer.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\nconst defaultModelPerProvider: Record<KnownProvider, string> = {\n\tanthropic: \"claude-sonnet-4-5\",\n\topenai: \"gpt-5.1-codex\",\n\tgoogle: \"gemini-2.5-pro\",\n\topenrouter: \"openai/gpt-5.1-codex\",\n\txai: \"grok-4-fast-non-reasoning\",\n\tgroq: \"openai/gpt-oss-120b\",\n\tcerebras: \"zai-glm-4.6\",\n\tzai: \"glm-4.6\",\n};\n\ntype Mode = \"text\" | \"json\" | \"rpc\";\n\ninterface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\tsession?: string;\n\tmodels?: string[];\n\tprint?: boolean;\n\tmessages: string[];\n}\n\nfunction parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (level === \"off\" || level === \"minimal\" || level === \"low\" || level === \"medium\" || level === \"high\") {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t`Warning: Invalid thinking level \"${level}\". Valid values: off, minimal, low, medium, high`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction printHelp() {\n\tconsole.log(`${chalk.bold(\"pi\")} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n pi [options] [messages...]\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <id> Model ID (default: gemini-2.5-flash)\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path> Use specific session file\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P\n --thinking <level> Set thinking level: off, minimal, low, medium, high\n --help, -h Show this help\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n pi\n\n # Interactive mode with initial prompt\n pi \"List all .ts files in src/\"\n\n # Non-interactive mode (process and exit)\n pi -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n pi \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n pi --continue \"What did we discuss?\"\n\n # Use different model\n pi --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Limit model cycling to specific models\n pi --models claude-sonnet,claude-haiku,gpt-4o\n\n # Cycle models with fixed thinking levels\n pi --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n pi --thinking high \"Solve this complex problem\"\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n OPENROUTER_API_KEY - OpenRouter API key\n ZAI_API_KEY - ZAI API key\n PI_CODING_AGENT_DIR - Session storage directory (default: ~/.pi/agent)\n\n${chalk.bold(\"Available Tools:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n`);\n}\n\nfunction buildSystemPrompt(customPrompt?: string): string {\n\t// Check if customPrompt is a file path that exists\n\tif (customPrompt && existsSync(customPrompt)) {\n\t\ttry {\n\t\t\tcustomPrompt = readFileSync(customPrompt, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read system prompt file ${customPrompt}: ${error}`));\n\t\t\t// Fall through to use as literal string\n\t\t}\n\t}\n\n\tif (customPrompt) {\n\t\t// Use custom prompt as base, then add context/datetime\n\t\tconst now = new Date();\n\t\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\t\tweekday: \"long\",\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"long\",\n\t\t\tday: \"numeric\",\n\t\t\thour: \"2-digit\",\n\t\t\tminute: \"2-digit\",\n\t\t\tsecond: \"2-digit\",\n\t\t\ttimeZoneName: \"short\",\n\t\t});\n\n\t\tlet prompt = customPrompt;\n\n\t\t// Append project context files\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Add date/time and working directory last\n\t\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\t\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\t\treturn prompt;\n\t}\n\n\tconst now = new Date();\n\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\tweekday: \"long\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"long\",\n\t\tday: \"numeric\",\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t\ttimeZoneName: \"short\",\n\t});\n\n\t// Get absolute path to README.md\n\tconst readmePath = resolve(join(__dirname, \"../README.md\"));\n\n\tlet prompt = `You are actually not Claude, you are Pi. You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n- read: Read file contents\n- bash: Execute bash commands (ls, grep, find, etc.)\n- edit: Make surgical edits to files (find exact text and replace)\n- write: Create or overwrite files\n\nGuidelines:\n- Always use bash tool for file operations like ls, grep, find\n- Use read to examine files before editing\n- Use edit for precise changes (old text must match exactly)\n- Use write only for new files or complete rewrites\n- Be concise in your responses\n- Show file paths clearly when working with files\n- When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did\n\nDocumentation:\n- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}\n- Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;\n\n\t// Append project context files\n\tconst contextFiles = loadProjectContextFiles();\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Add date/time and working directory last\n\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\treturn prompt;\n}\n\n/**\n * Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md)\n */\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.md\", \"CLAUDE.md\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load all project context files in order:\n * 1. Global: ~/.pi/agent/AGENTS.md or CLAUDE.md\n * 2. Parent directories (top-most first) down to cwd\n * Each returns {path, content} for separate messages\n */\nfunction loadProjectContextFiles(): Array<{ path: string; content: string }> {\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\n\t// 1. Load global context from ~/.pi/agent/\n\tconst homeDir = homedir();\n\tconst globalContextDir = resolve(process.env.PI_CODING_AGENT_DIR || join(homeDir, \".pi/agent/\"));\n\tconst globalContext = loadContextFileFromDir(globalContextDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t}\n\n\t// 2. Walk up from cwd to root, collecting all context files\n\tconst cwd = process.cwd();\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tlet currentDir = cwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile) {\n\t\t\t// Add to beginning so we get top-most parent first\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t}\n\n\t\t// Stop if we've reached root\n\t\tif (currentDir === root) break;\n\n\t\t// Move up one directory\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break; // Safety check\n\t\tcurrentDir = parentDir;\n\t}\n\n\t// Add ancestor files in order (top-most → cwd)\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch (error) {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n */\nasync function resolveModelScope(\n\tpatterns: string[],\n): Promise<Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }>> {\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.warn(chalk.yellow(`Warning: Error loading models: ${error}`));\n\t\treturn [];\n\t}\n\n\tconst scopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [];\n\n\tfor (const pattern of patterns) {\n\t\t// Parse pattern:level format\n\t\tconst parts = pattern.split(\":\");\n\t\tconst modelPattern = parts[0];\n\t\tlet thinkingLevel: ThinkingLevel = \"off\";\n\n\t\tif (parts.length > 1) {\n\t\t\tconst level = parts[1];\n\t\t\tif (level === \"off\" || level === \"minimal\" || level === \"low\" || level === \"medium\" || level === \"high\") {\n\t\t\t\tthinkingLevel = level;\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\tchalk.yellow(`Warning: Invalid thinking level \"${level}\" in pattern \"${pattern}\". Using \"off\" instead.`),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Check for provider/modelId format (provider is everything before the first /)\n\t\tconst slashIndex = modelPattern.indexOf(\"/\");\n\t\tif (slashIndex !== -1) {\n\t\t\tconst provider = modelPattern.substring(0, slashIndex);\n\t\t\tconst modelId = modelPattern.substring(slashIndex + 1);\n\t\t\tconst providerMatch = availableModels.find(\n\t\t\t\t(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatch) {\n\t\t\t\tif (\n\t\t\t\t\t!scopedModels.find(\n\t\t\t\t\t\t(sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tscopedModels.push({ model: providerMatch, thinkingLevel });\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No exact provider/model match - fall through to other matching\n\t\t}\n\n\t\t// Check for exact ID match (case-insensitive)\n\t\tconst exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());\n\t\tif (exactMatch) {\n\t\t\t// Exact match found - use it directly\n\t\t\tif (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {\n\t\t\t\tscopedModels.push({ model: exactMatch, thinkingLevel });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// No exact match - fall back to partial matching\n\t\tconst matches = availableModels.filter(\n\t\t\t(m) =>\n\t\t\t\tm.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n\t\t\t\tm.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n\t\t);\n\n\t\tif (matches.length === 0) {\n\t\t\tconsole.warn(chalk.yellow(`Warning: No models match pattern \"${modelPattern}\"`));\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Helper to check if a model ID looks like an alias (no date suffix)\n\t\t// Dates are typically in format: -20241022 or -20250929\n\t\tconst isAlias = (id: string): boolean => {\n\t\t\t// Check if ID ends with -latest\n\t\t\tif (id.endsWith(\"-latest\")) return true;\n\n\t\t\t// Check if ID ends with a date pattern (-YYYYMMDD)\n\t\t\tconst datePattern = /-\\d{8}$/;\n\t\t\treturn !datePattern.test(id);\n\t\t};\n\n\t\t// Separate into aliases and dated versions\n\t\tconst aliases = matches.filter((m) => isAlias(m.id));\n\t\tconst datedVersions = matches.filter((m) => !isAlias(m.id));\n\n\t\tlet bestMatch: Model<Api>;\n\n\t\tif (aliases.length > 0) {\n\t\t\t// Prefer alias - if multiple aliases, pick the one that sorts highest\n\t\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = aliases[0];\n\t\t} else {\n\t\t\t// No alias found, pick latest dated version\n\t\t\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = datedVersions[0];\n\t\t}\n\n\t\t// Avoid duplicates\n\t\tif (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {\n\t\t\tscopedModels.push({ model: bestMatch, thinkingLevel });\n\t\t}\n\t}\n\n\treturn scopedModels;\n}\n\nasync function selectSession(sessionManager: SessionManager): Promise<string | null> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal());\n\t\tlet resolved = false;\n\n\t\tconst selector = new SessionSelectorComponent(\n\t\t\tsessionManager,\n\t\t\t(path: string) => {\n\t\t\t\tif (!resolved) {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tui.stop();\n\t\t\t\t\tresolve(path);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (!resolved) {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tui.stop();\n\t\t\t\t\tresolve(null);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector.getSessionList());\n\t\tui.start();\n\t});\n}\n\nasync function runInteractiveMode(\n\tagent: Agent,\n\tsessionManager: SessionManager,\n\tsettingsManager: SettingsManager,\n\tversion: string,\n\tchangelogMarkdown: string | null = null,\n\tmodelFallbackMessage: string | null = null,\n\tnewVersion: string | null = null,\n\tscopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [],\n\tinitialMessages: string[] = [],\n): Promise<void> {\n\tconst renderer = new TuiRenderer(\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tversion,\n\t\tchangelogMarkdown,\n\t\tnewVersion,\n\t\tscopedModels,\n\t);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait renderer.init();\n\n\t// Render any existing messages (from --continue mode)\n\trenderer.renderInitialMessages(agent.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\trenderer.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait agent.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\trenderer.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await renderer.getUserInput();\n\n\t\t// Process the message - agent.prompt will add user message and trigger state updates\n\t\ttry {\n\t\t\tawait agent.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\t// Display error in the TUI by adding an error message to the chat\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\trenderer.showError(errorMessage);\n\t\t}\n\t}\n}\n\nasync function runSingleShotMode(\n\tagent: Agent,\n\t_sessionManager: SessionManager,\n\tmessages: string[],\n\tmode: \"text\" | \"json\",\n): Promise<void> {\n\tif (mode === \"json\") {\n\t\t// Subscribe to all events and output as JSON\n\t\tagent.subscribe((event) => {\n\t\t\t// Output event as JSON (same format as session manager)\n\t\t\tconsole.log(JSON.stringify(event));\n\t\t});\n\t}\n\n\tfor (const message of messages) {\n\t\tawait agent.prompt(message);\n\t}\n\n\t// In text mode, only output the final assistant message\n\tif (mode === \"text\") {\n\t\tconst lastMessage = agent.state.messages[agent.state.messages.length - 1];\n\t\tif (lastMessage.role === \"assistant\") {\n\t\t\tfor (const content of lastMessage.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\tconsole.log(content.text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function runRpcMode(agent: Agent, _sessionManager: SessionManager): Promise<void> {\n\t// Subscribe to all events and output as JSON\n\tagent.subscribe((event) => {\n\t\tconsole.log(JSON.stringify(event));\n\t});\n\n\t// Listen for JSON input on stdin\n\tconst readline = await import(\"readline\");\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst input = JSON.parse(line);\n\n\t\t\t// Handle different RPC commands\n\t\t\tif (input.type === \"prompt\" && input.message) {\n\t\t\t\tawait agent.prompt(input.message, input.attachments);\n\t\t\t} else if (input.type === \"abort\") {\n\t\t\t\tagent.abort();\n\t\t\t}\n\t\t} catch (error: any) {\n\t\t\t// Output error as JSON\n\t\t\tconsole.log(JSON.stringify({ type: \"error\", error: error.message }));\n\t\t}\n\t});\n\n\t// Keep process alive\n\treturn new Promise(() => {});\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\t// Disable session saving if --no-session flag is set\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\t// Set the selected session as the active session\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided (needed for initial model selection)\n\tlet scopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine initial model using priority system:\n\t// 1. CLI args (--provider and --model)\n\t// 2. First model from --models scope\n\t// 3. Restored from session (if --continue or --resume)\n\t// 4. Saved default from settings.json\n\t// 5. First available model with valid API key\n\t// 6. null (allowed in interactive mode)\n\tlet initialModel: Model<Api> | null = null;\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\tif (parsed.provider && parsed.model) {\n\t\t// 1. CLI args take priority\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tinitialModel = model;\n\t} else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\t// 2. Use first model from --models scope (skip if continuing/resuming session)\n\t\tinitialModel = scopedModels[0].model;\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else if (parsed.continue || parsed.resume) {\n\t\t// 3. Restore from session (will be handled below after loading session)\n\t\t// Leave initialModel as null for now\n\t}\n\n\tif (!initialModel) {\n\t\t// 3. Try saved default from settings\n\t\tconst defaultProvider = settingsManager.getDefaultProvider();\n\t\tconst defaultModel = settingsManager.getDefaultModel();\n\t\tif (defaultProvider && defaultModel) {\n\t\t\tconst { model, error } = findModel(defaultProvider, defaultModel);\n\t\t\tif (error) {\n\t\t\t\tconsole.error(chalk.red(error));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t\tinitialModel = model;\n\n\t\t\t// Also load saved thinking level if we're using saved model\n\t\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\t\tif (savedThinking) {\n\t\t\t\tinitialThinking = savedThinking;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!initialModel) {\n\t\t// 4. Try first available model with valid API key\n\t\t// Prefer default model for each provider if available\n\t\tconst { models: availableModels, error } = await getAvailableModels();\n\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tif (availableModels.length > 0) {\n\t\t\t// Try to find a default model from known providers\n\t\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[provider];\n\t\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);\n\t\t\t\tif (match) {\n\t\t\t\t\tinitialModel = match;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If no default found, use first available\n\t\t\tif (!initialModel) {\n\t\t\t\tinitialModel = availableModels[0];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Determine mode early to know if we should print messages and fail early\n\t// Interactive mode: no --print flag and no --mode flag\n\t// Having initial messages doesn't make it non-interactive anymore\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\tconst mode = parsed.mode || \"text\";\n\t// Only print informational messages in interactive mode\n\t// Non-interactive modes (-p, --mode json, --mode rpc) should be silent except for output\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(\"\\nOr create ~/.pi/agent/models.json\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tconst systemPrompt = buildSystemPrompt(parsed.systemPrompt);\n\n\t// Load previous messages if continuing or resuming\n\t// This may update initialModel if restoring from session\n\tif (parsed.continue || parsed.resume) {\n\t\t// Load and restore model (overrides initialModel if found and has API key)\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst { model: restoredModel, error } = findModel(savedModel.provider, savedModel.modelId);\n\n\t\t\tif (error) {\n\t\t\t\tconsole.error(chalk.red(error));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Check if restored model exists and has a valid API key\n\t\t\tconst hasApiKey = restoredModel ? !!(await getApiKeyForModel(restoredModel)) : false;\n\n\t\t\tif (restoredModel && hasApiKey) {\n\t\t\t\tinitialModel = restoredModel;\n\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\tconsole.log(chalk.dim(`Restored model: ${savedModel.provider}/${savedModel.modelId}`));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Model not found or no API key - fall back to default selection\n\t\t\t\tconst reason = !restoredModel ? \"model no longer exists\" : \"no API key available\";\n\n\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t\t`Warning: Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}).`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Ensure we have a valid model - use the same fallback logic\n\t\t\t\tif (!initialModel) {\n\t\t\t\t\tconst { models: availableModels, error: availableError } = await getAvailableModels();\n\t\t\t\t\tif (availableError) {\n\t\t\t\t\t\tconsole.error(chalk.red(availableError));\n\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t}\n\t\t\t\t\tif (availableModels.length > 0) {\n\t\t\t\t\t\t// Try to find a default model from known providers\n\t\t\t\t\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\t\t\t\t\tconst defaultModelId = defaultModelPerProvider[provider];\n\t\t\t\t\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tinitialModel = match;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If no default found, use first available\n\t\t\t\t\t\tif (!initialModel) {\n\t\t\t\t\t\t\tinitialModel = availableModels[0];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (initialModel && shouldPrintMessages) {\n\t\t\t\t\t\t\tconsole.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// No models available at all\n\t\t\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\t\t\tconsole.error(chalk.red(\"\\nNo models available.\"));\n\t\t\t\t\t\t\tconsole.error(chalk.yellow(\"Set an API key environment variable:\"));\n\t\t\t\t\t\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\t\t\t\t\t\tconsole.error(chalk.yellow(\"\\nOr create ~/.pi/agent/models.json\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t}\n\t\t\t\t} else if (shouldPrintMessages) {\n\t\t\t\t\tconsole.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Create agent (initialModel can be null in interactive mode)\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: codingTools,\n\t\t},\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\t// Dynamic API key lookup based on current model's provider\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\t// Try CLI override first\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\t// Use model-specific key lookup\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ~/.pi/agent/models.json`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, silently reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Track if we had to fall back from saved model (to show in chat later)\n\tlet modelFallbackMessage: string | null = null;\n\n\t// Load previous messages if continuing or resuming\n\tif (parsed.continue || parsed.resume) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tagent.setThinkingLevel(thinkingLevel);\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\n\t\t// Check if we had to fall back from saved model\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel && initialModel) {\n\t\t\tconst savedMatches = initialModel.provider === savedModel.provider && initialModel.id === savedModel.modelId;\n\t\t\tif (!savedMatches) {\n\t\t\t\tconst { model: restoredModel, error } = findModel(savedModel.provider, savedModel.modelId);\n\t\t\t\tif (error) {\n\t\t\t\t\t// Config error - already shown above, just use generic message\n\t\t\t\t\tmodelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId}. Using ${initialModel.provider}/${initialModel.id}.`;\n\t\t\t\t} else {\n\t\t\t\t\tconst reason = !restoredModel ? \"model no longer exists\" : \"no API key available\";\n\t\t\t\t\tmodelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}). Using ${initialModel.provider}/${initialModel.id}.`;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log loaded context files (they're already in the system prompt)\n\tif (shouldPrintMessages && !parsed.continue && !parsed.resume) {\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tconsole.log(chalk.dim(\"Loaded project context from:\"));\n\t\t\tfor (const { path: filePath } of contextFiles) {\n\t\t\t\tconsole.log(chalk.dim(` - ${filePath}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\t// RPC mode - headless operation\n\t\tawait runRpcMode(agent, sessionManager);\n\t} else if (isInteractive) {\n\t\t// Check for new version (don't block startup if it takes too long)\n\t\tlet newVersion: string | null = null;\n\t\ttry {\n\t\t\tnewVersion = await Promise.race([\n\t\t\t\tcheckForNewVersion(VERSION),\n\t\t\t\tnew Promise<null>((resolve) => setTimeout(() => resolve(null), 1000)), // 1 second timeout\n\t\t\t]);\n\t\t} catch (e) {\n\t\t\t// Ignore errors\n\t\t}\n\n\t\t// Check if we should show changelog (only in interactive mode, only for new sessions)\n\t\tlet changelogMarkdown: string | null = null;\n\t\tif (!parsed.continue && !parsed.resume) {\n\t\t\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\n\t\t\t// Check if we need to show changelog\n\t\t\tif (!lastVersion) {\n\t\t\t\t// First run - show all entries\n\t\t\t\tconst changelogPath = getChangelogPath();\n\t\t\t\tconst entries = parseChangelog(changelogPath);\n\t\t\t\tif (entries.length > 0) {\n\t\t\t\t\tchangelogMarkdown = entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Parse current and last versions\n\t\t\t\tconst changelogPath = getChangelogPath();\n\t\t\t\tconst entries = parseChangelog(changelogPath);\n\t\t\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\n\t\t\t\tif (newEntries.length > 0) {\n\t\t\t\t\tchangelogMarkdown = newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Interactive mode - use TUI (may have initial messages from CLI args)\n\t\tawait runInteractiveMode(\n\t\t\tagent,\n\t\t\tsessionManager,\n\t\t\tsettingsManager,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tnewVersion,\n\t\t\tscopedModels,\n\t\t\tparsed.messages,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runSingleShotMode(agent, sessionManager, parsed.messages, mode);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAwuBA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBAyZxC","sourcesContent":["import { Agent, type Attachment, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { Api, KnownProvider, Model } from \"@mariozechner/pi-ai\";\nimport { ProcessTerminal, TUI } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport { existsSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, extname, join, resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./changelog.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./model-config.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { SettingsManager } from \"./settings-manager.js\";\nimport { initTheme } from \"./theme/theme.js\";\nimport { codingTools } from \"./tools/index.js\";\nimport { SessionSelectorComponent } from \"./tui/session-selector.js\";\nimport { TuiRenderer } from \"./tui/tui-renderer.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\nconst defaultModelPerProvider: Record<KnownProvider, string> = {\n\tanthropic: \"claude-sonnet-4-5\",\n\topenai: \"gpt-5.1-codex\",\n\tgoogle: \"gemini-2.5-pro\",\n\topenrouter: \"openai/gpt-5.1-codex\",\n\txai: \"grok-4-fast-non-reasoning\",\n\tgroq: \"openai/gpt-oss-120b\",\n\tcerebras: \"zai-glm-4.6\",\n\tzai: \"glm-4.6\",\n};\n\ntype Mode = \"text\" | \"json\" | \"rpc\";\n\ninterface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\tsession?: string;\n\tmodels?: string[];\n\tprint?: boolean;\n\tmessages: string[];\n\tfileArgs: string[];\n}\n\nfunction parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (level === \"off\" || level === \"minimal\" || level === \"low\" || level === \"medium\" || level === \"high\") {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t`Warning: Invalid thinking level \"${level}\". Valid values: off, minimal, low, medium, high`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".png\": \"image/png\",\n\t\".gif\": \"image/gif\",\n\t\".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n\tconst ext = extname(filePath).toLowerCase();\n\treturn IMAGE_MIME_TYPES[ext] || null;\n}\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\n/**\n * Process @file arguments into text content and image attachments\n */\nfunction processFileArguments(fileArgs: string[]): { textContent: string; imageAttachments: Attachment[] } {\n\tlet textContent = \"\";\n\tconst imageAttachments: Attachment[] = [];\n\n\tfor (const fileArg of fileArgs) {\n\t\t// Expand and resolve path\n\t\tconst expandedPath = expandPath(fileArg);\n\t\tconst absolutePath = resolve(expandedPath);\n\n\t\t// Check if file exists\n\t\tif (!existsSync(absolutePath)) {\n\t\t\tconsole.error(chalk.red(`Error: File not found: ${absolutePath}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Check if file is empty\n\t\tconst stats = statSync(absolutePath);\n\t\tif (stats.size === 0) {\n\t\t\t// Skip empty files\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst mimeType = isImageFile(absolutePath);\n\n\t\tif (mimeType) {\n\t\t\t// Handle image file\n\t\t\tconst content = readFileSync(absolutePath);\n\t\t\tconst base64Content = content.toString(\"base64\");\n\n\t\t\tconst attachment: Attachment = {\n\t\t\t\tid: `file-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n\t\t\t\ttype: \"image\",\n\t\t\t\tfileName: absolutePath.split(\"/\").pop() || absolutePath,\n\t\t\t\tmimeType,\n\t\t\t\tsize: stats.size,\n\t\t\t\tcontent: base64Content,\n\t\t\t};\n\n\t\t\timageAttachments.push(attachment);\n\n\t\t\t// Add text reference to image\n\t\t\ttextContent += `<file name=\"${absolutePath}\"></file>\\n`;\n\t\t} else {\n\t\t\t// Handle text file\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(absolutePath, \"utf-8\");\n\t\t\t\ttextContent += `<file name=\"${absolutePath}\">\\n${content}\\n</file>\\n`;\n\t\t\t} catch (error: any) {\n\t\t\t\tconsole.error(chalk.red(`Error: Could not read file ${absolutePath}: ${error.message}`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { textContent, imageAttachments };\n}\n\nfunction printHelp() {\n\tconsole.log(`${chalk.bold(\"pi\")} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n pi [options] [@files...] [messages...]\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <id> Model ID (default: gemini-2.5-flash)\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path> Use specific session file\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P\n --thinking <level> Set thinking level: off, minimal, low, medium, high\n --help, -h Show this help\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n pi\n\n # Interactive mode with initial prompt\n pi \"List all .ts files in src/\"\n\n # Include files in initial message\n pi @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n pi -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n pi \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n pi --continue \"What did we discuss?\"\n\n # Use different model\n pi --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Limit model cycling to specific models\n pi --models claude-sonnet,claude-haiku,gpt-4o\n\n # Cycle models with fixed thinking levels\n pi --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n pi --thinking high \"Solve this complex problem\"\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n OPENROUTER_API_KEY - OpenRouter API key\n ZAI_API_KEY - ZAI API key\n PI_CODING_AGENT_DIR - Session storage directory (default: ~/.pi/agent)\n\n${chalk.bold(\"Available Tools:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n`);\n}\n\nfunction buildSystemPrompt(customPrompt?: string): string {\n\t// Check if customPrompt is a file path that exists\n\tif (customPrompt && existsSync(customPrompt)) {\n\t\ttry {\n\t\t\tcustomPrompt = readFileSync(customPrompt, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read system prompt file ${customPrompt}: ${error}`));\n\t\t\t// Fall through to use as literal string\n\t\t}\n\t}\n\n\tif (customPrompt) {\n\t\t// Use custom prompt as base, then add context/datetime\n\t\tconst now = new Date();\n\t\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\t\tweekday: \"long\",\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"long\",\n\t\t\tday: \"numeric\",\n\t\t\thour: \"2-digit\",\n\t\t\tminute: \"2-digit\",\n\t\t\tsecond: \"2-digit\",\n\t\t\ttimeZoneName: \"short\",\n\t\t});\n\n\t\tlet prompt = customPrompt;\n\n\t\t// Append project context files\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Add date/time and working directory last\n\t\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\t\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\t\treturn prompt;\n\t}\n\n\tconst now = new Date();\n\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\tweekday: \"long\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"long\",\n\t\tday: \"numeric\",\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t\ttimeZoneName: \"short\",\n\t});\n\n\t// Get absolute path to README.md\n\tconst readmePath = resolve(join(__dirname, \"../README.md\"));\n\n\tlet prompt = `You are actually not Claude, you are Pi. You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n- read: Read file contents\n- bash: Execute bash commands (ls, grep, find, etc.)\n- edit: Make surgical edits to files (find exact text and replace)\n- write: Create or overwrite files\n\nGuidelines:\n- Always use bash tool for file operations like ls, grep, find\n- Use read to examine files before editing\n- Use edit for precise changes (old text must match exactly)\n- Use write only for new files or complete rewrites\n- Be concise in your responses\n- Show file paths clearly when working with files\n- When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did\n\nDocumentation:\n- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}\n- Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;\n\n\t// Append project context files\n\tconst contextFiles = loadProjectContextFiles();\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Add date/time and working directory last\n\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\treturn prompt;\n}\n\n/**\n * Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md)\n */\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.md\", \"CLAUDE.md\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load all project context files in order:\n * 1. Global: ~/.pi/agent/AGENTS.md or CLAUDE.md\n * 2. Parent directories (top-most first) down to cwd\n * Each returns {path, content} for separate messages\n */\nfunction loadProjectContextFiles(): Array<{ path: string; content: string }> {\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\n\t// 1. Load global context from ~/.pi/agent/\n\tconst homeDir = homedir();\n\tconst globalContextDir = resolve(process.env.PI_CODING_AGENT_DIR || join(homeDir, \".pi/agent/\"));\n\tconst globalContext = loadContextFileFromDir(globalContextDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t}\n\n\t// 2. Walk up from cwd to root, collecting all context files\n\tconst cwd = process.cwd();\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tlet currentDir = cwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile) {\n\t\t\t// Add to beginning so we get top-most parent first\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t}\n\n\t\t// Stop if we've reached root\n\t\tif (currentDir === root) break;\n\n\t\t// Move up one directory\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break; // Safety check\n\t\tcurrentDir = parentDir;\n\t}\n\n\t// Add ancestor files in order (top-most → cwd)\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch (error) {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n */\nasync function resolveModelScope(\n\tpatterns: string[],\n): Promise<Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }>> {\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.warn(chalk.yellow(`Warning: Error loading models: ${error}`));\n\t\treturn [];\n\t}\n\n\tconst scopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [];\n\n\tfor (const pattern of patterns) {\n\t\t// Parse pattern:level format\n\t\tconst parts = pattern.split(\":\");\n\t\tconst modelPattern = parts[0];\n\t\tlet thinkingLevel: ThinkingLevel = \"off\";\n\n\t\tif (parts.length > 1) {\n\t\t\tconst level = parts[1];\n\t\t\tif (level === \"off\" || level === \"minimal\" || level === \"low\" || level === \"medium\" || level === \"high\") {\n\t\t\t\tthinkingLevel = level;\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\tchalk.yellow(`Warning: Invalid thinking level \"${level}\" in pattern \"${pattern}\". Using \"off\" instead.`),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Check for provider/modelId format (provider is everything before the first /)\n\t\tconst slashIndex = modelPattern.indexOf(\"/\");\n\t\tif (slashIndex !== -1) {\n\t\t\tconst provider = modelPattern.substring(0, slashIndex);\n\t\t\tconst modelId = modelPattern.substring(slashIndex + 1);\n\t\t\tconst providerMatch = availableModels.find(\n\t\t\t\t(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatch) {\n\t\t\t\tif (\n\t\t\t\t\t!scopedModels.find(\n\t\t\t\t\t\t(sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tscopedModels.push({ model: providerMatch, thinkingLevel });\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No exact provider/model match - fall through to other matching\n\t\t}\n\n\t\t// Check for exact ID match (case-insensitive)\n\t\tconst exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());\n\t\tif (exactMatch) {\n\t\t\t// Exact match found - use it directly\n\t\t\tif (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {\n\t\t\t\tscopedModels.push({ model: exactMatch, thinkingLevel });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// No exact match - fall back to partial matching\n\t\tconst matches = availableModels.filter(\n\t\t\t(m) =>\n\t\t\t\tm.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n\t\t\t\tm.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n\t\t);\n\n\t\tif (matches.length === 0) {\n\t\t\tconsole.warn(chalk.yellow(`Warning: No models match pattern \"${modelPattern}\"`));\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Helper to check if a model ID looks like an alias (no date suffix)\n\t\t// Dates are typically in format: -20241022 or -20250929\n\t\tconst isAlias = (id: string): boolean => {\n\t\t\t// Check if ID ends with -latest\n\t\t\tif (id.endsWith(\"-latest\")) return true;\n\n\t\t\t// Check if ID ends with a date pattern (-YYYYMMDD)\n\t\t\tconst datePattern = /-\\d{8}$/;\n\t\t\treturn !datePattern.test(id);\n\t\t};\n\n\t\t// Separate into aliases and dated versions\n\t\tconst aliases = matches.filter((m) => isAlias(m.id));\n\t\tconst datedVersions = matches.filter((m) => !isAlias(m.id));\n\n\t\tlet bestMatch: Model<Api>;\n\n\t\tif (aliases.length > 0) {\n\t\t\t// Prefer alias - if multiple aliases, pick the one that sorts highest\n\t\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = aliases[0];\n\t\t} else {\n\t\t\t// No alias found, pick latest dated version\n\t\t\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = datedVersions[0];\n\t\t}\n\n\t\t// Avoid duplicates\n\t\tif (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {\n\t\t\tscopedModels.push({ model: bestMatch, thinkingLevel });\n\t\t}\n\t}\n\n\treturn scopedModels;\n}\n\nasync function selectSession(sessionManager: SessionManager): Promise<string | null> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal());\n\t\tlet resolved = false;\n\n\t\tconst selector = new SessionSelectorComponent(\n\t\t\tsessionManager,\n\t\t\t(path: string) => {\n\t\t\t\tif (!resolved) {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tui.stop();\n\t\t\t\t\tresolve(path);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (!resolved) {\n\t\t\t\t\tresolved = true;\n\t\t\t\t\tui.stop();\n\t\t\t\t\tresolve(null);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector.getSessionList());\n\t\tui.start();\n\t});\n}\n\nasync function runInteractiveMode(\n\tagent: Agent,\n\tsessionManager: SessionManager,\n\tsettingsManager: SettingsManager,\n\tversion: string,\n\tchangelogMarkdown: string | null = null,\n\tmodelFallbackMessage: string | null = null,\n\tnewVersion: string | null = null,\n\tscopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [],\n\tinitialMessages: string[] = [],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n): Promise<void> {\n\tconst renderer = new TuiRenderer(\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tversion,\n\t\tchangelogMarkdown,\n\t\tnewVersion,\n\t\tscopedModels,\n\t);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait renderer.init();\n\n\t// Render any existing messages (from --continue mode)\n\trenderer.renderInitialMessages(agent.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\trenderer.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial message with attachments if provided (from @file args)\n\tif (initialMessage) {\n\t\ttry {\n\t\t\tawait agent.prompt(initialMessage, initialAttachments);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\trenderer.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Process remaining initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait agent.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\trenderer.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await renderer.getUserInput();\n\n\t\t// Process the message - agent.prompt will add user message and trigger state updates\n\t\ttry {\n\t\t\tawait agent.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\t// Display error in the TUI by adding an error message to the chat\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\trenderer.showError(errorMessage);\n\t\t}\n\t}\n}\n\nasync function runSingleShotMode(\n\tagent: Agent,\n\t_sessionManager: SessionManager,\n\tmessages: string[],\n\tmode: \"text\" | \"json\",\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n): Promise<void> {\n\tif (mode === \"json\") {\n\t\t// Subscribe to all events and output as JSON\n\t\tagent.subscribe((event) => {\n\t\t\t// Output event as JSON (same format as session manager)\n\t\t\tconsole.log(JSON.stringify(event));\n\t\t});\n\t}\n\n\t// Send initial message with attachments if provided\n\tif (initialMessage) {\n\t\tawait agent.prompt(initialMessage, initialAttachments);\n\t}\n\n\t// Send remaining messages\n\tfor (const message of messages) {\n\t\tawait agent.prompt(message);\n\t}\n\n\t// In text mode, only output the final assistant message\n\tif (mode === \"text\") {\n\t\tconst lastMessage = agent.state.messages[agent.state.messages.length - 1];\n\t\tif (lastMessage.role === \"assistant\") {\n\t\t\tfor (const content of lastMessage.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\tconsole.log(content.text);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nasync function runRpcMode(agent: Agent, _sessionManager: SessionManager): Promise<void> {\n\t// Subscribe to all events and output as JSON\n\tagent.subscribe((event) => {\n\t\tconsole.log(JSON.stringify(event));\n\t});\n\n\t// Listen for JSON input on stdin\n\tconst readline = await import(\"readline\");\n\tconst rl = readline.createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stdout,\n\t\tterminal: false,\n\t});\n\n\trl.on(\"line\", async (line: string) => {\n\t\ttry {\n\t\t\tconst input = JSON.parse(line);\n\n\t\t\t// Handle different RPC commands\n\t\t\tif (input.type === \"prompt\" && input.message) {\n\t\t\t\tawait agent.prompt(input.message, input.attachments);\n\t\t\t} else if (input.type === \"abort\") {\n\t\t\t\tagent.abort();\n\t\t\t}\n\t\t} catch (error: any) {\n\t\t\t// Output error as JSON\n\t\t\tconsole.log(JSON.stringify({ type: \"error\", error: error.message }));\n\t\t}\n\t});\n\n\t// Keep process alive\n\treturn new Promise(() => {});\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Validate: RPC mode doesn't support @file arguments\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Process @file arguments if any\n\tlet initialMessage: string | undefined;\n\tlet initialAttachments: Attachment[] | undefined;\n\n\tif (parsed.fileArgs.length > 0) {\n\t\tconst { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);\n\n\t\t// Combine file content with first plain text message (if any)\n\t\tif (parsed.messages.length > 0) {\n\t\t\tinitialMessage = textContent + parsed.messages[0];\n\t\t\tparsed.messages.shift(); // Remove first message as it's been combined\n\t\t} else {\n\t\t\tinitialMessage = textContent;\n\t\t}\n\n\t\tinitialAttachments = imageAttachments.length > 0 ? imageAttachments : undefined;\n\t}\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\t// Disable session saving if --no-session flag is set\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\t// Set the selected session as the active session\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided (needed for initial model selection)\n\tlet scopedModels: Array<{ model: Model<Api>; thinkingLevel: ThinkingLevel }> = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine initial model using priority system:\n\t// 1. CLI args (--provider and --model)\n\t// 2. First model from --models scope\n\t// 3. Restored from session (if --continue or --resume)\n\t// 4. Saved default from settings.json\n\t// 5. First available model with valid API key\n\t// 6. null (allowed in interactive mode)\n\tlet initialModel: Model<Api> | null = null;\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\tif (parsed.provider && parsed.model) {\n\t\t// 1. CLI args take priority\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tinitialModel = model;\n\t} else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\t// 2. Use first model from --models scope (skip if continuing/resuming session)\n\t\tinitialModel = scopedModels[0].model;\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else if (parsed.continue || parsed.resume) {\n\t\t// 3. Restore from session (will be handled below after loading session)\n\t\t// Leave initialModel as null for now\n\t}\n\n\tif (!initialModel) {\n\t\t// 3. Try saved default from settings\n\t\tconst defaultProvider = settingsManager.getDefaultProvider();\n\t\tconst defaultModel = settingsManager.getDefaultModel();\n\t\tif (defaultProvider && defaultModel) {\n\t\t\tconst { model, error } = findModel(defaultProvider, defaultModel);\n\t\t\tif (error) {\n\t\t\t\tconsole.error(chalk.red(error));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t\tinitialModel = model;\n\n\t\t\t// Also load saved thinking level if we're using saved model\n\t\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\t\tif (savedThinking) {\n\t\t\t\tinitialThinking = savedThinking;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!initialModel) {\n\t\t// 4. Try first available model with valid API key\n\t\t// Prefer default model for each provider if available\n\t\tconst { models: availableModels, error } = await getAvailableModels();\n\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tif (availableModels.length > 0) {\n\t\t\t// Try to find a default model from known providers\n\t\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[provider];\n\t\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);\n\t\t\t\tif (match) {\n\t\t\t\t\tinitialModel = match;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If no default found, use first available\n\t\t\tif (!initialModel) {\n\t\t\t\tinitialModel = availableModels[0];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Determine mode early to know if we should print messages and fail early\n\t// Interactive mode: no --print flag and no --mode flag\n\t// Having initial messages doesn't make it non-interactive anymore\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\tconst mode = parsed.mode || \"text\";\n\t// Only print informational messages in interactive mode\n\t// Non-interactive modes (-p, --mode json, --mode rpc) should be silent except for output\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(\"\\nOr create ~/.pi/agent/models.json\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tconst systemPrompt = buildSystemPrompt(parsed.systemPrompt);\n\n\t// Load previous messages if continuing or resuming\n\t// This may update initialModel if restoring from session\n\tif (parsed.continue || parsed.resume) {\n\t\t// Load and restore model (overrides initialModel if found and has API key)\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst { model: restoredModel, error } = findModel(savedModel.provider, savedModel.modelId);\n\n\t\t\tif (error) {\n\t\t\t\tconsole.error(chalk.red(error));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Check if restored model exists and has a valid API key\n\t\t\tconst hasApiKey = restoredModel ? !!(await getApiKeyForModel(restoredModel)) : false;\n\n\t\t\tif (restoredModel && hasApiKey) {\n\t\t\t\tinitialModel = restoredModel;\n\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\tconsole.log(chalk.dim(`Restored model: ${savedModel.provider}/${savedModel.modelId}`));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Model not found or no API key - fall back to default selection\n\t\t\t\tconst reason = !restoredModel ? \"model no longer exists\" : \"no API key available\";\n\n\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\tchalk.yellow(\n\t\t\t\t\t\t\t`Warning: Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}).`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Ensure we have a valid model - use the same fallback logic\n\t\t\t\tif (!initialModel) {\n\t\t\t\t\tconst { models: availableModels, error: availableError } = await getAvailableModels();\n\t\t\t\t\tif (availableError) {\n\t\t\t\t\t\tconsole.error(chalk.red(availableError));\n\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t}\n\t\t\t\t\tif (availableModels.length > 0) {\n\t\t\t\t\t\t// Try to find a default model from known providers\n\t\t\t\t\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\t\t\t\t\tconst defaultModelId = defaultModelPerProvider[provider];\n\t\t\t\t\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultModelId);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tinitialModel = match;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If no default found, use first available\n\t\t\t\t\t\tif (!initialModel) {\n\t\t\t\t\t\t\tinitialModel = availableModels[0];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (initialModel && shouldPrintMessages) {\n\t\t\t\t\t\t\tconsole.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// No models available at all\n\t\t\t\t\t\tif (shouldPrintMessages) {\n\t\t\t\t\t\t\tconsole.error(chalk.red(\"\\nNo models available.\"));\n\t\t\t\t\t\t\tconsole.error(chalk.yellow(\"Set an API key environment variable:\"));\n\t\t\t\t\t\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\t\t\t\t\t\tconsole.error(chalk.yellow(\"\\nOr create ~/.pi/agent/models.json\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprocess.exit(1);\n\t\t\t\t\t}\n\t\t\t\t} else if (shouldPrintMessages) {\n\t\t\t\t\tconsole.log(chalk.dim(`Falling back to: ${initialModel.provider}/${initialModel.id}`));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Create agent (initialModel can be null in interactive mode)\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: codingTools,\n\t\t},\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\t// Dynamic API key lookup based on current model's provider\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\t// Try CLI override first\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\t// Use model-specific key lookup\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ~/.pi/agent/models.json`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, silently reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Track if we had to fall back from saved model (to show in chat later)\n\tlet modelFallbackMessage: string | null = null;\n\n\t// Load previous messages if continuing or resuming\n\tif (parsed.continue || parsed.resume) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tagent.setThinkingLevel(thinkingLevel);\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\n\t\t// Check if we had to fall back from saved model\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel && initialModel) {\n\t\t\tconst savedMatches = initialModel.provider === savedModel.provider && initialModel.id === savedModel.modelId;\n\t\t\tif (!savedMatches) {\n\t\t\t\tconst { model: restoredModel, error } = findModel(savedModel.provider, savedModel.modelId);\n\t\t\t\tif (error) {\n\t\t\t\t\t// Config error - already shown above, just use generic message\n\t\t\t\t\tmodelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId}. Using ${initialModel.provider}/${initialModel.id}.`;\n\t\t\t\t} else {\n\t\t\t\t\tconst reason = !restoredModel ? \"model no longer exists\" : \"no API key available\";\n\t\t\t\t\tmodelFallbackMessage = `Could not restore model ${savedModel.provider}/${savedModel.modelId} (${reason}). Using ${initialModel.provider}/${initialModel.id}.`;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log loaded context files (they're already in the system prompt)\n\tif (shouldPrintMessages && !parsed.continue && !parsed.resume) {\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tconsole.log(chalk.dim(\"Loaded project context from:\"));\n\t\t\tfor (const { path: filePath } of contextFiles) {\n\t\t\t\tconsole.log(chalk.dim(` - ${filePath}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\t// RPC mode - headless operation\n\t\tawait runRpcMode(agent, sessionManager);\n\t} else if (isInteractive) {\n\t\t// Check for new version (don't block startup if it takes too long)\n\t\tlet newVersion: string | null = null;\n\t\ttry {\n\t\t\tnewVersion = await Promise.race([\n\t\t\t\tcheckForNewVersion(VERSION),\n\t\t\t\tnew Promise<null>((resolve) => setTimeout(() => resolve(null), 1000)), // 1 second timeout\n\t\t\t]);\n\t\t} catch (e) {\n\t\t\t// Ignore errors\n\t\t}\n\n\t\t// Check if we should show changelog (only in interactive mode, only for new sessions)\n\t\tlet changelogMarkdown: string | null = null;\n\t\tif (!parsed.continue && !parsed.resume) {\n\t\t\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\n\t\t\t// Check if we need to show changelog\n\t\t\tif (!lastVersion) {\n\t\t\t\t// First run - show all entries\n\t\t\t\tconst changelogPath = getChangelogPath();\n\t\t\t\tconst entries = parseChangelog(changelogPath);\n\t\t\t\tif (entries.length > 0) {\n\t\t\t\t\tchangelogMarkdown = entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Parse current and last versions\n\t\t\t\tconst changelogPath = getChangelogPath();\n\t\t\t\tconst entries = parseChangelog(changelogPath);\n\t\t\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\n\t\t\t\tif (newEntries.length > 0) {\n\t\t\t\t\tchangelogMarkdown = newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Interactive mode - use TUI (may have initial messages from CLI args)\n\t\tawait runInteractiveMode(\n\t\t\tagent,\n\t\t\tsessionManager,\n\t\t\tsettingsManager,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tnewVersion,\n\t\t\tscopedModels,\n\t\t\tparsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialAttachments,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runSingleShotMode(agent, sessionManager, parsed.messages, mode, initialMessage, initialAttachments);\n\t}\n}\n"]}
|