@poco-ai/tokenarena 0.2.0 → 0.2.2-beta.2

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/dist/index.js CHANGED
@@ -211,6 +211,17 @@ function readFileSafe(filePath) {
211
211
  return null;
212
212
  }
213
213
  }
214
+ function parseJsonl(content) {
215
+ const results = [];
216
+ for (const line of content.split("\n")) {
217
+ if (!line.trim()) continue;
218
+ try {
219
+ results.push(JSON.parse(line));
220
+ } catch {
221
+ }
222
+ }
223
+ return results;
224
+ }
214
225
  function extractSessionId(filePath) {
215
226
  return basename(filePath, ".jsonl");
216
227
  }
@@ -1098,37 +1109,657 @@ var OpenClawParser = class {
1098
1109
  };
1099
1110
  registerParser(new OpenClawParser());
1100
1111
 
1112
+ // src/parsers/qwen-code.ts
1113
+ import { existsSync as existsSync8, readdirSync as readdirSync6 } from "fs";
1114
+ import { homedir as homedir7 } from "os";
1115
+ import { join as join8 } from "path";
1116
+ var TOOL_ID = "qwen-code";
1117
+ var TOOL_NAME = "Qwen Code";
1118
+ var DEFAULT_DATA_DIR2 = join8(homedir7(), ".qwen", "tmp");
1119
+ function createToolDefinition(dataDir) {
1120
+ return {
1121
+ id: TOOL_ID,
1122
+ name: TOOL_NAME,
1123
+ dataDir
1124
+ };
1125
+ }
1126
+ function toSafeNumber(value) {
1127
+ const numberValue = Number(value);
1128
+ return Number.isFinite(numberValue) ? numberValue : 0;
1129
+ }
1130
+ function getPathLeaf2(value) {
1131
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
1132
+ const leaf = normalized.split("/").filter(Boolean).pop();
1133
+ return leaf || "unknown";
1134
+ }
1135
+ function normalizeForPrefix(value) {
1136
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
1137
+ }
1138
+ function findSessionFiles2(baseDir) {
1139
+ const results = [];
1140
+ if (!existsSync8(baseDir)) return results;
1141
+ try {
1142
+ for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
1143
+ if (!entry.isDirectory()) continue;
1144
+ const chatsDir = join8(baseDir, entry.name, "chats");
1145
+ if (!existsSync8(chatsDir)) continue;
1146
+ try {
1147
+ for (const file of readdirSync6(chatsDir)) {
1148
+ if (file.endsWith(".jsonl")) {
1149
+ results.push(join8(chatsDir, file));
1150
+ }
1151
+ }
1152
+ } catch {
1153
+ }
1154
+ }
1155
+ } catch {
1156
+ return results;
1157
+ }
1158
+ return results;
1159
+ }
1160
+ function resolveQwenProject(cwd, filePath, dataDir = DEFAULT_DATA_DIR2) {
1161
+ if (cwd) {
1162
+ return getPathLeaf2(cwd);
1163
+ }
1164
+ const normalizedFilePath = normalizeForPrefix(filePath);
1165
+ const normalizedDataDir = normalizeForPrefix(dataDir);
1166
+ const prefix = `${normalizedDataDir}/`;
1167
+ if (!normalizedFilePath.startsWith(prefix)) {
1168
+ return "unknown";
1169
+ }
1170
+ const relativePath = normalizedFilePath.slice(prefix.length);
1171
+ const projectId = relativePath.split("/")[0];
1172
+ return projectId || "unknown";
1173
+ }
1174
+ var QwenCodeParser = class {
1175
+ constructor(dataDir = DEFAULT_DATA_DIR2) {
1176
+ this.dataDir = dataDir;
1177
+ this.tool = createToolDefinition(dataDir);
1178
+ }
1179
+ tool;
1180
+ async parse() {
1181
+ const sessionFiles = findSessionFiles2(this.dataDir);
1182
+ if (sessionFiles.length === 0) {
1183
+ return { buckets: [], sessions: [] };
1184
+ }
1185
+ const entries = [];
1186
+ const sessionEvents = [];
1187
+ const seenUuids = /* @__PURE__ */ new Set();
1188
+ for (const filePath of sessionFiles) {
1189
+ const content = readFileSafe(filePath);
1190
+ if (!content) continue;
1191
+ const sessionId = filePath;
1192
+ for (const line of content.split("\n")) {
1193
+ if (!line.trim()) continue;
1194
+ try {
1195
+ const obj = JSON.parse(line);
1196
+ if (!obj.timestamp) continue;
1197
+ const timestamp = new Date(obj.timestamp);
1198
+ if (Number.isNaN(timestamp.getTime())) continue;
1199
+ const project = resolveQwenProject(obj.cwd, filePath, this.dataDir);
1200
+ if (obj.type === "user" || obj.type === "assistant") {
1201
+ sessionEvents.push({
1202
+ sessionId,
1203
+ source: TOOL_ID,
1204
+ project,
1205
+ timestamp,
1206
+ role: obj.type
1207
+ });
1208
+ }
1209
+ if (obj.type !== "assistant") continue;
1210
+ const usage = obj.usageMetadata || obj.usage;
1211
+ if (!usage) continue;
1212
+ const totalInput = toSafeNumber(usage.promptTokenCount) || toSafeNumber(usage.input_tokens);
1213
+ const totalOutput = toSafeNumber(usage.candidatesTokenCount) || toSafeNumber(usage.output_tokens);
1214
+ const cachedTokens = toSafeNumber(usage.cachedContentTokenCount);
1215
+ const reasoningTokens = toSafeNumber(usage.thoughtsTokenCount);
1216
+ if (totalInput === 0 && totalOutput === 0 && cachedTokens === 0 && reasoningTokens === 0) {
1217
+ continue;
1218
+ }
1219
+ if (obj.uuid) {
1220
+ if (seenUuids.has(obj.uuid)) continue;
1221
+ seenUuids.add(obj.uuid);
1222
+ }
1223
+ entries.push({
1224
+ sessionId,
1225
+ source: TOOL_ID,
1226
+ model: obj.model || "unknown",
1227
+ project,
1228
+ timestamp,
1229
+ inputTokens: Math.max(0, totalInput - cachedTokens),
1230
+ outputTokens: Math.max(0, totalOutput - reasoningTokens),
1231
+ reasoningTokens,
1232
+ cachedTokens
1233
+ });
1234
+ } catch {
1235
+ }
1236
+ }
1237
+ }
1238
+ return {
1239
+ buckets: aggregateToBuckets(entries),
1240
+ sessions: extractSessions(sessionEvents, entries)
1241
+ };
1242
+ }
1243
+ isInstalled() {
1244
+ return existsSync8(this.dataDir);
1245
+ }
1246
+ };
1247
+ registerParser(new QwenCodeParser());
1248
+
1249
+ // src/parsers/kimi-code.ts
1250
+ import { existsSync as existsSync9, readdirSync as readdirSync7 } from "fs";
1251
+ import { homedir as homedir8 } from "os";
1252
+ import { join as join9 } from "path";
1253
+ var TOOL_ID2 = "kimi-code";
1254
+ var TOOL_NAME2 = "Kimi Code";
1255
+ var DEFAULT_SESSIONS_DIR = join9(homedir8(), ".kimi", "sessions");
1256
+ var DEFAULT_CONFIG_PATH = join9(homedir8(), ".kimi", "kimi.json");
1257
+ var USER_EVENT_TYPES = /* @__PURE__ */ new Set(["UserMessage", "user_message", "Input"]);
1258
+ var ASSISTANT_EVENT_TYPES = /* @__PURE__ */ new Set([
1259
+ "AssistantMessage",
1260
+ "assistant_message",
1261
+ "Output",
1262
+ "ModelOutput",
1263
+ "AssistantOutput"
1264
+ ]);
1265
+ function createToolDefinition2(dataDir) {
1266
+ return {
1267
+ id: TOOL_ID2,
1268
+ name: TOOL_NAME2,
1269
+ dataDir
1270
+ };
1271
+ }
1272
+ function toSafeNumber2(value) {
1273
+ const numberValue = Number(value);
1274
+ return Number.isFinite(numberValue) ? numberValue : 0;
1275
+ }
1276
+ function getPathLeaf3(value) {
1277
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
1278
+ const leaf = normalized.split("/").filter(Boolean).pop();
1279
+ return leaf || "unknown";
1280
+ }
1281
+ function findWireFiles(baseDir) {
1282
+ const results = [];
1283
+ if (!existsSync9(baseDir)) return results;
1284
+ try {
1285
+ for (const workDir of readdirSync7(baseDir, { withFileTypes: true })) {
1286
+ if (!workDir.isDirectory()) continue;
1287
+ const workDirPath = join9(baseDir, workDir.name);
1288
+ try {
1289
+ for (const session of readdirSync7(workDirPath, {
1290
+ withFileTypes: true
1291
+ })) {
1292
+ if (!session.isDirectory()) continue;
1293
+ const wireFile = join9(workDirPath, session.name, "wire.jsonl");
1294
+ if (existsSync9(wireFile)) {
1295
+ results.push({ filePath: wireFile, workDirHash: workDir.name });
1296
+ }
1297
+ }
1298
+ } catch {
1299
+ }
1300
+ }
1301
+ } catch {
1302
+ return results;
1303
+ }
1304
+ return results;
1305
+ }
1306
+ function parseTimestamp(value) {
1307
+ if (value == null) return null;
1308
+ const timestamp = new Date(value);
1309
+ return Number.isNaN(timestamp.getTime()) ? null : timestamp;
1310
+ }
1311
+ function classifyKimiRole(type, payload) {
1312
+ if (payload?.role === "user" || payload?.role === "assistant") {
1313
+ return payload.role;
1314
+ }
1315
+ if (type && USER_EVENT_TYPES.has(type)) {
1316
+ return "user";
1317
+ }
1318
+ if (type && ASSISTANT_EVENT_TYPES.has(type)) {
1319
+ return "assistant";
1320
+ }
1321
+ if (type?.toLowerCase().includes("assistant")) {
1322
+ return "assistant";
1323
+ }
1324
+ return null;
1325
+ }
1326
+ function loadProjectMap(configPath) {
1327
+ const projectMap = /* @__PURE__ */ new Map();
1328
+ const content = readFileSafe(configPath);
1329
+ if (!content) return projectMap;
1330
+ try {
1331
+ const config = JSON.parse(content);
1332
+ const workspaces = config.workspaces || config.projects || {};
1333
+ for (const [hash, info] of Object.entries(workspaces)) {
1334
+ const pathValue = typeof info === "string" ? info : info.path || info.dir || void 0;
1335
+ if (!pathValue) continue;
1336
+ projectMap.set(hash, getPathLeaf3(pathValue));
1337
+ }
1338
+ } catch {
1339
+ }
1340
+ return projectMap;
1341
+ }
1342
+ var KimiCodeParser = class {
1343
+ tool;
1344
+ sessionsDir;
1345
+ configPath;
1346
+ constructor(options = {}) {
1347
+ this.sessionsDir = options.sessionsDir || DEFAULT_SESSIONS_DIR;
1348
+ this.configPath = options.configPath || DEFAULT_CONFIG_PATH;
1349
+ this.tool = createToolDefinition2(this.sessionsDir);
1350
+ }
1351
+ async parse() {
1352
+ const wireFiles = findWireFiles(this.sessionsDir);
1353
+ if (wireFiles.length === 0) {
1354
+ return { buckets: [], sessions: [] };
1355
+ }
1356
+ const projectMap = loadProjectMap(this.configPath);
1357
+ const entries = [];
1358
+ const sessionEvents = [];
1359
+ const seenMessageIds = /* @__PURE__ */ new Set();
1360
+ for (const { filePath, workDirHash } of wireFiles) {
1361
+ const content = readFileSafe(filePath);
1362
+ if (!content) continue;
1363
+ const sessionId = filePath;
1364
+ const project = projectMap.get(workDirHash) || workDirHash;
1365
+ let currentModel = "unknown";
1366
+ let lastTimestampRaw;
1367
+ for (const line of content.split("\n")) {
1368
+ if (!line.trim()) continue;
1369
+ let obj;
1370
+ try {
1371
+ obj = JSON.parse(line);
1372
+ } catch {
1373
+ continue;
1374
+ }
1375
+ const payload = obj.payload;
1376
+ if (!payload) continue;
1377
+ if (payload.model) {
1378
+ currentModel = payload.model;
1379
+ }
1380
+ const timestampValue = payload.timestamp ?? obj.timestamp ?? lastTimestampRaw;
1381
+ const timestamp = parseTimestamp(timestampValue);
1382
+ if (payload.timestamp != null) {
1383
+ lastTimestampRaw = payload.timestamp;
1384
+ } else if (obj.timestamp != null) {
1385
+ lastTimestampRaw = obj.timestamp;
1386
+ }
1387
+ const role = classifyKimiRole(obj.type, payload);
1388
+ if (role && timestamp) {
1389
+ sessionEvents.push({
1390
+ sessionId,
1391
+ source: TOOL_ID2,
1392
+ project,
1393
+ timestamp,
1394
+ role
1395
+ });
1396
+ }
1397
+ if (obj.type !== "StatusUpdate") continue;
1398
+ const tokenUsage = payload.token_usage;
1399
+ if (!tokenUsage || !timestamp) continue;
1400
+ const inputTokens = toSafeNumber2(tokenUsage.input_other);
1401
+ const outputTokens = toSafeNumber2(tokenUsage.output);
1402
+ const cachedTokens = toSafeNumber2(tokenUsage.input_cache_read);
1403
+ const cacheCreateTokens = toSafeNumber2(tokenUsage.input_cache_creation);
1404
+ if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && cacheCreateTokens === 0) {
1405
+ continue;
1406
+ }
1407
+ if (payload.message_id) {
1408
+ if (seenMessageIds.has(payload.message_id)) continue;
1409
+ seenMessageIds.add(payload.message_id);
1410
+ }
1411
+ if (!role) {
1412
+ sessionEvents.push({
1413
+ sessionId,
1414
+ source: TOOL_ID2,
1415
+ project,
1416
+ timestamp,
1417
+ role: "assistant"
1418
+ });
1419
+ }
1420
+ entries.push({
1421
+ sessionId,
1422
+ source: TOOL_ID2,
1423
+ model: currentModel,
1424
+ project,
1425
+ timestamp,
1426
+ inputTokens,
1427
+ outputTokens,
1428
+ reasoningTokens: 0,
1429
+ cachedTokens
1430
+ });
1431
+ }
1432
+ }
1433
+ return {
1434
+ buckets: aggregateToBuckets(entries),
1435
+ sessions: extractSessions(sessionEvents, entries)
1436
+ };
1437
+ }
1438
+ isInstalled() {
1439
+ return existsSync9(this.sessionsDir);
1440
+ }
1441
+ };
1442
+ registerParser(new KimiCodeParser());
1443
+
1444
+ // src/parsers/droid.ts
1445
+ import { existsSync as existsSync10, readdirSync as readdirSync8 } from "fs";
1446
+ import { homedir as homedir9 } from "os";
1447
+ import { basename as basename4, dirname as dirname2, join as join10 } from "path";
1448
+ var TOOL_ID3 = "droid";
1449
+ var TOOL_NAME3 = "Droid";
1450
+ var DEFAULT_DATA_DIR3 = join10(homedir9(), ".factory", "sessions");
1451
+ function createToolDefinition3(dataDir) {
1452
+ return {
1453
+ id: TOOL_ID3,
1454
+ name: TOOL_NAME3,
1455
+ dataDir
1456
+ };
1457
+ }
1458
+ function findSessionFiles3(dir) {
1459
+ const results = [];
1460
+ if (!existsSync10(dir)) return results;
1461
+ try {
1462
+ for (const entry of readdirSync8(dir, { withFileTypes: true })) {
1463
+ const fullPath = join10(dir, entry.name);
1464
+ if (entry.isDirectory()) {
1465
+ results.push(...findSessionFiles3(fullPath));
1466
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl") && !entry.name.endsWith(".settings.json")) {
1467
+ results.push(fullPath);
1468
+ }
1469
+ }
1470
+ } catch {
1471
+ }
1472
+ return results;
1473
+ }
1474
+ function extractDroidProject(slug) {
1475
+ const parts = slug.split("-").filter(Boolean);
1476
+ return parts.length > 0 ? parts[parts.length - 1] : "unknown";
1477
+ }
1478
+ function toSafeNumber3(value) {
1479
+ const numberValue = Number(value);
1480
+ return Number.isFinite(numberValue) ? numberValue : 0;
1481
+ }
1482
+ var DroidParser = class {
1483
+ constructor(dataDir = DEFAULT_DATA_DIR3) {
1484
+ this.dataDir = dataDir;
1485
+ this.tool = createToolDefinition3(dataDir);
1486
+ }
1487
+ tool;
1488
+ async parse() {
1489
+ const sessionFiles = findSessionFiles3(this.dataDir);
1490
+ if (sessionFiles.length === 0) {
1491
+ return { buckets: [], sessions: [] };
1492
+ }
1493
+ const entries = [];
1494
+ const sessionEvents = [];
1495
+ for (const filePath of sessionFiles) {
1496
+ const sessionId = filePath;
1497
+ const project = extractDroidProject(basename4(dirname2(filePath)));
1498
+ let firstMessageTimestamp = null;
1499
+ const content = readFileSafe(filePath);
1500
+ if (!content) continue;
1501
+ for (const line of content.split("\n")) {
1502
+ if (!line.trim()) continue;
1503
+ let obj;
1504
+ try {
1505
+ obj = JSON.parse(line);
1506
+ } catch {
1507
+ continue;
1508
+ }
1509
+ if (obj.type !== "message" || !obj.timestamp) continue;
1510
+ const timestamp = new Date(obj.timestamp);
1511
+ if (Number.isNaN(timestamp.getTime())) continue;
1512
+ const role = obj.message?.role;
1513
+ if (role !== "user" && role !== "assistant") continue;
1514
+ if (firstMessageTimestamp === null) {
1515
+ firstMessageTimestamp = timestamp;
1516
+ }
1517
+ sessionEvents.push({
1518
+ sessionId,
1519
+ source: TOOL_ID3,
1520
+ project,
1521
+ timestamp,
1522
+ role
1523
+ });
1524
+ }
1525
+ const settingsPath = join10(
1526
+ dirname2(filePath),
1527
+ `${basename4(filePath, ".jsonl")}.settings.json`
1528
+ );
1529
+ const settingsContent = readFileSafe(settingsPath);
1530
+ if (!settingsContent || firstMessageTimestamp === null) continue;
1531
+ let settings;
1532
+ try {
1533
+ settings = JSON.parse(settingsContent);
1534
+ } catch {
1535
+ continue;
1536
+ }
1537
+ const tokenUsage = settings.tokenUsage;
1538
+ if (!tokenUsage) continue;
1539
+ const cachedTokens = toSafeNumber3(tokenUsage.cacheReadTokens);
1540
+ const reasoningTokens = toSafeNumber3(tokenUsage.thinkingTokens);
1541
+ const inputTokens = Math.max(
1542
+ 0,
1543
+ toSafeNumber3(tokenUsage.inputTokens) - cachedTokens
1544
+ );
1545
+ const outputTokens = Math.max(
1546
+ 0,
1547
+ toSafeNumber3(tokenUsage.outputTokens) - reasoningTokens
1548
+ );
1549
+ if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
1550
+ continue;
1551
+ }
1552
+ entries.push({
1553
+ sessionId,
1554
+ source: TOOL_ID3,
1555
+ model: settings.model || "unknown",
1556
+ project,
1557
+ timestamp: firstMessageTimestamp,
1558
+ inputTokens,
1559
+ outputTokens,
1560
+ reasoningTokens,
1561
+ cachedTokens
1562
+ });
1563
+ }
1564
+ return {
1565
+ buckets: aggregateToBuckets(entries),
1566
+ sessions: extractSessions(sessionEvents, entries)
1567
+ };
1568
+ }
1569
+ isInstalled() {
1570
+ return existsSync10(this.dataDir);
1571
+ }
1572
+ };
1573
+ registerParser(new DroidParser());
1574
+
1575
+ // src/parsers/pi-coding-agent.ts
1576
+ import { existsSync as existsSync11 } from "fs";
1577
+ import { homedir as homedir10 } from "os";
1578
+ import { join as join11 } from "path";
1579
+ var TOOL_ID4 = "pi-coding-agent";
1580
+ var TOOL_NAME4 = "pi";
1581
+ var DEFAULT_SESSIONS_DIR2 = join11(homedir10(), ".pi", "agent", "sessions");
1582
+ function createToolDefinition4(dataDir) {
1583
+ return {
1584
+ id: TOOL_ID4,
1585
+ name: TOOL_NAME4,
1586
+ dataDir
1587
+ };
1588
+ }
1589
+ function toSafeNumber4(value) {
1590
+ const numberValue = Number(value);
1591
+ return Number.isFinite(numberValue) ? numberValue : 0;
1592
+ }
1593
+ function getPathLeaf4(value) {
1594
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
1595
+ const leaf = normalized.split("/").filter(Boolean).pop();
1596
+ return leaf || "unknown";
1597
+ }
1598
+ function normalizeForPrefix2(value) {
1599
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
1600
+ }
1601
+ function getUsageNumber(usage, ...keys) {
1602
+ for (const key of keys) {
1603
+ const value = usage[key];
1604
+ const numberValue = toSafeNumber4(value);
1605
+ if (numberValue > 0) {
1606
+ return numberValue;
1607
+ }
1608
+ }
1609
+ return 0;
1610
+ }
1611
+ function extractPiProjectFromCwd(cwd) {
1612
+ return getPathLeaf4(cwd);
1613
+ }
1614
+ function extractPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR2) {
1615
+ const normalizedFilePath = normalizeForPrefix2(filePath);
1616
+ const normalizedSessionsDir = normalizeForPrefix2(sessionsDir);
1617
+ const prefix = `${normalizedSessionsDir}/`;
1618
+ if (!normalizedFilePath.startsWith(prefix)) {
1619
+ return "unknown";
1620
+ }
1621
+ const relativePath = normalizedFilePath.slice(prefix.length);
1622
+ const firstSegment = relativePath.split("/")[0];
1623
+ if (!firstSegment) {
1624
+ return "unknown";
1625
+ }
1626
+ try {
1627
+ const decoded = decodeURIComponent(firstSegment);
1628
+ if (decoded.includes("/") || decoded.includes("\\")) {
1629
+ return getPathLeaf4(decoded);
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ const slugParts = firstSegment.split("-").filter(Boolean);
1634
+ return slugParts.length > 0 ? slugParts[slugParts.length - 1] : "unknown";
1635
+ }
1636
+ var PiCodingAgentParser = class {
1637
+ constructor(sessionsDir = DEFAULT_SESSIONS_DIR2) {
1638
+ this.sessionsDir = sessionsDir;
1639
+ this.tool = createToolDefinition4(sessionsDir);
1640
+ }
1641
+ tool;
1642
+ async parse() {
1643
+ const sessionFiles = findJsonlFiles(this.sessionsDir);
1644
+ if (sessionFiles.length === 0) {
1645
+ return { buckets: [], sessions: [] };
1646
+ }
1647
+ const entries = [];
1648
+ const sessionEvents = [];
1649
+ const seenEntryIds = /* @__PURE__ */ new Set();
1650
+ for (const filePath of sessionFiles) {
1651
+ const content = readFileSafe(filePath);
1652
+ if (!content) continue;
1653
+ const rows = parseJsonl(content);
1654
+ if (rows.length === 0) continue;
1655
+ let sessionId = filePath;
1656
+ let project = extractPiProjectFromDir(filePath, this.sessionsDir);
1657
+ for (const row of rows) {
1658
+ if (row.type !== "session") continue;
1659
+ if (row.id) {
1660
+ sessionId = row.id;
1661
+ }
1662
+ if (row.cwd) {
1663
+ project = extractPiProjectFromCwd(row.cwd);
1664
+ }
1665
+ break;
1666
+ }
1667
+ for (const row of rows) {
1668
+ if (row.type !== "message") continue;
1669
+ const message = row.message;
1670
+ if (!message) continue;
1671
+ const rawTimestamp = row.timestamp || message.timestamp;
1672
+ if (!rawTimestamp) continue;
1673
+ const timestamp = new Date(rawTimestamp);
1674
+ if (Number.isNaN(timestamp.getTime())) continue;
1675
+ if (message.role === "user" || message.role === "assistant") {
1676
+ sessionEvents.push({
1677
+ sessionId,
1678
+ source: TOOL_ID4,
1679
+ project,
1680
+ timestamp,
1681
+ role: message.role
1682
+ });
1683
+ }
1684
+ if (message.role !== "assistant") continue;
1685
+ const usage = message.usage;
1686
+ if (!usage) continue;
1687
+ const inputTokens = getUsageNumber(usage, "input", "inputTokens");
1688
+ const outputTokens = getUsageNumber(usage, "output", "outputTokens");
1689
+ const cachedTokens = getUsageNumber(
1690
+ usage,
1691
+ "cacheRead",
1692
+ "cacheReadTokens",
1693
+ "cache_read"
1694
+ );
1695
+ const reasoningTokens = getUsageNumber(
1696
+ usage,
1697
+ "reasoningOutputTokens",
1698
+ "thinkingTokens",
1699
+ "thoughts"
1700
+ );
1701
+ if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
1702
+ continue;
1703
+ }
1704
+ if (row.id) {
1705
+ if (seenEntryIds.has(row.id)) continue;
1706
+ seenEntryIds.add(row.id);
1707
+ }
1708
+ entries.push({
1709
+ sessionId,
1710
+ source: TOOL_ID4,
1711
+ model: message.model || "unknown",
1712
+ project,
1713
+ timestamp,
1714
+ inputTokens,
1715
+ outputTokens,
1716
+ reasoningTokens,
1717
+ cachedTokens
1718
+ });
1719
+ }
1720
+ }
1721
+ return {
1722
+ buckets: aggregateToBuckets(entries),
1723
+ sessions: extractSessions(sessionEvents, entries)
1724
+ };
1725
+ }
1726
+ isInstalled() {
1727
+ return existsSync11(this.sessionsDir);
1728
+ }
1729
+ };
1730
+ registerParser(new PiCodingAgentParser());
1731
+
1101
1732
  // src/cli.ts
1102
1733
  import { Command, Option } from "commander";
1103
1734
 
1104
1735
  // src/infrastructure/config/manager.ts
1105
1736
  import { randomUUID } from "crypto";
1106
1737
  import {
1107
- existsSync as existsSync8,
1738
+ existsSync as existsSync12,
1108
1739
  mkdirSync,
1109
1740
  readFileSync as readFileSync6,
1110
1741
  unlinkSync,
1111
1742
  writeFileSync
1112
1743
  } from "fs";
1113
- import { join as join9 } from "path";
1744
+ import { join as join13 } from "path";
1114
1745
 
1115
1746
  // src/infrastructure/xdg.ts
1116
- import { homedir as homedir7 } from "os";
1117
- import { join as join8 } from "path";
1747
+ import { homedir as homedir11 } from "os";
1748
+ import { join as join12 } from "path";
1118
1749
  function getConfigHome() {
1119
- return process.env.XDG_CONFIG_HOME || join8(homedir7(), ".config");
1750
+ return process.env.XDG_CONFIG_HOME || join12(homedir11(), ".config");
1120
1751
  }
1121
1752
  function getStateHome() {
1122
- return process.env.XDG_STATE_HOME || join8(homedir7(), ".local", "state");
1753
+ return process.env.XDG_STATE_HOME || join12(homedir11(), ".local", "state");
1123
1754
  }
1124
1755
  function getRuntimeDir() {
1125
1756
  return process.env.XDG_RUNTIME_DIR || getStateHome();
1126
1757
  }
1127
1758
 
1128
1759
  // src/infrastructure/config/manager.ts
1129
- var CONFIG_DIR = join9(getConfigHome(), "tokenarena");
1760
+ var CONFIG_DIR = join13(getConfigHome(), "tokenarena");
1130
1761
  var isDev = process.env.TOKEN_ARENA_DEV === "1";
1131
- var CONFIG_FILE = join9(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
1762
+ var CONFIG_FILE = join13(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
1132
1763
  var DEFAULT_API_URL = "https://token.poco-ai.com";
1133
1764
  var VALID_CONFIG_KEYS = [
1134
1765
  "apiKey",
@@ -1144,7 +1775,7 @@ function getConfigDir() {
1144
1775
  return CONFIG_DIR;
1145
1776
  }
1146
1777
  function loadConfig() {
1147
- if (!existsSync8(CONFIG_FILE)) return null;
1778
+ if (!existsSync12(CONFIG_FILE)) return null;
1148
1779
  try {
1149
1780
  const raw = readFileSync6(CONFIG_FILE, "utf-8");
1150
1781
  const config = JSON.parse(raw);
@@ -1162,7 +1793,7 @@ function saveConfig(config) {
1162
1793
  `, "utf-8");
1163
1794
  }
1164
1795
  function deleteConfig() {
1165
- if (existsSync8(CONFIG_FILE)) {
1796
+ if (existsSync12(CONFIG_FILE)) {
1166
1797
  unlinkSync(CONFIG_FILE);
1167
1798
  }
1168
1799
  }
@@ -2019,7 +2650,7 @@ var ApiClient = class {
2019
2650
  // src/infrastructure/runtime/lock.ts
2020
2651
  import {
2021
2652
  closeSync,
2022
- existsSync as existsSync9,
2653
+ existsSync as existsSync13,
2023
2654
  openSync,
2024
2655
  readFileSync as readFileSync7,
2025
2656
  rmSync,
@@ -2028,22 +2659,22 @@ import {
2028
2659
 
2029
2660
  // src/infrastructure/runtime/paths.ts
2030
2661
  import { mkdirSync as mkdirSync2 } from "fs";
2031
- import { join as join10 } from "path";
2662
+ import { join as join14 } from "path";
2032
2663
  var APP_NAME = "tokenarena";
2033
2664
  function getRuntimeDirPath() {
2034
- return join10(getRuntimeDir(), APP_NAME);
2665
+ return join14(getRuntimeDir(), APP_NAME);
2035
2666
  }
2036
2667
  function getStateDir() {
2037
- return join10(getStateHome(), APP_NAME);
2668
+ return join14(getStateHome(), APP_NAME);
2038
2669
  }
2039
2670
  function getSyncLockPath() {
2040
- return join10(getRuntimeDirPath(), "sync.lock");
2671
+ return join14(getRuntimeDirPath(), "sync.lock");
2041
2672
  }
2042
2673
  function getSyncStatePath() {
2043
- return join10(getStateDir(), "status.json");
2674
+ return join14(getStateDir(), "status.json");
2044
2675
  }
2045
2676
  function getUploadManifestPath() {
2046
- return join10(getStateDir(), "upload-manifest.json");
2677
+ return join14(getStateDir(), "upload-manifest.json");
2047
2678
  }
2048
2679
  function ensureAppDirs() {
2049
2680
  mkdirSync2(getRuntimeDirPath(), { recursive: true });
@@ -2061,7 +2692,7 @@ function isProcessAlive(pid) {
2061
2692
  }
2062
2693
  }
2063
2694
  function readLockMetadata(lockPath) {
2064
- if (!existsSync9(lockPath)) {
2695
+ if (!existsSync13(lockPath)) {
2065
2696
  return null;
2066
2697
  }
2067
2698
  try {
@@ -2135,13 +2766,13 @@ function describeExistingSyncLock() {
2135
2766
  }
2136
2767
 
2137
2768
  // src/infrastructure/runtime/state.ts
2138
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
2769
+ import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
2139
2770
  function getDefaultState() {
2140
2771
  return { status: "idle" };
2141
2772
  }
2142
2773
  function loadSyncState() {
2143
2774
  const path = getSyncStatePath();
2144
- if (!existsSync10(path)) {
2775
+ if (!existsSync14(path)) {
2145
2776
  return getDefaultState();
2146
2777
  }
2147
2778
  try {
@@ -2202,7 +2833,7 @@ function markSyncFailed(source, error, status) {
2202
2833
  }
2203
2834
 
2204
2835
  // src/infrastructure/runtime/upload-manifest.ts
2205
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2836
+ import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2206
2837
  function isRecordOfStrings(value) {
2207
2838
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2208
2839
  return false;
@@ -2218,7 +2849,7 @@ function isUploadManifest(value) {
2218
2849
  }
2219
2850
  function loadUploadManifest() {
2220
2851
  const path = getUploadManifestPath();
2221
- if (!existsSync11(path)) {
2852
+ if (!existsSync15(path)) {
2222
2853
  return null;
2223
2854
  }
2224
2855
  try {
@@ -2646,10 +3277,10 @@ View your dashboard at: ${apiUrl}/usage`);
2646
3277
 
2647
3278
  // src/commands/init.ts
2648
3279
  import { execFileSync as execFileSync2, spawn } from "child_process";
2649
- import { existsSync as existsSync12 } from "fs";
3280
+ import { existsSync as existsSync16 } from "fs";
2650
3281
  import { appendFile, mkdir, readFile } from "fs/promises";
2651
- import { homedir as homedir8, platform } from "os";
2652
- import { dirname as dirname2, join as join11, posix, win32 } from "path";
3282
+ import { homedir as homedir12, platform } from "os";
3283
+ import { dirname as dirname3, join as join15, posix, win32 } from "path";
2653
3284
  function joinForPlatform(currentPlatform, ...parts) {
2654
3285
  return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
2655
3286
  }
@@ -2693,7 +3324,7 @@ function resolvePowerShellProfilePath() {
2693
3324
  const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
2694
3325
  const candidates = [
2695
3326
  "pwsh.exe",
2696
- join11(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
3327
+ join15(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
2697
3328
  ];
2698
3329
  for (const command of candidates) {
2699
3330
  try {
@@ -2722,8 +3353,8 @@ function resolvePowerShellProfilePath() {
2722
3353
  function resolveShellAliasSetup(options = {}) {
2723
3354
  const currentPlatform = options.currentPlatform ?? platform();
2724
3355
  const env = options.env ?? process.env;
2725
- const homeDir = options.homeDir ?? homedir8();
2726
- const pathExists = options.exists ?? existsSync12;
3356
+ const homeDir = options.homeDir ?? homedir12();
3357
+ const pathExists = options.exists ?? existsSync16;
2727
3358
  const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
2728
3359
  const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
2729
3360
  const aliasName = "ta";
@@ -2894,9 +3525,9 @@ async function setupShellAlias() {
2894
3525
  return;
2895
3526
  }
2896
3527
  try {
2897
- await mkdir(dirname2(setup.configFile), { recursive: true });
3528
+ await mkdir(dirname3(setup.configFile), { recursive: true });
2898
3529
  let existingContent = "";
2899
- if (existsSync12(setup.configFile)) {
3530
+ if (existsSync16(setup.configFile)) {
2900
3531
  existingContent = await readFile(setup.configFile, "utf-8");
2901
3532
  }
2902
3533
  const normalizedContent = existingContent.toLowerCase();
@@ -3089,8 +3720,8 @@ async function runSyncCommand(opts = {}) {
3089
3720
  }
3090
3721
 
3091
3722
  // src/commands/uninstall.ts
3092
- import { existsSync as existsSync13, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
3093
- import { homedir as homedir9, platform as platform2 } from "os";
3723
+ import { existsSync as existsSync17, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
3724
+ import { homedir as homedir13, platform as platform2 } from "os";
3094
3725
  function removeShellAlias() {
3095
3726
  const shell = process.env.SHELL;
3096
3727
  if (!shell) return;
@@ -3099,22 +3730,22 @@ function removeShellAlias() {
3099
3730
  let configFile;
3100
3731
  switch (shellName) {
3101
3732
  case "zsh":
3102
- configFile = `${homedir9()}/.zshrc`;
3733
+ configFile = `${homedir13()}/.zshrc`;
3103
3734
  break;
3104
3735
  case "bash":
3105
- if (platform2() === "darwin" && existsSync13(`${homedir9()}/.bash_profile`)) {
3106
- configFile = `${homedir9()}/.bash_profile`;
3736
+ if (platform2() === "darwin" && existsSync17(`${homedir13()}/.bash_profile`)) {
3737
+ configFile = `${homedir13()}/.bash_profile`;
3107
3738
  } else {
3108
- configFile = `${homedir9()}/.bashrc`;
3739
+ configFile = `${homedir13()}/.bashrc`;
3109
3740
  }
3110
3741
  break;
3111
3742
  case "fish":
3112
- configFile = `${homedir9()}/.config/fish/config.fish`;
3743
+ configFile = `${homedir13()}/.config/fish/config.fish`;
3113
3744
  break;
3114
3745
  default:
3115
3746
  return;
3116
3747
  }
3117
- if (!existsSync13(configFile)) return;
3748
+ if (!existsSync17(configFile)) return;
3118
3749
  try {
3119
3750
  let content = readFileSync10(configFile, "utf-8");
3120
3751
  const aliasPatterns = [
@@ -3149,7 +3780,7 @@ function removeShellAlias() {
3149
3780
  async function runUninstall() {
3150
3781
  const configPath = getConfigPath();
3151
3782
  const configDir = getConfigDir();
3152
- if (!existsSync13(configPath)) {
3783
+ if (!existsSync17(configPath)) {
3153
3784
  logger.info(formatHeader("\u5378\u8F7D TokenArena"));
3154
3785
  logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
3155
3786
  return;
@@ -3178,7 +3809,7 @@ async function runUninstall() {
3178
3809
  deleteConfig();
3179
3810
  logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
3180
3811
  logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
3181
- if (existsSync13(configDir)) {
3812
+ if (existsSync17(configDir)) {
3182
3813
  try {
3183
3814
  rmSync2(configDir, { recursive: false, force: true });
3184
3815
  logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
@@ -3186,12 +3817,12 @@ async function runUninstall() {
3186
3817
  }
3187
3818
  }
3188
3819
  const stateDir = getStateDir();
3189
- if (existsSync13(stateDir)) {
3820
+ if (existsSync17(stateDir)) {
3190
3821
  rmSync2(stateDir, { recursive: true, force: true });
3191
3822
  logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
3192
3823
  }
3193
3824
  const runtimeDir = getRuntimeDirPath();
3194
- if (existsSync13(runtimeDir)) {
3825
+ if (existsSync17(runtimeDir)) {
3195
3826
  rmSync2(runtimeDir, { recursive: true, force: true });
3196
3827
  logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
3197
3828
  }
@@ -3316,7 +3947,7 @@ async function runHome(program) {
3316
3947
 
3317
3948
  // src/infrastructure/runtime/cli-version.ts
3318
3949
  import { readFileSync as readFileSync11 } from "fs";
3319
- import { dirname as dirname3, join as join12 } from "path";
3950
+ import { dirname as dirname4, join as join16 } from "path";
3320
3951
  import { fileURLToPath } from "url";
3321
3952
  var FALLBACK_VERSION = "0.0.0";
3322
3953
  var cachedVersion;
@@ -3324,8 +3955,8 @@ function getCliVersion(metaUrl = import.meta.url) {
3324
3955
  if (cachedVersion) {
3325
3956
  return cachedVersion;
3326
3957
  }
3327
- const packageJsonPath = join12(
3328
- dirname3(fileURLToPath(metaUrl)),
3958
+ const packageJsonPath = join16(
3959
+ dirname4(fileURLToPath(metaUrl)),
3329
3960
  "..",
3330
3961
  "package.json"
3331
3962
  );
@@ -3380,7 +4011,7 @@ function createCli() {
3380
4011
  }
3381
4012
 
3382
4013
  // src/infrastructure/runtime/main-module.ts
3383
- import { existsSync as existsSync14, realpathSync } from "fs";
4014
+ import { existsSync as existsSync18, realpathSync } from "fs";
3384
4015
  import { resolve } from "path";
3385
4016
  import { fileURLToPath as fileURLToPath2 } from "url";
3386
4017
  function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
@@ -3391,7 +4022,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
3391
4022
  try {
3392
4023
  return realpathSync(argvEntry) === realpathSync(currentModulePath);
3393
4024
  } catch {
3394
- if (!existsSync14(argvEntry)) {
4025
+ if (!existsSync18(argvEntry)) {
3395
4026
  return false;
3396
4027
  }
3397
4028
  return resolve(argvEntry) === resolve(currentModulePath);