@poco-ai/tokenarena 0.2.1 → 0.2.3
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/chunk-XJKRJ3K2.js +413 -0
- package/dist/chunk-XJKRJ3K2.js.map +1 -0
- package/dist/index.js +754 -221
- package/dist/index.js.map +1 -1
- package/dist/service-4U7K4DKW.js +8 -0
- package/dist/service-4U7K4DKW.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatBullet,
|
|
4
|
+
formatHeader,
|
|
5
|
+
formatKeyValue,
|
|
6
|
+
formatMutedPath,
|
|
7
|
+
formatSection,
|
|
8
|
+
formatStatusBadge,
|
|
9
|
+
isCommandAvailable,
|
|
10
|
+
isInteractiveTerminal,
|
|
11
|
+
logger,
|
|
12
|
+
maskSecret,
|
|
13
|
+
promptConfirm,
|
|
14
|
+
promptPassword,
|
|
15
|
+
promptSelect,
|
|
16
|
+
promptText,
|
|
17
|
+
runInstallService
|
|
18
|
+
} from "./chunk-XJKRJ3K2.js";
|
|
2
19
|
|
|
3
20
|
// src/parsers/claude-code.ts
|
|
4
21
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -211,6 +228,17 @@ function readFileSafe(filePath) {
|
|
|
211
228
|
return null;
|
|
212
229
|
}
|
|
213
230
|
}
|
|
231
|
+
function parseJsonl(content) {
|
|
232
|
+
const results = [];
|
|
233
|
+
for (const line of content.split("\n")) {
|
|
234
|
+
if (!line.trim()) continue;
|
|
235
|
+
try {
|
|
236
|
+
results.push(JSON.parse(line));
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
214
242
|
function extractSessionId(filePath) {
|
|
215
243
|
return basename(filePath, ".jsonl");
|
|
216
244
|
}
|
|
@@ -1098,37 +1126,657 @@ var OpenClawParser = class {
|
|
|
1098
1126
|
};
|
|
1099
1127
|
registerParser(new OpenClawParser());
|
|
1100
1128
|
|
|
1129
|
+
// src/parsers/qwen-code.ts
|
|
1130
|
+
import { existsSync as existsSync8, readdirSync as readdirSync6 } from "fs";
|
|
1131
|
+
import { homedir as homedir7 } from "os";
|
|
1132
|
+
import { join as join8 } from "path";
|
|
1133
|
+
var TOOL_ID = "qwen-code";
|
|
1134
|
+
var TOOL_NAME = "Qwen Code";
|
|
1135
|
+
var DEFAULT_DATA_DIR2 = join8(homedir7(), ".qwen", "tmp");
|
|
1136
|
+
function createToolDefinition(dataDir) {
|
|
1137
|
+
return {
|
|
1138
|
+
id: TOOL_ID,
|
|
1139
|
+
name: TOOL_NAME,
|
|
1140
|
+
dataDir
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function toSafeNumber(value) {
|
|
1144
|
+
const numberValue = Number(value);
|
|
1145
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
1146
|
+
}
|
|
1147
|
+
function getPathLeaf2(value) {
|
|
1148
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1149
|
+
const leaf = normalized.split("/").filter(Boolean).pop();
|
|
1150
|
+
return leaf || "unknown";
|
|
1151
|
+
}
|
|
1152
|
+
function normalizeForPrefix(value) {
|
|
1153
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1154
|
+
}
|
|
1155
|
+
function findSessionFiles2(baseDir) {
|
|
1156
|
+
const results = [];
|
|
1157
|
+
if (!existsSync8(baseDir)) return results;
|
|
1158
|
+
try {
|
|
1159
|
+
for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
|
|
1160
|
+
if (!entry.isDirectory()) continue;
|
|
1161
|
+
const chatsDir = join8(baseDir, entry.name, "chats");
|
|
1162
|
+
if (!existsSync8(chatsDir)) continue;
|
|
1163
|
+
try {
|
|
1164
|
+
for (const file of readdirSync6(chatsDir)) {
|
|
1165
|
+
if (file.endsWith(".jsonl")) {
|
|
1166
|
+
results.push(join8(chatsDir, file));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
} catch {
|
|
1173
|
+
return results;
|
|
1174
|
+
}
|
|
1175
|
+
return results;
|
|
1176
|
+
}
|
|
1177
|
+
function resolveQwenProject(cwd, filePath, dataDir = DEFAULT_DATA_DIR2) {
|
|
1178
|
+
if (cwd) {
|
|
1179
|
+
return getPathLeaf2(cwd);
|
|
1180
|
+
}
|
|
1181
|
+
const normalizedFilePath = normalizeForPrefix(filePath);
|
|
1182
|
+
const normalizedDataDir = normalizeForPrefix(dataDir);
|
|
1183
|
+
const prefix = `${normalizedDataDir}/`;
|
|
1184
|
+
if (!normalizedFilePath.startsWith(prefix)) {
|
|
1185
|
+
return "unknown";
|
|
1186
|
+
}
|
|
1187
|
+
const relativePath = normalizedFilePath.slice(prefix.length);
|
|
1188
|
+
const projectId = relativePath.split("/")[0];
|
|
1189
|
+
return projectId || "unknown";
|
|
1190
|
+
}
|
|
1191
|
+
var QwenCodeParser = class {
|
|
1192
|
+
constructor(dataDir = DEFAULT_DATA_DIR2) {
|
|
1193
|
+
this.dataDir = dataDir;
|
|
1194
|
+
this.tool = createToolDefinition(dataDir);
|
|
1195
|
+
}
|
|
1196
|
+
tool;
|
|
1197
|
+
async parse() {
|
|
1198
|
+
const sessionFiles = findSessionFiles2(this.dataDir);
|
|
1199
|
+
if (sessionFiles.length === 0) {
|
|
1200
|
+
return { buckets: [], sessions: [] };
|
|
1201
|
+
}
|
|
1202
|
+
const entries = [];
|
|
1203
|
+
const sessionEvents = [];
|
|
1204
|
+
const seenUuids = /* @__PURE__ */ new Set();
|
|
1205
|
+
for (const filePath of sessionFiles) {
|
|
1206
|
+
const content = readFileSafe(filePath);
|
|
1207
|
+
if (!content) continue;
|
|
1208
|
+
const sessionId = filePath;
|
|
1209
|
+
for (const line of content.split("\n")) {
|
|
1210
|
+
if (!line.trim()) continue;
|
|
1211
|
+
try {
|
|
1212
|
+
const obj = JSON.parse(line);
|
|
1213
|
+
if (!obj.timestamp) continue;
|
|
1214
|
+
const timestamp = new Date(obj.timestamp);
|
|
1215
|
+
if (Number.isNaN(timestamp.getTime())) continue;
|
|
1216
|
+
const project = resolveQwenProject(obj.cwd, filePath, this.dataDir);
|
|
1217
|
+
if (obj.type === "user" || obj.type === "assistant") {
|
|
1218
|
+
sessionEvents.push({
|
|
1219
|
+
sessionId,
|
|
1220
|
+
source: TOOL_ID,
|
|
1221
|
+
project,
|
|
1222
|
+
timestamp,
|
|
1223
|
+
role: obj.type
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
if (obj.type !== "assistant") continue;
|
|
1227
|
+
const usage = obj.usageMetadata || obj.usage;
|
|
1228
|
+
if (!usage) continue;
|
|
1229
|
+
const totalInput = toSafeNumber(usage.promptTokenCount) || toSafeNumber(usage.input_tokens);
|
|
1230
|
+
const totalOutput = toSafeNumber(usage.candidatesTokenCount) || toSafeNumber(usage.output_tokens);
|
|
1231
|
+
const cachedTokens = toSafeNumber(usage.cachedContentTokenCount);
|
|
1232
|
+
const reasoningTokens = toSafeNumber(usage.thoughtsTokenCount);
|
|
1233
|
+
if (totalInput === 0 && totalOutput === 0 && cachedTokens === 0 && reasoningTokens === 0) {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (obj.uuid) {
|
|
1237
|
+
if (seenUuids.has(obj.uuid)) continue;
|
|
1238
|
+
seenUuids.add(obj.uuid);
|
|
1239
|
+
}
|
|
1240
|
+
entries.push({
|
|
1241
|
+
sessionId,
|
|
1242
|
+
source: TOOL_ID,
|
|
1243
|
+
model: obj.model || "unknown",
|
|
1244
|
+
project,
|
|
1245
|
+
timestamp,
|
|
1246
|
+
inputTokens: Math.max(0, totalInput - cachedTokens),
|
|
1247
|
+
outputTokens: Math.max(0, totalOutput - reasoningTokens),
|
|
1248
|
+
reasoningTokens,
|
|
1249
|
+
cachedTokens
|
|
1250
|
+
});
|
|
1251
|
+
} catch {
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
buckets: aggregateToBuckets(entries),
|
|
1257
|
+
sessions: extractSessions(sessionEvents, entries)
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
isInstalled() {
|
|
1261
|
+
return existsSync8(this.dataDir);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
registerParser(new QwenCodeParser());
|
|
1265
|
+
|
|
1266
|
+
// src/parsers/kimi-code.ts
|
|
1267
|
+
import { existsSync as existsSync9, readdirSync as readdirSync7 } from "fs";
|
|
1268
|
+
import { homedir as homedir8 } from "os";
|
|
1269
|
+
import { join as join9 } from "path";
|
|
1270
|
+
var TOOL_ID2 = "kimi-code";
|
|
1271
|
+
var TOOL_NAME2 = "Kimi Code";
|
|
1272
|
+
var DEFAULT_SESSIONS_DIR = join9(homedir8(), ".kimi", "sessions");
|
|
1273
|
+
var DEFAULT_CONFIG_PATH = join9(homedir8(), ".kimi", "kimi.json");
|
|
1274
|
+
var USER_EVENT_TYPES = /* @__PURE__ */ new Set(["UserMessage", "user_message", "Input"]);
|
|
1275
|
+
var ASSISTANT_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1276
|
+
"AssistantMessage",
|
|
1277
|
+
"assistant_message",
|
|
1278
|
+
"Output",
|
|
1279
|
+
"ModelOutput",
|
|
1280
|
+
"AssistantOutput"
|
|
1281
|
+
]);
|
|
1282
|
+
function createToolDefinition2(dataDir) {
|
|
1283
|
+
return {
|
|
1284
|
+
id: TOOL_ID2,
|
|
1285
|
+
name: TOOL_NAME2,
|
|
1286
|
+
dataDir
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function toSafeNumber2(value) {
|
|
1290
|
+
const numberValue = Number(value);
|
|
1291
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
1292
|
+
}
|
|
1293
|
+
function getPathLeaf3(value) {
|
|
1294
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1295
|
+
const leaf = normalized.split("/").filter(Boolean).pop();
|
|
1296
|
+
return leaf || "unknown";
|
|
1297
|
+
}
|
|
1298
|
+
function findWireFiles(baseDir) {
|
|
1299
|
+
const results = [];
|
|
1300
|
+
if (!existsSync9(baseDir)) return results;
|
|
1301
|
+
try {
|
|
1302
|
+
for (const workDir of readdirSync7(baseDir, { withFileTypes: true })) {
|
|
1303
|
+
if (!workDir.isDirectory()) continue;
|
|
1304
|
+
const workDirPath = join9(baseDir, workDir.name);
|
|
1305
|
+
try {
|
|
1306
|
+
for (const session of readdirSync7(workDirPath, {
|
|
1307
|
+
withFileTypes: true
|
|
1308
|
+
})) {
|
|
1309
|
+
if (!session.isDirectory()) continue;
|
|
1310
|
+
const wireFile = join9(workDirPath, session.name, "wire.jsonl");
|
|
1311
|
+
if (existsSync9(wireFile)) {
|
|
1312
|
+
results.push({ filePath: wireFile, workDirHash: workDir.name });
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
} catch {
|
|
1319
|
+
return results;
|
|
1320
|
+
}
|
|
1321
|
+
return results;
|
|
1322
|
+
}
|
|
1323
|
+
function parseTimestamp(value) {
|
|
1324
|
+
if (value == null) return null;
|
|
1325
|
+
const timestamp = new Date(value);
|
|
1326
|
+
return Number.isNaN(timestamp.getTime()) ? null : timestamp;
|
|
1327
|
+
}
|
|
1328
|
+
function classifyKimiRole(type, payload) {
|
|
1329
|
+
if (payload?.role === "user" || payload?.role === "assistant") {
|
|
1330
|
+
return payload.role;
|
|
1331
|
+
}
|
|
1332
|
+
if (type && USER_EVENT_TYPES.has(type)) {
|
|
1333
|
+
return "user";
|
|
1334
|
+
}
|
|
1335
|
+
if (type && ASSISTANT_EVENT_TYPES.has(type)) {
|
|
1336
|
+
return "assistant";
|
|
1337
|
+
}
|
|
1338
|
+
if (type?.toLowerCase().includes("assistant")) {
|
|
1339
|
+
return "assistant";
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
function loadProjectMap(configPath) {
|
|
1344
|
+
const projectMap = /* @__PURE__ */ new Map();
|
|
1345
|
+
const content = readFileSafe(configPath);
|
|
1346
|
+
if (!content) return projectMap;
|
|
1347
|
+
try {
|
|
1348
|
+
const config = JSON.parse(content);
|
|
1349
|
+
const workspaces = config.workspaces || config.projects || {};
|
|
1350
|
+
for (const [hash, info] of Object.entries(workspaces)) {
|
|
1351
|
+
const pathValue = typeof info === "string" ? info : info.path || info.dir || void 0;
|
|
1352
|
+
if (!pathValue) continue;
|
|
1353
|
+
projectMap.set(hash, getPathLeaf3(pathValue));
|
|
1354
|
+
}
|
|
1355
|
+
} catch {
|
|
1356
|
+
}
|
|
1357
|
+
return projectMap;
|
|
1358
|
+
}
|
|
1359
|
+
var KimiCodeParser = class {
|
|
1360
|
+
tool;
|
|
1361
|
+
sessionsDir;
|
|
1362
|
+
configPath;
|
|
1363
|
+
constructor(options = {}) {
|
|
1364
|
+
this.sessionsDir = options.sessionsDir || DEFAULT_SESSIONS_DIR;
|
|
1365
|
+
this.configPath = options.configPath || DEFAULT_CONFIG_PATH;
|
|
1366
|
+
this.tool = createToolDefinition2(this.sessionsDir);
|
|
1367
|
+
}
|
|
1368
|
+
async parse() {
|
|
1369
|
+
const wireFiles = findWireFiles(this.sessionsDir);
|
|
1370
|
+
if (wireFiles.length === 0) {
|
|
1371
|
+
return { buckets: [], sessions: [] };
|
|
1372
|
+
}
|
|
1373
|
+
const projectMap = loadProjectMap(this.configPath);
|
|
1374
|
+
const entries = [];
|
|
1375
|
+
const sessionEvents = [];
|
|
1376
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
1377
|
+
for (const { filePath, workDirHash } of wireFiles) {
|
|
1378
|
+
const content = readFileSafe(filePath);
|
|
1379
|
+
if (!content) continue;
|
|
1380
|
+
const sessionId = filePath;
|
|
1381
|
+
const project = projectMap.get(workDirHash) || workDirHash;
|
|
1382
|
+
let currentModel = "unknown";
|
|
1383
|
+
let lastTimestampRaw;
|
|
1384
|
+
for (const line of content.split("\n")) {
|
|
1385
|
+
if (!line.trim()) continue;
|
|
1386
|
+
let obj;
|
|
1387
|
+
try {
|
|
1388
|
+
obj = JSON.parse(line);
|
|
1389
|
+
} catch {
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
const payload = obj.payload;
|
|
1393
|
+
if (!payload) continue;
|
|
1394
|
+
if (payload.model) {
|
|
1395
|
+
currentModel = payload.model;
|
|
1396
|
+
}
|
|
1397
|
+
const timestampValue = payload.timestamp ?? obj.timestamp ?? lastTimestampRaw;
|
|
1398
|
+
const timestamp = parseTimestamp(timestampValue);
|
|
1399
|
+
if (payload.timestamp != null) {
|
|
1400
|
+
lastTimestampRaw = payload.timestamp;
|
|
1401
|
+
} else if (obj.timestamp != null) {
|
|
1402
|
+
lastTimestampRaw = obj.timestamp;
|
|
1403
|
+
}
|
|
1404
|
+
const role = classifyKimiRole(obj.type, payload);
|
|
1405
|
+
if (role && timestamp) {
|
|
1406
|
+
sessionEvents.push({
|
|
1407
|
+
sessionId,
|
|
1408
|
+
source: TOOL_ID2,
|
|
1409
|
+
project,
|
|
1410
|
+
timestamp,
|
|
1411
|
+
role
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
if (obj.type !== "StatusUpdate") continue;
|
|
1415
|
+
const tokenUsage = payload.token_usage;
|
|
1416
|
+
if (!tokenUsage || !timestamp) continue;
|
|
1417
|
+
const inputTokens = toSafeNumber2(tokenUsage.input_other);
|
|
1418
|
+
const outputTokens = toSafeNumber2(tokenUsage.output);
|
|
1419
|
+
const cachedTokens = toSafeNumber2(tokenUsage.input_cache_read);
|
|
1420
|
+
const cacheCreateTokens = toSafeNumber2(tokenUsage.input_cache_creation);
|
|
1421
|
+
if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && cacheCreateTokens === 0) {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
if (payload.message_id) {
|
|
1425
|
+
if (seenMessageIds.has(payload.message_id)) continue;
|
|
1426
|
+
seenMessageIds.add(payload.message_id);
|
|
1427
|
+
}
|
|
1428
|
+
if (!role) {
|
|
1429
|
+
sessionEvents.push({
|
|
1430
|
+
sessionId,
|
|
1431
|
+
source: TOOL_ID2,
|
|
1432
|
+
project,
|
|
1433
|
+
timestamp,
|
|
1434
|
+
role: "assistant"
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
entries.push({
|
|
1438
|
+
sessionId,
|
|
1439
|
+
source: TOOL_ID2,
|
|
1440
|
+
model: currentModel,
|
|
1441
|
+
project,
|
|
1442
|
+
timestamp,
|
|
1443
|
+
inputTokens,
|
|
1444
|
+
outputTokens,
|
|
1445
|
+
reasoningTokens: 0,
|
|
1446
|
+
cachedTokens
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return {
|
|
1451
|
+
buckets: aggregateToBuckets(entries),
|
|
1452
|
+
sessions: extractSessions(sessionEvents, entries)
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
isInstalled() {
|
|
1456
|
+
return existsSync9(this.sessionsDir);
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
registerParser(new KimiCodeParser());
|
|
1460
|
+
|
|
1461
|
+
// src/parsers/droid.ts
|
|
1462
|
+
import { existsSync as existsSync10, readdirSync as readdirSync8 } from "fs";
|
|
1463
|
+
import { homedir as homedir9 } from "os";
|
|
1464
|
+
import { basename as basename4, dirname as dirname2, join as join10 } from "path";
|
|
1465
|
+
var TOOL_ID3 = "droid";
|
|
1466
|
+
var TOOL_NAME3 = "Droid";
|
|
1467
|
+
var DEFAULT_DATA_DIR3 = join10(homedir9(), ".factory", "sessions");
|
|
1468
|
+
function createToolDefinition3(dataDir) {
|
|
1469
|
+
return {
|
|
1470
|
+
id: TOOL_ID3,
|
|
1471
|
+
name: TOOL_NAME3,
|
|
1472
|
+
dataDir
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
function findSessionFiles3(dir) {
|
|
1476
|
+
const results = [];
|
|
1477
|
+
if (!existsSync10(dir)) return results;
|
|
1478
|
+
try {
|
|
1479
|
+
for (const entry of readdirSync8(dir, { withFileTypes: true })) {
|
|
1480
|
+
const fullPath = join10(dir, entry.name);
|
|
1481
|
+
if (entry.isDirectory()) {
|
|
1482
|
+
results.push(...findSessionFiles3(fullPath));
|
|
1483
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl") && !entry.name.endsWith(".settings.json")) {
|
|
1484
|
+
results.push(fullPath);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
return results;
|
|
1490
|
+
}
|
|
1491
|
+
function extractDroidProject(slug) {
|
|
1492
|
+
const parts = slug.split("-").filter(Boolean);
|
|
1493
|
+
return parts.length > 0 ? parts[parts.length - 1] : "unknown";
|
|
1494
|
+
}
|
|
1495
|
+
function toSafeNumber3(value) {
|
|
1496
|
+
const numberValue = Number(value);
|
|
1497
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
1498
|
+
}
|
|
1499
|
+
var DroidParser = class {
|
|
1500
|
+
constructor(dataDir = DEFAULT_DATA_DIR3) {
|
|
1501
|
+
this.dataDir = dataDir;
|
|
1502
|
+
this.tool = createToolDefinition3(dataDir);
|
|
1503
|
+
}
|
|
1504
|
+
tool;
|
|
1505
|
+
async parse() {
|
|
1506
|
+
const sessionFiles = findSessionFiles3(this.dataDir);
|
|
1507
|
+
if (sessionFiles.length === 0) {
|
|
1508
|
+
return { buckets: [], sessions: [] };
|
|
1509
|
+
}
|
|
1510
|
+
const entries = [];
|
|
1511
|
+
const sessionEvents = [];
|
|
1512
|
+
for (const filePath of sessionFiles) {
|
|
1513
|
+
const sessionId = filePath;
|
|
1514
|
+
const project = extractDroidProject(basename4(dirname2(filePath)));
|
|
1515
|
+
let firstMessageTimestamp = null;
|
|
1516
|
+
const content = readFileSafe(filePath);
|
|
1517
|
+
if (!content) continue;
|
|
1518
|
+
for (const line of content.split("\n")) {
|
|
1519
|
+
if (!line.trim()) continue;
|
|
1520
|
+
let obj;
|
|
1521
|
+
try {
|
|
1522
|
+
obj = JSON.parse(line);
|
|
1523
|
+
} catch {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (obj.type !== "message" || !obj.timestamp) continue;
|
|
1527
|
+
const timestamp = new Date(obj.timestamp);
|
|
1528
|
+
if (Number.isNaN(timestamp.getTime())) continue;
|
|
1529
|
+
const role = obj.message?.role;
|
|
1530
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
1531
|
+
if (firstMessageTimestamp === null) {
|
|
1532
|
+
firstMessageTimestamp = timestamp;
|
|
1533
|
+
}
|
|
1534
|
+
sessionEvents.push({
|
|
1535
|
+
sessionId,
|
|
1536
|
+
source: TOOL_ID3,
|
|
1537
|
+
project,
|
|
1538
|
+
timestamp,
|
|
1539
|
+
role
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
const settingsPath = join10(
|
|
1543
|
+
dirname2(filePath),
|
|
1544
|
+
`${basename4(filePath, ".jsonl")}.settings.json`
|
|
1545
|
+
);
|
|
1546
|
+
const settingsContent = readFileSafe(settingsPath);
|
|
1547
|
+
if (!settingsContent || firstMessageTimestamp === null) continue;
|
|
1548
|
+
let settings;
|
|
1549
|
+
try {
|
|
1550
|
+
settings = JSON.parse(settingsContent);
|
|
1551
|
+
} catch {
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
const tokenUsage = settings.tokenUsage;
|
|
1555
|
+
if (!tokenUsage) continue;
|
|
1556
|
+
const cachedTokens = toSafeNumber3(tokenUsage.cacheReadTokens);
|
|
1557
|
+
const reasoningTokens = toSafeNumber3(tokenUsage.thinkingTokens);
|
|
1558
|
+
const inputTokens = Math.max(
|
|
1559
|
+
0,
|
|
1560
|
+
toSafeNumber3(tokenUsage.inputTokens) - cachedTokens
|
|
1561
|
+
);
|
|
1562
|
+
const outputTokens = Math.max(
|
|
1563
|
+
0,
|
|
1564
|
+
toSafeNumber3(tokenUsage.outputTokens) - reasoningTokens
|
|
1565
|
+
);
|
|
1566
|
+
if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
entries.push({
|
|
1570
|
+
sessionId,
|
|
1571
|
+
source: TOOL_ID3,
|
|
1572
|
+
model: settings.model || "unknown",
|
|
1573
|
+
project,
|
|
1574
|
+
timestamp: firstMessageTimestamp,
|
|
1575
|
+
inputTokens,
|
|
1576
|
+
outputTokens,
|
|
1577
|
+
reasoningTokens,
|
|
1578
|
+
cachedTokens
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
buckets: aggregateToBuckets(entries),
|
|
1583
|
+
sessions: extractSessions(sessionEvents, entries)
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
isInstalled() {
|
|
1587
|
+
return existsSync10(this.dataDir);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
registerParser(new DroidParser());
|
|
1591
|
+
|
|
1592
|
+
// src/parsers/pi-coding-agent.ts
|
|
1593
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1594
|
+
import { homedir as homedir10 } from "os";
|
|
1595
|
+
import { join as join11 } from "path";
|
|
1596
|
+
var TOOL_ID4 = "pi-coding-agent";
|
|
1597
|
+
var TOOL_NAME4 = "pi";
|
|
1598
|
+
var DEFAULT_SESSIONS_DIR2 = join11(homedir10(), ".pi", "agent", "sessions");
|
|
1599
|
+
function createToolDefinition4(dataDir) {
|
|
1600
|
+
return {
|
|
1601
|
+
id: TOOL_ID4,
|
|
1602
|
+
name: TOOL_NAME4,
|
|
1603
|
+
dataDir
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
function toSafeNumber4(value) {
|
|
1607
|
+
const numberValue = Number(value);
|
|
1608
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
1609
|
+
}
|
|
1610
|
+
function getPathLeaf4(value) {
|
|
1611
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1612
|
+
const leaf = normalized.split("/").filter(Boolean).pop();
|
|
1613
|
+
return leaf || "unknown";
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeForPrefix2(value) {
|
|
1616
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1617
|
+
}
|
|
1618
|
+
function getUsageNumber(usage, ...keys) {
|
|
1619
|
+
for (const key of keys) {
|
|
1620
|
+
const value = usage[key];
|
|
1621
|
+
const numberValue = toSafeNumber4(value);
|
|
1622
|
+
if (numberValue > 0) {
|
|
1623
|
+
return numberValue;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return 0;
|
|
1627
|
+
}
|
|
1628
|
+
function extractPiProjectFromCwd(cwd) {
|
|
1629
|
+
return getPathLeaf4(cwd);
|
|
1630
|
+
}
|
|
1631
|
+
function extractPiProjectFromDir(filePath, sessionsDir = DEFAULT_SESSIONS_DIR2) {
|
|
1632
|
+
const normalizedFilePath = normalizeForPrefix2(filePath);
|
|
1633
|
+
const normalizedSessionsDir = normalizeForPrefix2(sessionsDir);
|
|
1634
|
+
const prefix = `${normalizedSessionsDir}/`;
|
|
1635
|
+
if (!normalizedFilePath.startsWith(prefix)) {
|
|
1636
|
+
return "unknown";
|
|
1637
|
+
}
|
|
1638
|
+
const relativePath = normalizedFilePath.slice(prefix.length);
|
|
1639
|
+
const firstSegment = relativePath.split("/")[0];
|
|
1640
|
+
if (!firstSegment) {
|
|
1641
|
+
return "unknown";
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
const decoded = decodeURIComponent(firstSegment);
|
|
1645
|
+
if (decoded.includes("/") || decoded.includes("\\")) {
|
|
1646
|
+
return getPathLeaf4(decoded);
|
|
1647
|
+
}
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
const slugParts = firstSegment.split("-").filter(Boolean);
|
|
1651
|
+
return slugParts.length > 0 ? slugParts[slugParts.length - 1] : "unknown";
|
|
1652
|
+
}
|
|
1653
|
+
var PiCodingAgentParser = class {
|
|
1654
|
+
constructor(sessionsDir = DEFAULT_SESSIONS_DIR2) {
|
|
1655
|
+
this.sessionsDir = sessionsDir;
|
|
1656
|
+
this.tool = createToolDefinition4(sessionsDir);
|
|
1657
|
+
}
|
|
1658
|
+
tool;
|
|
1659
|
+
async parse() {
|
|
1660
|
+
const sessionFiles = findJsonlFiles(this.sessionsDir);
|
|
1661
|
+
if (sessionFiles.length === 0) {
|
|
1662
|
+
return { buckets: [], sessions: [] };
|
|
1663
|
+
}
|
|
1664
|
+
const entries = [];
|
|
1665
|
+
const sessionEvents = [];
|
|
1666
|
+
const seenEntryIds = /* @__PURE__ */ new Set();
|
|
1667
|
+
for (const filePath of sessionFiles) {
|
|
1668
|
+
const content = readFileSafe(filePath);
|
|
1669
|
+
if (!content) continue;
|
|
1670
|
+
const rows = parseJsonl(content);
|
|
1671
|
+
if (rows.length === 0) continue;
|
|
1672
|
+
let sessionId = filePath;
|
|
1673
|
+
let project = extractPiProjectFromDir(filePath, this.sessionsDir);
|
|
1674
|
+
for (const row of rows) {
|
|
1675
|
+
if (row.type !== "session") continue;
|
|
1676
|
+
if (row.id) {
|
|
1677
|
+
sessionId = row.id;
|
|
1678
|
+
}
|
|
1679
|
+
if (row.cwd) {
|
|
1680
|
+
project = extractPiProjectFromCwd(row.cwd);
|
|
1681
|
+
}
|
|
1682
|
+
break;
|
|
1683
|
+
}
|
|
1684
|
+
for (const row of rows) {
|
|
1685
|
+
if (row.type !== "message") continue;
|
|
1686
|
+
const message = row.message;
|
|
1687
|
+
if (!message) continue;
|
|
1688
|
+
const rawTimestamp = row.timestamp || message.timestamp;
|
|
1689
|
+
if (!rawTimestamp) continue;
|
|
1690
|
+
const timestamp = new Date(rawTimestamp);
|
|
1691
|
+
if (Number.isNaN(timestamp.getTime())) continue;
|
|
1692
|
+
if (message.role === "user" || message.role === "assistant") {
|
|
1693
|
+
sessionEvents.push({
|
|
1694
|
+
sessionId,
|
|
1695
|
+
source: TOOL_ID4,
|
|
1696
|
+
project,
|
|
1697
|
+
timestamp,
|
|
1698
|
+
role: message.role
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
if (message.role !== "assistant") continue;
|
|
1702
|
+
const usage = message.usage;
|
|
1703
|
+
if (!usage) continue;
|
|
1704
|
+
const inputTokens = getUsageNumber(usage, "input", "inputTokens");
|
|
1705
|
+
const outputTokens = getUsageNumber(usage, "output", "outputTokens");
|
|
1706
|
+
const cachedTokens = getUsageNumber(
|
|
1707
|
+
usage,
|
|
1708
|
+
"cacheRead",
|
|
1709
|
+
"cacheReadTokens",
|
|
1710
|
+
"cache_read"
|
|
1711
|
+
);
|
|
1712
|
+
const reasoningTokens = getUsageNumber(
|
|
1713
|
+
usage,
|
|
1714
|
+
"reasoningOutputTokens",
|
|
1715
|
+
"thinkingTokens",
|
|
1716
|
+
"thoughts"
|
|
1717
|
+
);
|
|
1718
|
+
if (inputTokens === 0 && outputTokens === 0 && cachedTokens === 0 && reasoningTokens === 0) {
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
if (row.id) {
|
|
1722
|
+
if (seenEntryIds.has(row.id)) continue;
|
|
1723
|
+
seenEntryIds.add(row.id);
|
|
1724
|
+
}
|
|
1725
|
+
entries.push({
|
|
1726
|
+
sessionId,
|
|
1727
|
+
source: TOOL_ID4,
|
|
1728
|
+
model: message.model || "unknown",
|
|
1729
|
+
project,
|
|
1730
|
+
timestamp,
|
|
1731
|
+
inputTokens,
|
|
1732
|
+
outputTokens,
|
|
1733
|
+
reasoningTokens,
|
|
1734
|
+
cachedTokens
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
buckets: aggregateToBuckets(entries),
|
|
1740
|
+
sessions: extractSessions(sessionEvents, entries)
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
isInstalled() {
|
|
1744
|
+
return existsSync11(this.sessionsDir);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
registerParser(new PiCodingAgentParser());
|
|
1748
|
+
|
|
1101
1749
|
// src/cli.ts
|
|
1102
1750
|
import { Command, Option } from "commander";
|
|
1103
1751
|
|
|
1104
1752
|
// src/infrastructure/config/manager.ts
|
|
1105
1753
|
import { randomUUID } from "crypto";
|
|
1106
1754
|
import {
|
|
1107
|
-
existsSync as
|
|
1755
|
+
existsSync as existsSync12,
|
|
1108
1756
|
mkdirSync,
|
|
1109
1757
|
readFileSync as readFileSync6,
|
|
1110
1758
|
unlinkSync,
|
|
1111
1759
|
writeFileSync
|
|
1112
1760
|
} from "fs";
|
|
1113
|
-
import { join as
|
|
1761
|
+
import { join as join13 } from "path";
|
|
1114
1762
|
|
|
1115
1763
|
// src/infrastructure/xdg.ts
|
|
1116
|
-
import { homedir as
|
|
1117
|
-
import { join as
|
|
1764
|
+
import { homedir as homedir11 } from "os";
|
|
1765
|
+
import { join as join12 } from "path";
|
|
1118
1766
|
function getConfigHome() {
|
|
1119
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
1767
|
+
return process.env.XDG_CONFIG_HOME || join12(homedir11(), ".config");
|
|
1120
1768
|
}
|
|
1121
1769
|
function getStateHome() {
|
|
1122
|
-
return process.env.XDG_STATE_HOME ||
|
|
1770
|
+
return process.env.XDG_STATE_HOME || join12(homedir11(), ".local", "state");
|
|
1123
1771
|
}
|
|
1124
1772
|
function getRuntimeDir() {
|
|
1125
1773
|
return process.env.XDG_RUNTIME_DIR || getStateHome();
|
|
1126
1774
|
}
|
|
1127
1775
|
|
|
1128
1776
|
// src/infrastructure/config/manager.ts
|
|
1129
|
-
var CONFIG_DIR =
|
|
1777
|
+
var CONFIG_DIR = join13(getConfigHome(), "tokenarena");
|
|
1130
1778
|
var isDev = process.env.TOKEN_ARENA_DEV === "1";
|
|
1131
|
-
var CONFIG_FILE =
|
|
1779
|
+
var CONFIG_FILE = join13(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
|
|
1132
1780
|
var DEFAULT_API_URL = "https://token.poco-ai.com";
|
|
1133
1781
|
var VALID_CONFIG_KEYS = [
|
|
1134
1782
|
"apiKey",
|
|
@@ -1144,7 +1792,7 @@ function getConfigDir() {
|
|
|
1144
1792
|
return CONFIG_DIR;
|
|
1145
1793
|
}
|
|
1146
1794
|
function loadConfig() {
|
|
1147
|
-
if (!
|
|
1795
|
+
if (!existsSync12(CONFIG_FILE)) return null;
|
|
1148
1796
|
try {
|
|
1149
1797
|
const raw = readFileSync6(CONFIG_FILE, "utf-8");
|
|
1150
1798
|
const config = JSON.parse(raw);
|
|
@@ -1162,7 +1810,7 @@ function saveConfig(config) {
|
|
|
1162
1810
|
`, "utf-8");
|
|
1163
1811
|
}
|
|
1164
1812
|
function deleteConfig() {
|
|
1165
|
-
if (
|
|
1813
|
+
if (existsSync12(CONFIG_FILE)) {
|
|
1166
1814
|
unlinkSync(CONFIG_FILE);
|
|
1167
1815
|
}
|
|
1168
1816
|
}
|
|
@@ -1182,150 +1830,6 @@ function getDefaultApiUrl() {
|
|
|
1182
1830
|
return process.env.TOKEN_ARENA_API_URL || DEFAULT_API_URL;
|
|
1183
1831
|
}
|
|
1184
1832
|
|
|
1185
|
-
// src/infrastructure/ui/format.ts
|
|
1186
|
-
var hasColor = Boolean(process.stdout.isTTY && process.env.NO_COLOR !== "1");
|
|
1187
|
-
function withCode(code, value) {
|
|
1188
|
-
if (!hasColor) return value;
|
|
1189
|
-
return `\x1B[${code}m${value}\x1B[0m`;
|
|
1190
|
-
}
|
|
1191
|
-
function bold(value) {
|
|
1192
|
-
return withCode("1", value);
|
|
1193
|
-
}
|
|
1194
|
-
function dim(value) {
|
|
1195
|
-
return withCode("2", value);
|
|
1196
|
-
}
|
|
1197
|
-
function cyan(value) {
|
|
1198
|
-
return withCode("36", value);
|
|
1199
|
-
}
|
|
1200
|
-
function green(value) {
|
|
1201
|
-
return withCode("32", value);
|
|
1202
|
-
}
|
|
1203
|
-
function yellow(value) {
|
|
1204
|
-
return withCode("33", value);
|
|
1205
|
-
}
|
|
1206
|
-
function red(value) {
|
|
1207
|
-
return withCode("31", value);
|
|
1208
|
-
}
|
|
1209
|
-
function magenta(value) {
|
|
1210
|
-
return withCode("35", value);
|
|
1211
|
-
}
|
|
1212
|
-
function formatHeader(title, subtitle) {
|
|
1213
|
-
const lines = [`${cyan("\u25C8")} ${bold(title)}`];
|
|
1214
|
-
if (subtitle) {
|
|
1215
|
-
lines.push(dim(subtitle));
|
|
1216
|
-
}
|
|
1217
|
-
return `
|
|
1218
|
-
${lines.join("\n")}`;
|
|
1219
|
-
}
|
|
1220
|
-
function formatSection(title) {
|
|
1221
|
-
return `
|
|
1222
|
-
${bold(title)}`;
|
|
1223
|
-
}
|
|
1224
|
-
function formatKeyValue(label, value) {
|
|
1225
|
-
return ` ${dim(label.padEnd(14, " "))} ${value}`;
|
|
1226
|
-
}
|
|
1227
|
-
function formatBullet(value, tone = "neutral") {
|
|
1228
|
-
const icon = tone === "success" ? green("\u2714") : tone === "warning" ? yellow("!") : tone === "danger" ? red("\u2716") : cyan("\u2022");
|
|
1229
|
-
return ` ${icon} ${value}`;
|
|
1230
|
-
}
|
|
1231
|
-
function formatMutedPath(path) {
|
|
1232
|
-
return dim(path);
|
|
1233
|
-
}
|
|
1234
|
-
function maskSecret(value, visible = 8) {
|
|
1235
|
-
if (!value) return "(empty)";
|
|
1236
|
-
if (value.length <= visible) return value;
|
|
1237
|
-
return `${value.slice(0, visible)}\u2026`;
|
|
1238
|
-
}
|
|
1239
|
-
function formatStatusBadge(label, tone = "neutral") {
|
|
1240
|
-
if (tone === "success") return green(label);
|
|
1241
|
-
if (tone === "warning") return yellow(label);
|
|
1242
|
-
if (tone === "danger") return red(label);
|
|
1243
|
-
return magenta(label);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// src/infrastructure/ui/prompts.ts
|
|
1247
|
-
import {
|
|
1248
|
-
confirm,
|
|
1249
|
-
input,
|
|
1250
|
-
password,
|
|
1251
|
-
select
|
|
1252
|
-
} from "@inquirer/prompts";
|
|
1253
|
-
function isInteractiveTerminal() {
|
|
1254
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1255
|
-
}
|
|
1256
|
-
async function promptConfirm(options) {
|
|
1257
|
-
return confirm({
|
|
1258
|
-
message: options.message,
|
|
1259
|
-
default: options.defaultValue
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
async function promptText(options) {
|
|
1263
|
-
return input({
|
|
1264
|
-
message: options.message,
|
|
1265
|
-
default: options.defaultValue,
|
|
1266
|
-
validate: options.validate
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
async function promptPassword(options) {
|
|
1270
|
-
return password({
|
|
1271
|
-
message: options.message,
|
|
1272
|
-
mask: options.mask ?? "*",
|
|
1273
|
-
validate: options.validate
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
async function promptSelect(options) {
|
|
1277
|
-
return select({
|
|
1278
|
-
message: options.message,
|
|
1279
|
-
choices: [...options.choices],
|
|
1280
|
-
pageSize: Math.min(Math.max(options.choices.length, 6), 10)
|
|
1281
|
-
});
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// src/utils/logger.ts
|
|
1285
|
-
var LOG_LEVELS = {
|
|
1286
|
-
debug: 0,
|
|
1287
|
-
info: 1,
|
|
1288
|
-
warn: 2,
|
|
1289
|
-
error: 3
|
|
1290
|
-
};
|
|
1291
|
-
var Logger = class {
|
|
1292
|
-
level;
|
|
1293
|
-
constructor(level = "info") {
|
|
1294
|
-
this.level = level;
|
|
1295
|
-
}
|
|
1296
|
-
setLevel(level) {
|
|
1297
|
-
this.level = level;
|
|
1298
|
-
}
|
|
1299
|
-
debug(msg) {
|
|
1300
|
-
if (LOG_LEVELS[this.level] <= LOG_LEVELS.debug) {
|
|
1301
|
-
process.stderr.write(`[debug] ${msg}
|
|
1302
|
-
`);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
info(msg) {
|
|
1306
|
-
if (LOG_LEVELS[this.level] <= LOG_LEVELS.info) {
|
|
1307
|
-
process.stdout.write(`${msg}
|
|
1308
|
-
`);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
warn(msg) {
|
|
1312
|
-
if (LOG_LEVELS[this.level] <= LOG_LEVELS.warn) {
|
|
1313
|
-
process.stderr.write(`warn: ${msg}
|
|
1314
|
-
`);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
error(msg) {
|
|
1318
|
-
if (LOG_LEVELS[this.level] <= LOG_LEVELS.error) {
|
|
1319
|
-
process.stderr.write(`error: ${msg}
|
|
1320
|
-
`);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
log(msg) {
|
|
1324
|
-
this.info(msg);
|
|
1325
|
-
}
|
|
1326
|
-
};
|
|
1327
|
-
var logger = new Logger();
|
|
1328
|
-
|
|
1329
1833
|
// src/commands/config.ts
|
|
1330
1834
|
var VALID_KEYS = ["apiKey", "apiUrl", "syncInterval", "logLevel"];
|
|
1331
1835
|
function isConfigKey(value) {
|
|
@@ -1631,14 +2135,14 @@ import { hostname as hostname3 } from "os";
|
|
|
1631
2135
|
|
|
1632
2136
|
// src/domain/project-identity.ts
|
|
1633
2137
|
import { createHmac } from "crypto";
|
|
1634
|
-
function toProjectIdentity(
|
|
1635
|
-
if (
|
|
2138
|
+
function toProjectIdentity(input) {
|
|
2139
|
+
if (input.mode === "disabled") {
|
|
1636
2140
|
return { projectKey: "unknown", projectLabel: "Unknown Project" };
|
|
1637
2141
|
}
|
|
1638
|
-
if (
|
|
1639
|
-
return { projectKey:
|
|
2142
|
+
if (input.mode === "raw") {
|
|
2143
|
+
return { projectKey: input.project, projectLabel: input.project };
|
|
1640
2144
|
}
|
|
1641
|
-
const projectKey = createHmac("sha256",
|
|
2145
|
+
const projectKey = createHmac("sha256", input.salt).update(input.project).digest("hex").slice(0, 16);
|
|
1642
2146
|
return {
|
|
1643
2147
|
projectKey,
|
|
1644
2148
|
projectLabel: `Project ${projectKey.slice(0, 6)}`
|
|
@@ -1654,13 +2158,13 @@ function shortHash(value) {
|
|
|
1654
2158
|
function normalizeApiUrl(apiUrl) {
|
|
1655
2159
|
return apiUrl.replace(/\/+$/, "");
|
|
1656
2160
|
}
|
|
1657
|
-
function buildUploadManifestScope(
|
|
2161
|
+
function buildUploadManifestScope(input) {
|
|
1658
2162
|
return {
|
|
1659
|
-
apiKeyHash: shortHash(
|
|
1660
|
-
apiUrl: normalizeApiUrl(
|
|
1661
|
-
deviceId:
|
|
1662
|
-
projectHashSaltHash: shortHash(
|
|
1663
|
-
projectMode:
|
|
2163
|
+
apiKeyHash: shortHash(input.apiKey),
|
|
2164
|
+
apiUrl: normalizeApiUrl(input.apiUrl),
|
|
2165
|
+
deviceId: input.deviceId,
|
|
2166
|
+
projectHashSaltHash: shortHash(input.settings.projectHashSalt),
|
|
2167
|
+
projectMode: input.settings.projectMode
|
|
1664
2168
|
};
|
|
1665
2169
|
}
|
|
1666
2170
|
function describeUploadManifestScopeChanges(previous, current) {
|
|
@@ -1730,20 +2234,20 @@ function buildRecordHashes(items, getKey, getHash) {
|
|
|
1730
2234
|
}
|
|
1731
2235
|
return hashes;
|
|
1732
2236
|
}
|
|
1733
|
-
function createUploadManifest(
|
|
2237
|
+
function createUploadManifest(input) {
|
|
1734
2238
|
return {
|
|
1735
2239
|
buckets: buildRecordHashes(
|
|
1736
|
-
|
|
2240
|
+
input.buckets,
|
|
1737
2241
|
getUploadBucketManifestKey,
|
|
1738
2242
|
getUploadBucketContentHash
|
|
1739
2243
|
),
|
|
1740
|
-
scope:
|
|
2244
|
+
scope: input.scope,
|
|
1741
2245
|
sessions: buildRecordHashes(
|
|
1742
|
-
|
|
2246
|
+
input.sessions,
|
|
1743
2247
|
getUploadSessionManifestKey,
|
|
1744
2248
|
getUploadSessionContentHash
|
|
1745
2249
|
),
|
|
1746
|
-
updatedAt:
|
|
2250
|
+
updatedAt: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1747
2251
|
version: MANIFEST_VERSION
|
|
1748
2252
|
};
|
|
1749
2253
|
}
|
|
@@ -1756,21 +2260,21 @@ function countRemovedRecords(previous, current) {
|
|
|
1756
2260
|
}
|
|
1757
2261
|
return removed;
|
|
1758
2262
|
}
|
|
1759
|
-
function diffUploadManifest(
|
|
2263
|
+
function diffUploadManifest(input) {
|
|
1760
2264
|
const nextManifest = createUploadManifest({
|
|
1761
|
-
buckets:
|
|
1762
|
-
scope:
|
|
1763
|
-
sessions:
|
|
1764
|
-
updatedAt:
|
|
2265
|
+
buckets: input.buckets,
|
|
2266
|
+
scope: input.scope,
|
|
2267
|
+
sessions: input.sessions,
|
|
2268
|
+
updatedAt: input.updatedAt
|
|
1765
2269
|
});
|
|
1766
|
-
const scopeChangedReasons =
|
|
1767
|
-
const previousBuckets =
|
|
1768
|
-
const previousSessions =
|
|
1769
|
-
const bucketsToUpload =
|
|
2270
|
+
const scopeChangedReasons = input.previous ? describeUploadManifestScopeChanges(input.previous.scope, input.scope) : [];
|
|
2271
|
+
const previousBuckets = input.previous && scopeChangedReasons.length === 0 ? input.previous.buckets : {};
|
|
2272
|
+
const previousSessions = input.previous && scopeChangedReasons.length === 0 ? input.previous.sessions : {};
|
|
2273
|
+
const bucketsToUpload = input.buckets.filter((bucket) => {
|
|
1770
2274
|
const key = getUploadBucketManifestKey(bucket);
|
|
1771
2275
|
return previousBuckets[key] !== nextManifest.buckets[key];
|
|
1772
2276
|
});
|
|
1773
|
-
const sessionsToUpload =
|
|
2277
|
+
const sessionsToUpload = input.sessions.filter((session) => {
|
|
1774
2278
|
const key = getUploadSessionManifestKey(session);
|
|
1775
2279
|
return previousSessions[key] !== nextManifest.sessions[key];
|
|
1776
2280
|
});
|
|
@@ -1784,8 +2288,8 @@ function diffUploadManifest(input2) {
|
|
|
1784
2288
|
),
|
|
1785
2289
|
scopeChangedReasons,
|
|
1786
2290
|
sessionsToUpload,
|
|
1787
|
-
unchangedBuckets:
|
|
1788
|
-
unchangedSessions:
|
|
2291
|
+
unchangedBuckets: input.buckets.length - bucketsToUpload.length,
|
|
2292
|
+
unchangedSessions: input.sessions.length - sessionsToUpload.length
|
|
1789
2293
|
};
|
|
1790
2294
|
}
|
|
1791
2295
|
|
|
@@ -2019,7 +2523,7 @@ var ApiClient = class {
|
|
|
2019
2523
|
// src/infrastructure/runtime/lock.ts
|
|
2020
2524
|
import {
|
|
2021
2525
|
closeSync,
|
|
2022
|
-
existsSync as
|
|
2526
|
+
existsSync as existsSync13,
|
|
2023
2527
|
openSync,
|
|
2024
2528
|
readFileSync as readFileSync7,
|
|
2025
2529
|
rmSync,
|
|
@@ -2028,22 +2532,22 @@ import {
|
|
|
2028
2532
|
|
|
2029
2533
|
// src/infrastructure/runtime/paths.ts
|
|
2030
2534
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2031
|
-
import { join as
|
|
2535
|
+
import { join as join14 } from "path";
|
|
2032
2536
|
var APP_NAME = "tokenarena";
|
|
2033
2537
|
function getRuntimeDirPath() {
|
|
2034
|
-
return
|
|
2538
|
+
return join14(getRuntimeDir(), APP_NAME);
|
|
2035
2539
|
}
|
|
2036
2540
|
function getStateDir() {
|
|
2037
|
-
return
|
|
2541
|
+
return join14(getStateHome(), APP_NAME);
|
|
2038
2542
|
}
|
|
2039
2543
|
function getSyncLockPath() {
|
|
2040
|
-
return
|
|
2544
|
+
return join14(getRuntimeDirPath(), "sync.lock");
|
|
2041
2545
|
}
|
|
2042
2546
|
function getSyncStatePath() {
|
|
2043
|
-
return
|
|
2547
|
+
return join14(getStateDir(), "status.json");
|
|
2044
2548
|
}
|
|
2045
2549
|
function getUploadManifestPath() {
|
|
2046
|
-
return
|
|
2550
|
+
return join14(getStateDir(), "upload-manifest.json");
|
|
2047
2551
|
}
|
|
2048
2552
|
function ensureAppDirs() {
|
|
2049
2553
|
mkdirSync2(getRuntimeDirPath(), { recursive: true });
|
|
@@ -2061,7 +2565,7 @@ function isProcessAlive(pid) {
|
|
|
2061
2565
|
}
|
|
2062
2566
|
}
|
|
2063
2567
|
function readLockMetadata(lockPath) {
|
|
2064
|
-
if (!
|
|
2568
|
+
if (!existsSync13(lockPath)) {
|
|
2065
2569
|
return null;
|
|
2066
2570
|
}
|
|
2067
2571
|
try {
|
|
@@ -2135,13 +2639,13 @@ function describeExistingSyncLock() {
|
|
|
2135
2639
|
}
|
|
2136
2640
|
|
|
2137
2641
|
// src/infrastructure/runtime/state.ts
|
|
2138
|
-
import { existsSync as
|
|
2642
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
2139
2643
|
function getDefaultState() {
|
|
2140
2644
|
return { status: "idle" };
|
|
2141
2645
|
}
|
|
2142
2646
|
function loadSyncState() {
|
|
2143
2647
|
const path = getSyncStatePath();
|
|
2144
|
-
if (!
|
|
2648
|
+
if (!existsSync14(path)) {
|
|
2145
2649
|
return getDefaultState();
|
|
2146
2650
|
}
|
|
2147
2651
|
try {
|
|
@@ -2202,7 +2706,7 @@ function markSyncFailed(source, error, status) {
|
|
|
2202
2706
|
}
|
|
2203
2707
|
|
|
2204
2708
|
// src/infrastructure/runtime/upload-manifest.ts
|
|
2205
|
-
import { existsSync as
|
|
2709
|
+
import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2206
2710
|
function isRecordOfStrings(value) {
|
|
2207
2711
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2208
2712
|
return false;
|
|
@@ -2218,7 +2722,7 @@ function isUploadManifest(value) {
|
|
|
2218
2722
|
}
|
|
2219
2723
|
function loadUploadManifest() {
|
|
2220
2724
|
const path = getUploadManifestPath();
|
|
2221
|
-
if (!
|
|
2725
|
+
if (!existsSync15(path)) {
|
|
2222
2726
|
return null;
|
|
2223
2727
|
}
|
|
2224
2728
|
try {
|
|
@@ -2646,15 +3150,15 @@ View your dashboard at: ${apiUrl}/usage`);
|
|
|
2646
3150
|
|
|
2647
3151
|
// src/commands/init.ts
|
|
2648
3152
|
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
2649
|
-
import { existsSync as
|
|
3153
|
+
import { existsSync as existsSync16 } from "fs";
|
|
2650
3154
|
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
2651
|
-
import { homedir as
|
|
2652
|
-
import { dirname as
|
|
3155
|
+
import { homedir as homedir12, platform } from "os";
|
|
3156
|
+
import { dirname as dirname3, join as join15, posix, win32 } from "path";
|
|
2653
3157
|
function joinForPlatform(currentPlatform, ...parts) {
|
|
2654
3158
|
return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
|
|
2655
3159
|
}
|
|
2656
|
-
function basenameLikeShell(
|
|
2657
|
-
return
|
|
3160
|
+
function basenameLikeShell(input) {
|
|
3161
|
+
return input.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input;
|
|
2658
3162
|
}
|
|
2659
3163
|
function getBrowserLaunchCommand(url, currentPlatform = platform()) {
|
|
2660
3164
|
switch (currentPlatform) {
|
|
@@ -2693,7 +3197,7 @@ function resolvePowerShellProfilePath() {
|
|
|
2693
3197
|
const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
|
|
2694
3198
|
const candidates = [
|
|
2695
3199
|
"pwsh.exe",
|
|
2696
|
-
|
|
3200
|
+
join15(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
2697
3201
|
];
|
|
2698
3202
|
for (const command of candidates) {
|
|
2699
3203
|
try {
|
|
@@ -2722,8 +3226,8 @@ function resolvePowerShellProfilePath() {
|
|
|
2722
3226
|
function resolveShellAliasSetup(options = {}) {
|
|
2723
3227
|
const currentPlatform = options.currentPlatform ?? platform();
|
|
2724
3228
|
const env = options.env ?? process.env;
|
|
2725
|
-
const homeDir = options.homeDir ??
|
|
2726
|
-
const pathExists = options.exists ??
|
|
3229
|
+
const homeDir = options.homeDir ?? homedir12();
|
|
3230
|
+
const pathExists = options.exists ?? existsSync16;
|
|
2727
3231
|
const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
|
|
2728
3232
|
const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
|
|
2729
3233
|
const aliasName = "ta";
|
|
@@ -2879,6 +3383,30 @@ async function runInit(opts = {}) {
|
|
|
2879
3383
|
logger.info(formatBullet("TokenArena \u5DF2\u51C6\u5907\u5C31\u7EEA\u3002", "success"));
|
|
2880
3384
|
logger.info(formatKeyValue("\u63A7\u5236\u53F0", `${apiUrl}/usage`));
|
|
2881
3385
|
await setupShellAlias();
|
|
3386
|
+
await setupSystemdService();
|
|
3387
|
+
}
|
|
3388
|
+
async function setupSystemdService() {
|
|
3389
|
+
const currentPlatform = platform();
|
|
3390
|
+
if (currentPlatform !== "linux") {
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
if (!isCommandAvailable("systemctl")) {
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
const shouldSetup = await promptConfirm({
|
|
3397
|
+
message: "\u662F\u5426\u8BBE\u7F6E systemd \u670D\u52A1\u4EE5\u81EA\u52A8\u8FD0\u884C daemon\uFF1F",
|
|
3398
|
+
defaultValue: true
|
|
3399
|
+
});
|
|
3400
|
+
if (!shouldSetup) {
|
|
3401
|
+
logger.info(formatBullet("\u5DF2\u8DF3\u8FC7 systemd \u670D\u52A1\u8BBE\u7F6E\u3002"));
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
try {
|
|
3405
|
+
const { runInstallService: runInstallService2 } = await import("./service-4U7K4DKW.js");
|
|
3406
|
+
await runInstallService2({ action: "setup", skipPrompt: true });
|
|
3407
|
+
} catch (err) {
|
|
3408
|
+
logger.warn(`systemd \u670D\u52A1\u8BBE\u7F6E\u5931\u8D25: ${err.message}`);
|
|
3409
|
+
}
|
|
2882
3410
|
}
|
|
2883
3411
|
async function setupShellAlias() {
|
|
2884
3412
|
const setup = resolveShellAliasSetup();
|
|
@@ -2894,9 +3422,9 @@ async function setupShellAlias() {
|
|
|
2894
3422
|
return;
|
|
2895
3423
|
}
|
|
2896
3424
|
try {
|
|
2897
|
-
await mkdir(
|
|
3425
|
+
await mkdir(dirname3(setup.configFile), { recursive: true });
|
|
2898
3426
|
let existingContent = "";
|
|
2899
|
-
if (
|
|
3427
|
+
if (existsSync16(setup.configFile)) {
|
|
2900
3428
|
existingContent = await readFile(setup.configFile, "utf-8");
|
|
2901
3429
|
}
|
|
2902
3430
|
const normalizedContent = existingContent.toLowerCase();
|
|
@@ -3089,8 +3617,8 @@ async function runSyncCommand(opts = {}) {
|
|
|
3089
3617
|
}
|
|
3090
3618
|
|
|
3091
3619
|
// src/commands/uninstall.ts
|
|
3092
|
-
import { existsSync as
|
|
3093
|
-
import { homedir as
|
|
3620
|
+
import { existsSync as existsSync17, readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3621
|
+
import { homedir as homedir13, platform as platform2 } from "os";
|
|
3094
3622
|
function removeShellAlias() {
|
|
3095
3623
|
const shell = process.env.SHELL;
|
|
3096
3624
|
if (!shell) return;
|
|
@@ -3099,22 +3627,22 @@ function removeShellAlias() {
|
|
|
3099
3627
|
let configFile;
|
|
3100
3628
|
switch (shellName) {
|
|
3101
3629
|
case "zsh":
|
|
3102
|
-
configFile = `${
|
|
3630
|
+
configFile = `${homedir13()}/.zshrc`;
|
|
3103
3631
|
break;
|
|
3104
3632
|
case "bash":
|
|
3105
|
-
if (platform2() === "darwin" &&
|
|
3106
|
-
configFile = `${
|
|
3633
|
+
if (platform2() === "darwin" && existsSync17(`${homedir13()}/.bash_profile`)) {
|
|
3634
|
+
configFile = `${homedir13()}/.bash_profile`;
|
|
3107
3635
|
} else {
|
|
3108
|
-
configFile = `${
|
|
3636
|
+
configFile = `${homedir13()}/.bashrc`;
|
|
3109
3637
|
}
|
|
3110
3638
|
break;
|
|
3111
3639
|
case "fish":
|
|
3112
|
-
configFile = `${
|
|
3640
|
+
configFile = `${homedir13()}/.config/fish/config.fish`;
|
|
3113
3641
|
break;
|
|
3114
3642
|
default:
|
|
3115
3643
|
return;
|
|
3116
3644
|
}
|
|
3117
|
-
if (!
|
|
3645
|
+
if (!existsSync17(configFile)) return;
|
|
3118
3646
|
try {
|
|
3119
3647
|
let content = readFileSync10(configFile, "utf-8");
|
|
3120
3648
|
const aliasPatterns = [
|
|
@@ -3149,7 +3677,7 @@ function removeShellAlias() {
|
|
|
3149
3677
|
async function runUninstall() {
|
|
3150
3678
|
const configPath = getConfigPath();
|
|
3151
3679
|
const configDir = getConfigDir();
|
|
3152
|
-
if (!
|
|
3680
|
+
if (!existsSync17(configPath)) {
|
|
3153
3681
|
logger.info(formatHeader("\u5378\u8F7D TokenArena"));
|
|
3154
3682
|
logger.info(formatBullet("\u672A\u53D1\u73B0\u672C\u5730\u914D\u7F6E\uFF0C\u65E0\u9700\u5378\u8F7D\u3002"));
|
|
3155
3683
|
return;
|
|
@@ -3178,7 +3706,7 @@ async function runUninstall() {
|
|
|
3178
3706
|
deleteConfig();
|
|
3179
3707
|
logger.info(formatSection("\u6267\u884C\u7ED3\u679C"));
|
|
3180
3708
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u6587\u4EF6\u3002", "success"));
|
|
3181
|
-
if (
|
|
3709
|
+
if (existsSync17(configDir)) {
|
|
3182
3710
|
try {
|
|
3183
3711
|
rmSync2(configDir, { recursive: false, force: true });
|
|
3184
3712
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u914D\u7F6E\u76EE\u5F55\u3002", "success"));
|
|
@@ -3186,12 +3714,12 @@ async function runUninstall() {
|
|
|
3186
3714
|
}
|
|
3187
3715
|
}
|
|
3188
3716
|
const stateDir = getStateDir();
|
|
3189
|
-
if (
|
|
3717
|
+
if (existsSync17(stateDir)) {
|
|
3190
3718
|
rmSync2(stateDir, { recursive: true, force: true });
|
|
3191
3719
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u72B6\u6001\u6570\u636E\u3002", "success"));
|
|
3192
3720
|
}
|
|
3193
3721
|
const runtimeDir = getRuntimeDirPath();
|
|
3194
|
-
if (
|
|
3722
|
+
if (existsSync17(runtimeDir)) {
|
|
3195
3723
|
rmSync2(runtimeDir, { recursive: true, force: true });
|
|
3196
3724
|
logger.info(formatBullet("\u5DF2\u5220\u9664\u8FD0\u884C\u65F6\u6570\u636E\u3002", "success"));
|
|
3197
3725
|
}
|
|
@@ -3316,7 +3844,7 @@ async function runHome(program) {
|
|
|
3316
3844
|
|
|
3317
3845
|
// src/infrastructure/runtime/cli-version.ts
|
|
3318
3846
|
import { readFileSync as readFileSync11 } from "fs";
|
|
3319
|
-
import { dirname as
|
|
3847
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
3320
3848
|
import { fileURLToPath } from "url";
|
|
3321
3849
|
var FALLBACK_VERSION = "0.0.0";
|
|
3322
3850
|
var cachedVersion;
|
|
@@ -3324,8 +3852,8 @@ function getCliVersion(metaUrl = import.meta.url) {
|
|
|
3324
3852
|
if (cachedVersion) {
|
|
3325
3853
|
return cachedVersion;
|
|
3326
3854
|
}
|
|
3327
|
-
const packageJsonPath =
|
|
3328
|
-
|
|
3855
|
+
const packageJsonPath = join16(
|
|
3856
|
+
dirname4(fileURLToPath(metaUrl)),
|
|
3329
3857
|
"..",
|
|
3330
3858
|
"package.json"
|
|
3331
3859
|
);
|
|
@@ -3363,6 +3891,11 @@ function createCli() {
|
|
|
3363
3891
|
program.command("daemon").description("Run continuous sync (every 5 minutes by default)").option("--interval <ms>", "Sync interval in milliseconds", parseInt).action(async (opts) => {
|
|
3364
3892
|
await runDaemon(opts);
|
|
3365
3893
|
});
|
|
3894
|
+
program.command("service [action]").description(
|
|
3895
|
+
"Manage systemd user service (setup|start|stop|restart|status|uninstall)"
|
|
3896
|
+
).action(async (action) => {
|
|
3897
|
+
await runInstallService({ action });
|
|
3898
|
+
});
|
|
3366
3899
|
program.command("status").description("Show configuration and detected tools").action(async () => {
|
|
3367
3900
|
await runStatus();
|
|
3368
3901
|
});
|
|
@@ -3380,7 +3913,7 @@ function createCli() {
|
|
|
3380
3913
|
}
|
|
3381
3914
|
|
|
3382
3915
|
// src/infrastructure/runtime/main-module.ts
|
|
3383
|
-
import { existsSync as
|
|
3916
|
+
import { existsSync as existsSync18, realpathSync } from "fs";
|
|
3384
3917
|
import { resolve } from "path";
|
|
3385
3918
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3386
3919
|
function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
@@ -3391,7 +3924,7 @@ function isMainModule(argvEntry = process.argv[1], metaUrl = import.meta.url) {
|
|
|
3391
3924
|
try {
|
|
3392
3925
|
return realpathSync(argvEntry) === realpathSync(currentModulePath);
|
|
3393
3926
|
} catch {
|
|
3394
|
-
if (!
|
|
3927
|
+
if (!existsSync18(argvEntry)) {
|
|
3395
3928
|
return false;
|
|
3396
3929
|
}
|
|
3397
3930
|
return resolve(argvEntry) === resolve(currentModulePath);
|