@mariozechner/pi-coding-agent 0.10.2 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +52 -2
  3. package/dist/export-html.d.ts +5 -0
  4. package/dist/export-html.d.ts.map +1 -1
  5. package/dist/export-html.js +662 -1
  6. package/dist/export-html.js.map +1 -1
  7. package/dist/main.d.ts.map +1 -1
  8. package/dist/main.js +116 -19
  9. package/dist/main.js.map +1 -1
  10. package/dist/tools/find.d.ts +9 -0
  11. package/dist/tools/find.d.ts.map +1 -0
  12. package/dist/tools/find.js +143 -0
  13. package/dist/tools/find.js.map +1 -0
  14. package/dist/tools/grep.d.ts +13 -0
  15. package/dist/tools/grep.d.ts.map +1 -0
  16. package/dist/tools/grep.js +207 -0
  17. package/dist/tools/grep.js.map +1 -0
  18. package/dist/tools/index.d.ts +42 -0
  19. package/dist/tools/index.d.ts.map +1 -1
  20. package/dist/tools/index.js +17 -0
  21. package/dist/tools/index.js.map +1 -1
  22. package/dist/tools/ls.d.ts +8 -0
  23. package/dist/tools/ls.d.ts.map +1 -0
  24. package/dist/tools/ls.js +100 -0
  25. package/dist/tools/ls.js.map +1 -0
  26. package/dist/tools-manager.d.ts +3 -0
  27. package/dist/tools-manager.d.ts.map +1 -0
  28. package/dist/tools-manager.js +186 -0
  29. package/dist/tools-manager.js.map +1 -0
  30. package/dist/tui/footer.d.ts +12 -0
  31. package/dist/tui/footer.d.ts.map +1 -1
  32. package/dist/tui/footer.js +42 -1
  33. package/dist/tui/footer.js.map +1 -1
  34. package/dist/tui/tool-execution.d.ts.map +1 -1
  35. package/dist/tui/tool-execution.js +77 -0
  36. package/dist/tui/tool-execution.js.map +1 -1
  37. package/dist/tui/tui-renderer.d.ts +1 -1
  38. package/dist/tui/tui-renderer.d.ts.map +1 -1
  39. package/dist/tui/tui-renderer.js +8 -2
  40. package/dist/tui/tui-renderer.js.map +1 -1
  41. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- import { readFileSync, writeFileSync } from "fs";
1
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { basename, dirname, join } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -867,4 +867,665 @@ export function exportSessionToHtml(sessionManager, state, outputPath) {
867
867
  writeFileSync(outputPath, html, "utf8");
868
868
  return outputPath;
869
869
  }
870
+ /**
871
+ * Parse session manager format (type: "session", "message", "model_change")
872
+ */
873
+ function parseSessionManagerFormat(lines) {
874
+ const data = {
875
+ sessionId: "unknown",
876
+ timestamp: new Date().toISOString(),
877
+ modelsUsed: new Set(),
878
+ messages: [],
879
+ toolResultsMap: new Map(),
880
+ sessionEvents: [],
881
+ tokenStats: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
882
+ costStats: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
883
+ };
884
+ for (const line of lines) {
885
+ try {
886
+ const entry = JSON.parse(line);
887
+ if (entry.type === "session") {
888
+ data.sessionId = entry.id || "unknown";
889
+ data.timestamp = entry.timestamp || data.timestamp;
890
+ data.cwd = entry.cwd;
891
+ data.systemPrompt = entry.systemPrompt;
892
+ if (entry.modelId) {
893
+ const modelInfo = entry.provider ? `${entry.provider}/${entry.modelId}` : entry.modelId;
894
+ data.modelsUsed.add(modelInfo);
895
+ }
896
+ }
897
+ else if (entry.type === "message") {
898
+ data.messages.push(entry.message);
899
+ data.sessionEvents.push(entry);
900
+ if (entry.message.role === "toolResult") {
901
+ data.toolResultsMap.set(entry.message.toolCallId, entry.message);
902
+ }
903
+ if (entry.message.role === "assistant" && entry.message.usage) {
904
+ const usage = entry.message.usage;
905
+ data.tokenStats.input += usage.input || 0;
906
+ data.tokenStats.output += usage.output || 0;
907
+ data.tokenStats.cacheRead += usage.cacheRead || 0;
908
+ data.tokenStats.cacheWrite += usage.cacheWrite || 0;
909
+ if (usage.cost) {
910
+ data.costStats.input += usage.cost.input || 0;
911
+ data.costStats.output += usage.cost.output || 0;
912
+ data.costStats.cacheRead += usage.cost.cacheRead || 0;
913
+ data.costStats.cacheWrite += usage.cost.cacheWrite || 0;
914
+ }
915
+ }
916
+ }
917
+ else if (entry.type === "model_change") {
918
+ data.sessionEvents.push(entry);
919
+ if (entry.modelId) {
920
+ const modelInfo = entry.provider ? `${entry.provider}/${entry.modelId}` : entry.modelId;
921
+ data.modelsUsed.add(modelInfo);
922
+ }
923
+ }
924
+ }
925
+ catch {
926
+ // Skip malformed lines
927
+ }
928
+ }
929
+ return data;
930
+ }
931
+ /**
932
+ * Parse streaming event format (type: "agent_start", "message_start", "message_end", etc.)
933
+ */
934
+ function parseStreamingEventFormat(lines) {
935
+ const data = {
936
+ sessionId: "unknown",
937
+ timestamp: new Date().toISOString(),
938
+ modelsUsed: new Set(),
939
+ messages: [],
940
+ toolResultsMap: new Map(),
941
+ sessionEvents: [],
942
+ tokenStats: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
943
+ costStats: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
944
+ isStreamingFormat: true,
945
+ };
946
+ let timestampSet = false;
947
+ // Track messages by collecting message_end events (which have the final state)
948
+ for (const line of lines) {
949
+ try {
950
+ const entry = JSON.parse(line);
951
+ if (entry.type === "message_end" && entry.message) {
952
+ const msg = entry.message;
953
+ data.messages.push(msg);
954
+ data.sessionEvents.push({ type: "message", message: msg, timestamp: msg.timestamp });
955
+ // Build tool results map
956
+ if (msg.role === "toolResult") {
957
+ data.toolResultsMap.set(msg.toolCallId, msg);
958
+ }
959
+ // Track models and accumulate stats from assistant messages
960
+ if (msg.role === "assistant") {
961
+ if (msg.model) {
962
+ const modelInfo = msg.provider ? `${msg.provider}/${msg.model}` : msg.model;
963
+ data.modelsUsed.add(modelInfo);
964
+ }
965
+ if (msg.usage) {
966
+ data.tokenStats.input += msg.usage.input || 0;
967
+ data.tokenStats.output += msg.usage.output || 0;
968
+ data.tokenStats.cacheRead += msg.usage.cacheRead || 0;
969
+ data.tokenStats.cacheWrite += msg.usage.cacheWrite || 0;
970
+ if (msg.usage.cost) {
971
+ data.costStats.input += msg.usage.cost.input || 0;
972
+ data.costStats.output += msg.usage.cost.output || 0;
973
+ data.costStats.cacheRead += msg.usage.cost.cacheRead || 0;
974
+ data.costStats.cacheWrite += msg.usage.cost.cacheWrite || 0;
975
+ }
976
+ }
977
+ }
978
+ // Use first message timestamp as session timestamp
979
+ if (!timestampSet && msg.timestamp) {
980
+ data.timestamp = new Date(msg.timestamp).toISOString();
981
+ timestampSet = true;
982
+ }
983
+ }
984
+ }
985
+ catch {
986
+ // Skip malformed lines
987
+ }
988
+ }
989
+ // Generate a session ID from the timestamp
990
+ data.sessionId = `stream-${data.timestamp.replace(/[:.]/g, "-")}`;
991
+ return data;
992
+ }
993
+ /**
994
+ * Detect the format of a session file by examining the first valid JSON line
995
+ */
996
+ function detectFormat(lines) {
997
+ for (const line of lines) {
998
+ try {
999
+ const entry = JSON.parse(line);
1000
+ if (entry.type === "session")
1001
+ return "session-manager";
1002
+ if (entry.type === "agent_start" || entry.type === "message_start" || entry.type === "turn_start") {
1003
+ return "streaming-events";
1004
+ }
1005
+ }
1006
+ catch {
1007
+ // Skip malformed lines
1008
+ }
1009
+ }
1010
+ return "unknown";
1011
+ }
1012
+ /**
1013
+ * Generate HTML from parsed session data
1014
+ */
1015
+ function generateHtml(data, inputFilename) {
1016
+ // Calculate message stats
1017
+ const userMessages = data.messages.filter((m) => m.role === "user").length;
1018
+ const assistantMessages = data.messages.filter((m) => m.role === "assistant").length;
1019
+ // Count tool calls from assistant messages
1020
+ let toolCallsCount = 0;
1021
+ for (const message of data.messages) {
1022
+ if (message.role === "assistant") {
1023
+ const assistantMsg = message;
1024
+ toolCallsCount += assistantMsg.content.filter((c) => c.type === "toolCall").length;
1025
+ }
1026
+ }
1027
+ // Get last assistant message for context info
1028
+ const lastAssistantMessage = data.messages
1029
+ .slice()
1030
+ .reverse()
1031
+ .find((m) => m.role === "assistant" && m.stopReason !== "aborted");
1032
+ const contextTokens = lastAssistantMessage
1033
+ ? lastAssistantMessage.usage.input +
1034
+ lastAssistantMessage.usage.output +
1035
+ lastAssistantMessage.usage.cacheRead +
1036
+ lastAssistantMessage.usage.cacheWrite
1037
+ : 0;
1038
+ const lastModel = lastAssistantMessage?.model || "unknown";
1039
+ const lastProvider = lastAssistantMessage?.provider || "";
1040
+ const lastModelInfo = lastProvider ? `${lastProvider}/${lastModel}` : lastModel;
1041
+ // Generate messages HTML
1042
+ let messagesHtml = "";
1043
+ for (const event of data.sessionEvents) {
1044
+ if (event.type === "message" && event.message.role !== "toolResult") {
1045
+ messagesHtml += formatMessage(event.message, data.toolResultsMap);
1046
+ }
1047
+ else if (event.type === "model_change") {
1048
+ messagesHtml += formatModelChange(event);
1049
+ }
1050
+ }
1051
+ // Tools section (only if tools info available)
1052
+ const toolsHtml = data.tools
1053
+ ? `
1054
+ <div class="tools-list">
1055
+ <div class="tools-header">Available Tools</div>
1056
+ <div class="tools-content">
1057
+ ${data.tools.map((tool) => `<div class="tool-item"><span class="tool-item-name">${escapeHtml(tool.name)}</span> - ${escapeHtml(tool.description)}</div>`).join("")}
1058
+ </div>
1059
+ </div>`
1060
+ : "";
1061
+ // System prompt section (only if available)
1062
+ const systemPromptHtml = data.systemPrompt
1063
+ ? `
1064
+ <div class="system-prompt">
1065
+ <div class="system-prompt-header">System Prompt</div>
1066
+ <div class="system-prompt-content">${escapeHtml(data.systemPrompt)}</div>
1067
+ </div>`
1068
+ : "";
1069
+ return `<!DOCTYPE html>
1070
+ <html lang="en">
1071
+ <head>
1072
+ <meta charset="UTF-8">
1073
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1074
+ <title>Session Export - ${escapeHtml(inputFilename)}</title>
1075
+ <style>
1076
+ * {
1077
+ margin: 0;
1078
+ padding: 0;
1079
+ box-sizing: border-box;
1080
+ }
1081
+
1082
+ body {
1083
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
1084
+ font-size: 12px;
1085
+ line-height: 1.6;
1086
+ color: ${COLORS.text};
1087
+ background: ${COLORS.bodyBg};
1088
+ padding: 24px;
1089
+ }
1090
+
1091
+ .container {
1092
+ max-width: 700px;
1093
+ margin: 0 auto;
1094
+ }
1095
+
1096
+ .header {
1097
+ margin-bottom: 24px;
1098
+ padding: 16px;
1099
+ background: ${COLORS.containerBg};
1100
+ border-radius: 4px;
1101
+ }
1102
+
1103
+ .header h1 {
1104
+ font-size: 14px;
1105
+ font-weight: bold;
1106
+ margin-bottom: 12px;
1107
+ color: ${COLORS.cyan};
1108
+ }
1109
+
1110
+ .header-info {
1111
+ display: flex;
1112
+ flex-direction: column;
1113
+ gap: 3px;
1114
+ font-size: 11px;
1115
+ }
1116
+
1117
+ .info-item {
1118
+ color: ${COLORS.textDim};
1119
+ display: flex;
1120
+ align-items: baseline;
1121
+ }
1122
+
1123
+ .info-label {
1124
+ font-weight: 600;
1125
+ margin-right: 8px;
1126
+ min-width: 100px;
1127
+ }
1128
+
1129
+ .info-value {
1130
+ color: ${COLORS.text};
1131
+ flex: 1;
1132
+ }
1133
+
1134
+ .info-value.cost {
1135
+ font-family: 'SF Mono', monospace;
1136
+ }
1137
+
1138
+ .messages {
1139
+ display: flex;
1140
+ flex-direction: column;
1141
+ gap: 16px;
1142
+ }
1143
+
1144
+ .message-timestamp {
1145
+ font-size: 10px;
1146
+ color: ${COLORS.textDim};
1147
+ margin-bottom: 4px;
1148
+ opacity: 0.8;
1149
+ }
1150
+
1151
+ .user-message {
1152
+ background: ${COLORS.userMessageBg};
1153
+ padding: 12px 16px;
1154
+ border-radius: 4px;
1155
+ white-space: pre-wrap;
1156
+ word-wrap: break-word;
1157
+ overflow-wrap: break-word;
1158
+ word-break: break-word;
1159
+ }
1160
+
1161
+ .assistant-message {
1162
+ padding: 0;
1163
+ }
1164
+
1165
+ .assistant-text {
1166
+ padding: 12px 16px;
1167
+ white-space: pre-wrap;
1168
+ word-wrap: break-word;
1169
+ overflow-wrap: break-word;
1170
+ word-break: break-word;
1171
+ }
1172
+
1173
+ .thinking-text {
1174
+ padding: 12px 16px;
1175
+ color: ${COLORS.italic};
1176
+ font-style: italic;
1177
+ white-space: pre-wrap;
1178
+ word-wrap: break-word;
1179
+ overflow-wrap: break-word;
1180
+ word-break: break-word;
1181
+ }
1182
+
1183
+ .model-change {
1184
+ padding: 8px 16px;
1185
+ background: rgb(40, 40, 50);
1186
+ border-radius: 4px;
1187
+ }
1188
+
1189
+ .model-change-text {
1190
+ color: ${COLORS.textDim};
1191
+ font-size: 11px;
1192
+ }
1193
+
1194
+ .model-name {
1195
+ color: ${COLORS.cyan};
1196
+ font-weight: bold;
1197
+ }
1198
+
1199
+ .tool-execution {
1200
+ padding: 12px 16px;
1201
+ border-radius: 4px;
1202
+ margin-top: 8px;
1203
+ }
1204
+
1205
+ .tool-header {
1206
+ font-weight: bold;
1207
+ }
1208
+
1209
+ .tool-name {
1210
+ font-weight: bold;
1211
+ }
1212
+
1213
+ .tool-path {
1214
+ color: ${COLORS.cyan};
1215
+ word-break: break-all;
1216
+ }
1217
+
1218
+ .line-count {
1219
+ color: ${COLORS.textDim};
1220
+ }
1221
+
1222
+ .tool-command {
1223
+ font-weight: bold;
1224
+ white-space: pre-wrap;
1225
+ word-wrap: break-word;
1226
+ overflow-wrap: break-word;
1227
+ word-break: break-word;
1228
+ }
1229
+
1230
+ .tool-output {
1231
+ margin-top: 12px;
1232
+ color: ${COLORS.textDim};
1233
+ white-space: pre-wrap;
1234
+ word-wrap: break-word;
1235
+ overflow-wrap: break-word;
1236
+ word-break: break-word;
1237
+ font-family: inherit;
1238
+ overflow-x: auto;
1239
+ }
1240
+
1241
+ .tool-output > div {
1242
+ line-height: 1.4;
1243
+ }
1244
+
1245
+ .tool-output pre {
1246
+ margin: 0;
1247
+ font-family: inherit;
1248
+ color: inherit;
1249
+ white-space: pre-wrap;
1250
+ word-wrap: break-word;
1251
+ overflow-wrap: break-word;
1252
+ }
1253
+
1254
+ .tool-output.expandable {
1255
+ cursor: pointer;
1256
+ }
1257
+
1258
+ .tool-output.expandable:hover {
1259
+ opacity: 0.9;
1260
+ }
1261
+
1262
+ .tool-output.expandable .output-full {
1263
+ display: none;
1264
+ }
1265
+
1266
+ .tool-output.expandable.expanded .output-preview {
1267
+ display: none;
1268
+ }
1269
+
1270
+ .tool-output.expandable.expanded .output-full {
1271
+ display: block;
1272
+ }
1273
+
1274
+ .expand-hint {
1275
+ color: ${COLORS.cyan};
1276
+ font-style: italic;
1277
+ margin-top: 4px;
1278
+ }
1279
+
1280
+ .system-prompt {
1281
+ background: rgb(60, 55, 40);
1282
+ padding: 12px 16px;
1283
+ border-radius: 4px;
1284
+ margin-bottom: 16px;
1285
+ }
1286
+
1287
+ .system-prompt-header {
1288
+ font-weight: bold;
1289
+ color: ${COLORS.yellow};
1290
+ margin-bottom: 8px;
1291
+ }
1292
+
1293
+ .system-prompt-content {
1294
+ color: ${COLORS.textDim};
1295
+ white-space: pre-wrap;
1296
+ word-wrap: break-word;
1297
+ overflow-wrap: break-word;
1298
+ word-break: break-word;
1299
+ font-size: 11px;
1300
+ }
1301
+
1302
+ .tools-list {
1303
+ background: rgb(60, 55, 40);
1304
+ padding: 12px 16px;
1305
+ border-radius: 4px;
1306
+ margin-bottom: 16px;
1307
+ }
1308
+
1309
+ .tools-header {
1310
+ font-weight: bold;
1311
+ color: ${COLORS.yellow};
1312
+ margin-bottom: 8px;
1313
+ }
1314
+
1315
+ .tools-content {
1316
+ color: ${COLORS.textDim};
1317
+ font-size: 11px;
1318
+ }
1319
+
1320
+ .tool-item {
1321
+ margin: 4px 0;
1322
+ }
1323
+
1324
+ .tool-item-name {
1325
+ font-weight: bold;
1326
+ color: ${COLORS.text};
1327
+ }
1328
+
1329
+ .tool-diff {
1330
+ margin-top: 12px;
1331
+ font-size: 11px;
1332
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
1333
+ overflow-x: auto;
1334
+ max-width: 100%;
1335
+ }
1336
+
1337
+ .diff-line-old {
1338
+ color: ${COLORS.red};
1339
+ white-space: pre-wrap;
1340
+ word-wrap: break-word;
1341
+ overflow-wrap: break-word;
1342
+ }
1343
+
1344
+ .diff-line-new {
1345
+ color: ${COLORS.green};
1346
+ white-space: pre-wrap;
1347
+ word-wrap: break-word;
1348
+ overflow-wrap: break-word;
1349
+ }
1350
+
1351
+ .diff-line-context {
1352
+ color: ${COLORS.textDim};
1353
+ white-space: pre-wrap;
1354
+ word-wrap: break-word;
1355
+ overflow-wrap: break-word;
1356
+ }
1357
+
1358
+ .error-text {
1359
+ color: ${COLORS.red};
1360
+ padding: 12px 16px;
1361
+ }
1362
+
1363
+ .footer {
1364
+ margin-top: 48px;
1365
+ padding: 20px;
1366
+ text-align: center;
1367
+ color: ${COLORS.textDim};
1368
+ font-size: 10px;
1369
+ }
1370
+
1371
+ .streaming-notice {
1372
+ background: rgb(50, 45, 35);
1373
+ padding: 12px 16px;
1374
+ border-radius: 4px;
1375
+ margin-bottom: 16px;
1376
+ color: ${COLORS.textDim};
1377
+ font-size: 11px;
1378
+ }
1379
+
1380
+ @media print {
1381
+ body {
1382
+ background: white;
1383
+ color: black;
1384
+ }
1385
+ .tool-execution {
1386
+ border: 1px solid #ddd;
1387
+ }
1388
+ }
1389
+ </style>
1390
+ </head>
1391
+ <body>
1392
+ <div class="container">
1393
+ <div class="header">
1394
+ <h1>pi v${VERSION}</h1>
1395
+ <div class="header-info">
1396
+ <div class="info-item">
1397
+ <span class="info-label">Session:</span>
1398
+ <span class="info-value">${escapeHtml(data.sessionId)}</span>
1399
+ </div>
1400
+ <div class="info-item">
1401
+ <span class="info-label">Date:</span>
1402
+ <span class="info-value">${new Date(data.timestamp).toLocaleString()}</span>
1403
+ </div>
1404
+ <div class="info-item">
1405
+ <span class="info-label">Models:</span>
1406
+ <span class="info-value">${Array.from(data.modelsUsed)
1407
+ .map((m) => escapeHtml(m))
1408
+ .join(", ") || "unknown"}</span>
1409
+ </div>
1410
+ </div>
1411
+ </div>
1412
+
1413
+ <div class="header">
1414
+ <h1>Messages</h1>
1415
+ <div class="header-info">
1416
+ <div class="info-item">
1417
+ <span class="info-label">User:</span>
1418
+ <span class="info-value">${userMessages}</span>
1419
+ </div>
1420
+ <div class="info-item">
1421
+ <span class="info-label">Assistant:</span>
1422
+ <span class="info-value">${assistantMessages}</span>
1423
+ </div>
1424
+ <div class="info-item">
1425
+ <span class="info-label">Tool Calls:</span>
1426
+ <span class="info-value">${toolCallsCount}</span>
1427
+ </div>
1428
+ </div>
1429
+ </div>
1430
+
1431
+ <div class="header">
1432
+ <h1>Tokens & Cost</h1>
1433
+ <div class="header-info">
1434
+ <div class="info-item">
1435
+ <span class="info-label">Input:</span>
1436
+ <span class="info-value">${data.tokenStats.input.toLocaleString()} tokens</span>
1437
+ </div>
1438
+ <div class="info-item">
1439
+ <span class="info-label">Output:</span>
1440
+ <span class="info-value">${data.tokenStats.output.toLocaleString()} tokens</span>
1441
+ </div>
1442
+ <div class="info-item">
1443
+ <span class="info-label">Cache Read:</span>
1444
+ <span class="info-value">${data.tokenStats.cacheRead.toLocaleString()} tokens</span>
1445
+ </div>
1446
+ <div class="info-item">
1447
+ <span class="info-label">Cache Write:</span>
1448
+ <span class="info-value">${data.tokenStats.cacheWrite.toLocaleString()} tokens</span>
1449
+ </div>
1450
+ <div class="info-item">
1451
+ <span class="info-label">Total:</span>
1452
+ <span class="info-value">${(data.tokenStats.input + data.tokenStats.output + data.tokenStats.cacheRead + data.tokenStats.cacheWrite).toLocaleString()} tokens</span>
1453
+ </div>
1454
+ <div class="info-item">
1455
+ <span class="info-label">Input Cost:</span>
1456
+ <span class="info-value cost">$${data.costStats.input.toFixed(4)}</span>
1457
+ </div>
1458
+ <div class="info-item">
1459
+ <span class="info-label">Output Cost:</span>
1460
+ <span class="info-value cost">$${data.costStats.output.toFixed(4)}</span>
1461
+ </div>
1462
+ <div class="info-item">
1463
+ <span class="info-label">Cache Read Cost:</span>
1464
+ <span class="info-value cost">$${data.costStats.cacheRead.toFixed(4)}</span>
1465
+ </div>
1466
+ <div class="info-item">
1467
+ <span class="info-label">Cache Write Cost:</span>
1468
+ <span class="info-value cost">$${data.costStats.cacheWrite.toFixed(4)}</span>
1469
+ </div>
1470
+ <div class="info-item">
1471
+ <span class="info-label">Total Cost:</span>
1472
+ <span class="info-value cost"><strong>$${(data.costStats.input + data.costStats.output + data.costStats.cacheRead + data.costStats.cacheWrite).toFixed(4)}</strong></span>
1473
+ </div>
1474
+ <div class="info-item">
1475
+ <span class="info-label">Context Usage:</span>
1476
+ <span class="info-value">${contextTokens.toLocaleString()} tokens (last turn) - ${escapeHtml(lastModelInfo)}</span>
1477
+ </div>
1478
+ </div>
1479
+ </div>
1480
+
1481
+ ${systemPromptHtml}
1482
+ ${toolsHtml}
1483
+
1484
+ ${data.isStreamingFormat
1485
+ ? `<div class="streaming-notice">
1486
+ <em>Note: This session was reconstructed from raw agent event logs, which do not contain system prompt or tool definitions.</em>
1487
+ </div>`
1488
+ : ""}
1489
+
1490
+ <div class="messages">
1491
+ ${messagesHtml}
1492
+ </div>
1493
+
1494
+ <div class="footer">
1495
+ Generated by pi coding-agent on ${new Date().toLocaleString()}
1496
+ </div>
1497
+ </div>
1498
+ </body>
1499
+ </html>`;
1500
+ }
1501
+ /**
1502
+ * Export a session file to HTML (standalone, without AgentState or SessionManager)
1503
+ * Auto-detects format: session manager format or streaming event format
1504
+ */
1505
+ export function exportFromFile(inputPath, outputPath) {
1506
+ if (!existsSync(inputPath)) {
1507
+ throw new Error(`File not found: ${inputPath}`);
1508
+ }
1509
+ const content = readFileSync(inputPath, "utf8");
1510
+ const lines = content
1511
+ .trim()
1512
+ .split("\n")
1513
+ .filter((l) => l.trim());
1514
+ if (lines.length === 0) {
1515
+ throw new Error(`Empty file: ${inputPath}`);
1516
+ }
1517
+ const format = detectFormat(lines);
1518
+ if (format === "unknown") {
1519
+ throw new Error(`Unknown session file format: ${inputPath}`);
1520
+ }
1521
+ const data = format === "session-manager" ? parseSessionManagerFormat(lines) : parseStreamingEventFormat(lines);
1522
+ // Generate output path if not provided
1523
+ if (!outputPath) {
1524
+ const inputBasename = basename(inputPath, ".jsonl");
1525
+ outputPath = `pi-session-${inputBasename}.html`;
1526
+ }
1527
+ const html = generateHtml(data, basename(inputPath));
1528
+ writeFileSync(outputPath, html, "utf8");
1529
+ return outputPath;
1530
+ }
870
1531
  //# sourceMappingURL=export-html.js.map