@poco-ai/tokenarena 0.1.4 → 0.1.5-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 +424 -197
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/parsers/claude-code.ts
|
|
4
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4
5
|
import { homedir } from "os";
|
|
5
6
|
import { join as join2, sep as sep2 } from "path";
|
|
6
7
|
|
|
@@ -220,15 +221,23 @@ var TOOLS = [];
|
|
|
220
221
|
var parsers = /* @__PURE__ */ new Map();
|
|
221
222
|
function registerParser(parser) {
|
|
222
223
|
parsers.set(parser.tool.id, parser);
|
|
223
|
-
if (!TOOLS.find((
|
|
224
|
+
if (!TOOLS.find((tool) => tool.id === parser.tool.id)) {
|
|
224
225
|
TOOLS.push(parser.tool);
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
function getAllParsers() {
|
|
228
229
|
return Array.from(parsers.values());
|
|
229
230
|
}
|
|
231
|
+
function isToolInstalled(toolId) {
|
|
232
|
+
const parser = parsers.get(toolId);
|
|
233
|
+
if (parser?.isInstalled) {
|
|
234
|
+
return parser.isInstalled();
|
|
235
|
+
}
|
|
236
|
+
const tool = TOOLS.find((candidate) => candidate.id === toolId);
|
|
237
|
+
return tool ? existsSync2(tool.dataDir) : false;
|
|
238
|
+
}
|
|
230
239
|
function detectInstalledTools() {
|
|
231
|
-
return TOOLS.filter((
|
|
240
|
+
return TOOLS.filter((tool) => isToolInstalled(tool.id));
|
|
232
241
|
}
|
|
233
242
|
function getAllTools() {
|
|
234
243
|
return TOOLS;
|
|
@@ -240,14 +249,17 @@ var TOOL = {
|
|
|
240
249
|
name: "Claude Code",
|
|
241
250
|
dataDir: join2(homedir(), ".claude", "projects")
|
|
242
251
|
};
|
|
243
|
-
var
|
|
252
|
+
var TRANSCRIPT_DIRS = [
|
|
253
|
+
join2(homedir(), ".claude", "transcripts"),
|
|
254
|
+
join2(homedir(), ".claude", "sessions")
|
|
255
|
+
];
|
|
244
256
|
function extractProject(filePath) {
|
|
245
257
|
const projectsPrefix = TOOL.dataDir + sep2;
|
|
246
258
|
if (!filePath.startsWith(projectsPrefix)) return "unknown";
|
|
247
259
|
const relative = filePath.slice(projectsPrefix.length);
|
|
248
|
-
const
|
|
249
|
-
if (!
|
|
250
|
-
const parts =
|
|
260
|
+
const firstSegment = relative.split(sep2)[0];
|
|
261
|
+
if (!firstSegment) return "unknown";
|
|
262
|
+
const parts = firstSegment.split("-").filter(Boolean);
|
|
251
263
|
return parts.length > 0 ? parts[parts.length - 1] : "unknown";
|
|
252
264
|
}
|
|
253
265
|
var ClaudeCodeParser = class {
|
|
@@ -270,23 +282,24 @@ var ClaudeCodeParser = class {
|
|
|
270
282
|
const obj = JSON.parse(line);
|
|
271
283
|
const timestamp = obj.timestamp;
|
|
272
284
|
if (!timestamp) continue;
|
|
273
|
-
const
|
|
274
|
-
if (Number.isNaN(
|
|
285
|
+
const parsedTimestamp = new Date(timestamp);
|
|
286
|
+
if (Number.isNaN(parsedTimestamp.getTime())) continue;
|
|
275
287
|
if (obj.type === "user" || obj.type === "assistant") {
|
|
276
288
|
sessionEvents.push({
|
|
277
289
|
sessionId,
|
|
278
290
|
source: "claude-code",
|
|
279
291
|
project,
|
|
280
|
-
timestamp:
|
|
292
|
+
timestamp: parsedTimestamp,
|
|
281
293
|
role: obj.type === "user" ? "user" : "assistant"
|
|
282
294
|
});
|
|
283
295
|
}
|
|
284
296
|
if (obj.type !== "assistant") continue;
|
|
285
|
-
const
|
|
286
|
-
if (!
|
|
287
|
-
const usage =
|
|
288
|
-
if (usage.input_tokens == null && usage.output_tokens == null)
|
|
297
|
+
const message = obj.message;
|
|
298
|
+
if (!message?.usage) continue;
|
|
299
|
+
const usage = message.usage;
|
|
300
|
+
if (usage.input_tokens == null && usage.output_tokens == null) {
|
|
289
301
|
continue;
|
|
302
|
+
}
|
|
290
303
|
const uuid = obj.uuid;
|
|
291
304
|
if (uuid) {
|
|
292
305
|
if (seenUuids.has(uuid)) continue;
|
|
@@ -295,9 +308,9 @@ var ClaudeCodeParser = class {
|
|
|
295
308
|
entries.push({
|
|
296
309
|
sessionId,
|
|
297
310
|
source: "claude-code",
|
|
298
|
-
model:
|
|
311
|
+
model: message.model || "unknown",
|
|
299
312
|
project,
|
|
300
|
-
timestamp:
|
|
313
|
+
timestamp: parsedTimestamp,
|
|
301
314
|
inputTokens: usage.input_tokens || 0,
|
|
302
315
|
outputTokens: usage.output_tokens || 0,
|
|
303
316
|
reasoningTokens: 0,
|
|
@@ -307,30 +320,32 @@ var ClaudeCodeParser = class {
|
|
|
307
320
|
}
|
|
308
321
|
}
|
|
309
322
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
323
|
+
for (const transcriptsDir of TRANSCRIPT_DIRS) {
|
|
324
|
+
const transcriptFiles = findJsonlFiles(transcriptsDir);
|
|
325
|
+
for (const filePath of transcriptFiles) {
|
|
326
|
+
const sessionId = extractSessionId(filePath);
|
|
327
|
+
if (seenSessionIds.has(sessionId)) continue;
|
|
328
|
+
const content = readFileSafe(filePath);
|
|
329
|
+
if (!content) continue;
|
|
330
|
+
for (const line of content.split("\n")) {
|
|
331
|
+
if (!line.trim()) continue;
|
|
332
|
+
try {
|
|
333
|
+
const obj = JSON.parse(line);
|
|
334
|
+
const timestamp = obj.timestamp;
|
|
335
|
+
if (!timestamp) continue;
|
|
336
|
+
const parsedTimestamp = new Date(timestamp);
|
|
337
|
+
if (Number.isNaN(parsedTimestamp.getTime())) continue;
|
|
338
|
+
if (obj.type === "user" || obj.type === "assistant") {
|
|
339
|
+
sessionEvents.push({
|
|
340
|
+
sessionId,
|
|
341
|
+
source: "claude-code",
|
|
342
|
+
project: "unknown",
|
|
343
|
+
timestamp: parsedTimestamp,
|
|
344
|
+
role: obj.type === "user" ? "user" : "assistant"
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
332
348
|
}
|
|
333
|
-
} catch {
|
|
334
349
|
}
|
|
335
350
|
}
|
|
336
351
|
}
|
|
@@ -339,6 +354,9 @@ var ClaudeCodeParser = class {
|
|
|
339
354
|
sessions: extractSessions(sessionEvents, entries)
|
|
340
355
|
};
|
|
341
356
|
}
|
|
357
|
+
isInstalled() {
|
|
358
|
+
return existsSync3(TOOL.dataDir) || TRANSCRIPT_DIRS.some((dir) => existsSync3(dir));
|
|
359
|
+
}
|
|
342
360
|
};
|
|
343
361
|
registerParser(new ClaudeCodeParser());
|
|
344
362
|
|
|
@@ -350,6 +368,27 @@ var TOOL2 = {
|
|
|
350
368
|
name: "Codex CLI",
|
|
351
369
|
dataDir: join3(homedir2(), ".codex", "sessions")
|
|
352
370
|
};
|
|
371
|
+
function getPathLeaf(value) {
|
|
372
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
373
|
+
const leaf = normalized.split("/").filter(Boolean).pop();
|
|
374
|
+
return leaf || "unknown";
|
|
375
|
+
}
|
|
376
|
+
function resolveCodexProject(payload) {
|
|
377
|
+
if (!payload) {
|
|
378
|
+
return "unknown";
|
|
379
|
+
}
|
|
380
|
+
const repositoryUrl = payload.git?.repository_url;
|
|
381
|
+
if (repositoryUrl) {
|
|
382
|
+
const match = repositoryUrl.match(/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
383
|
+
if (match) {
|
|
384
|
+
return match[1];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (payload.cwd) {
|
|
388
|
+
return getPathLeaf(payload.cwd);
|
|
389
|
+
}
|
|
390
|
+
return "unknown";
|
|
391
|
+
}
|
|
353
392
|
var CodexParser = class {
|
|
354
393
|
tool = TOOL2;
|
|
355
394
|
async parse() {
|
|
@@ -368,17 +407,8 @@ var CodexParser = class {
|
|
|
368
407
|
if (!line.trim()) continue;
|
|
369
408
|
try {
|
|
370
409
|
const obj = JSON.parse(line);
|
|
371
|
-
if (obj.type === "session_meta"
|
|
372
|
-
|
|
373
|
-
if (meta.cwd) {
|
|
374
|
-
sessionProject = meta.cwd.split("/").pop() || "unknown";
|
|
375
|
-
}
|
|
376
|
-
if (meta.git?.repository_url) {
|
|
377
|
-
const match = meta.git.repository_url.match(
|
|
378
|
-
/([^/]+\/[^/]+?)(?:\.git)?$/
|
|
379
|
-
);
|
|
380
|
-
if (match) sessionProject = match[1];
|
|
381
|
-
}
|
|
410
|
+
if (obj.type === "session_meta") {
|
|
411
|
+
sessionProject = resolveCodexProject(obj.payload);
|
|
382
412
|
break;
|
|
383
413
|
}
|
|
384
414
|
} catch {
|
|
@@ -392,13 +422,13 @@ var CodexParser = class {
|
|
|
392
422
|
try {
|
|
393
423
|
const obj = JSON.parse(line);
|
|
394
424
|
if (obj.type === "turn_context" && obj.timestamp) {
|
|
395
|
-
const
|
|
396
|
-
if (!Number.isNaN(
|
|
425
|
+
const eventTimestamp = new Date(obj.timestamp);
|
|
426
|
+
if (!Number.isNaN(eventTimestamp.getTime())) {
|
|
397
427
|
sessionEvents.push({
|
|
398
428
|
sessionId: filePath,
|
|
399
429
|
source: "codex",
|
|
400
430
|
project: sessionProject,
|
|
401
|
-
timestamp:
|
|
431
|
+
timestamp: eventTimestamp,
|
|
402
432
|
role: "user"
|
|
403
433
|
});
|
|
404
434
|
}
|
|
@@ -409,8 +439,7 @@ var CodexParser = class {
|
|
|
409
439
|
}
|
|
410
440
|
if (obj.type !== "event_msg") continue;
|
|
411
441
|
const payload = obj.payload;
|
|
412
|
-
if (!payload) continue;
|
|
413
|
-
if (payload.type !== "token_count") continue;
|
|
442
|
+
if (!payload || payload.type !== "token_count") continue;
|
|
414
443
|
const info = payload.info;
|
|
415
444
|
if (!info) continue;
|
|
416
445
|
const timestamp = obj.timestamp ? new Date(obj.timestamp) : null;
|
|
@@ -467,7 +496,7 @@ var CodexParser = class {
|
|
|
467
496
|
registerParser(new CodexParser());
|
|
468
497
|
|
|
469
498
|
// src/parsers/gemini-cli.ts
|
|
470
|
-
import { existsSync as
|
|
499
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
471
500
|
import { homedir as homedir3 } from "os";
|
|
472
501
|
import { join as join4 } from "path";
|
|
473
502
|
var TOOL3 = {
|
|
@@ -477,12 +506,12 @@ var TOOL3 = {
|
|
|
477
506
|
};
|
|
478
507
|
function findSessionFiles(baseDir) {
|
|
479
508
|
const results = [];
|
|
480
|
-
if (!
|
|
509
|
+
if (!existsSync4(baseDir)) return results;
|
|
481
510
|
try {
|
|
482
511
|
for (const entry of readdirSync2(baseDir, { withFileTypes: true })) {
|
|
483
512
|
if (!entry.isDirectory()) continue;
|
|
484
513
|
const chatsDir = join4(baseDir, entry.name, "chats");
|
|
485
|
-
if (!
|
|
514
|
+
if (!existsSync4(chatsDir)) continue;
|
|
486
515
|
try {
|
|
487
516
|
for (const f of readdirSync2(chatsDir)) {
|
|
488
517
|
if (f.startsWith("session-") && f.endsWith(".json")) {
|
|
@@ -571,39 +600,51 @@ var GeminiCliParser = class {
|
|
|
571
600
|
registerParser(new GeminiCliParser());
|
|
572
601
|
|
|
573
602
|
// src/parsers/copilot-cli.ts
|
|
574
|
-
import { existsSync as
|
|
603
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
575
604
|
import { homedir as homedir4 } from "os";
|
|
576
|
-
import { basename as basename2, join as join5 } from "path";
|
|
605
|
+
import { basename as basename2, dirname, join as join5 } from "path";
|
|
606
|
+
var ROOT_DIR = join5(homedir4(), ".copilot");
|
|
577
607
|
var TOOL4 = {
|
|
578
608
|
id: "copilot-cli",
|
|
579
609
|
name: "GitHub Copilot CLI",
|
|
580
|
-
dataDir:
|
|
610
|
+
dataDir: ROOT_DIR
|
|
581
611
|
};
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
|
|
612
|
+
function collectEventFiles(dir, results, visited) {
|
|
613
|
+
if (!existsSync5(dir) || visited.has(dir)) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
visited.add(dir);
|
|
585
617
|
try {
|
|
586
|
-
for (const entry of readdirSync3(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
618
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
619
|
+
const fullPath = join5(dir, entry.name);
|
|
620
|
+
if (entry.isDirectory()) {
|
|
621
|
+
collectEventFiles(fullPath, results, visited);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (entry.isFile() && entry.name === "events.jsonl") {
|
|
625
|
+
results.push({
|
|
626
|
+
filePath: fullPath,
|
|
627
|
+
sessionId: basename2(dirname(fullPath))
|
|
628
|
+
});
|
|
591
629
|
}
|
|
592
630
|
}
|
|
593
631
|
} catch {
|
|
594
|
-
return results;
|
|
595
632
|
}
|
|
633
|
+
}
|
|
634
|
+
function findEventFiles(baseDir) {
|
|
635
|
+
const results = [];
|
|
636
|
+
collectEventFiles(baseDir, results, /* @__PURE__ */ new Set());
|
|
596
637
|
return results;
|
|
597
638
|
}
|
|
598
639
|
function getProjectFromContext(context) {
|
|
599
640
|
const projectPath = context?.gitRoot || context?.cwd;
|
|
600
641
|
if (!projectPath) return "unknown";
|
|
601
|
-
return basename2(projectPath) || "unknown";
|
|
642
|
+
return basename2(projectPath.replace(/[\\/]+$/, "")) || "unknown";
|
|
602
643
|
}
|
|
603
644
|
var CopilotCliParser = class {
|
|
604
645
|
tool = TOOL4;
|
|
605
646
|
async parse() {
|
|
606
|
-
const eventFiles = findEventFiles(
|
|
647
|
+
const eventFiles = findEventFiles(ROOT_DIR);
|
|
607
648
|
if (eventFiles.length === 0) {
|
|
608
649
|
return { buckets: [], sessions: [] };
|
|
609
650
|
}
|
|
@@ -644,8 +685,9 @@ var CopilotCliParser = class {
|
|
|
644
685
|
role: "assistant"
|
|
645
686
|
});
|
|
646
687
|
}
|
|
647
|
-
if (obj.type !== "session.shutdown" || !hasTimestamp || !timestamp)
|
|
688
|
+
if (obj.type !== "session.shutdown" || !hasTimestamp || !timestamp) {
|
|
648
689
|
continue;
|
|
690
|
+
}
|
|
649
691
|
const modelMetrics = obj.data?.modelMetrics || {};
|
|
650
692
|
for (const [model, metrics] of Object.entries(modelMetrics)) {
|
|
651
693
|
const usage = metrics?.usage;
|
|
@@ -677,27 +719,127 @@ var CopilotCliParser = class {
|
|
|
677
719
|
sessions: extractSessions(sessionEvents, entries)
|
|
678
720
|
};
|
|
679
721
|
}
|
|
722
|
+
isInstalled() {
|
|
723
|
+
return existsSync5(ROOT_DIR);
|
|
724
|
+
}
|
|
680
725
|
};
|
|
681
726
|
registerParser(new CopilotCliParser());
|
|
682
727
|
|
|
683
728
|
// src/parsers/opencode.ts
|
|
684
729
|
import { execFileSync } from "child_process";
|
|
685
|
-
import { existsSync as
|
|
730
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
686
731
|
import { homedir as homedir5 } from "os";
|
|
687
732
|
import { basename as basename3, join as join6 } from "path";
|
|
733
|
+
var DEFAULT_DATA_DIR = join6(homedir5(), ".local", "share", "opencode");
|
|
688
734
|
var TOOL5 = {
|
|
689
735
|
id: "opencode",
|
|
690
736
|
name: "OpenCode",
|
|
691
|
-
dataDir:
|
|
737
|
+
dataDir: DEFAULT_DATA_DIR
|
|
692
738
|
};
|
|
693
|
-
|
|
694
|
-
|
|
739
|
+
function getOpenCodeDataDirs(env = process.env) {
|
|
740
|
+
const dirs = [
|
|
741
|
+
env.TOKEN_ARENA_OPENCODE_DIR,
|
|
742
|
+
env.XDG_DATA_HOME ? join6(env.XDG_DATA_HOME, "opencode") : void 0,
|
|
743
|
+
DEFAULT_DATA_DIR,
|
|
744
|
+
env.LOCALAPPDATA ? join6(env.LOCALAPPDATA, "opencode") : void 0,
|
|
745
|
+
env.APPDATA ? join6(env.APPDATA, "opencode") : void 0
|
|
746
|
+
].filter((value) => Boolean(value));
|
|
747
|
+
return Array.from(new Set(dirs));
|
|
748
|
+
}
|
|
749
|
+
async function withSuppressedSqliteWarning(fn) {
|
|
750
|
+
const originalEmitWarning = process.emitWarning;
|
|
751
|
+
process.emitWarning = ((warning, ...args) => {
|
|
752
|
+
const warningName = typeof warning === "string" ? typeof args[0] === "string" ? args[0] : "" : warning.name;
|
|
753
|
+
const warningMessage = typeof warning === "string" ? warning : warning.message;
|
|
754
|
+
if (warningName === "ExperimentalWarning" && warningMessage.includes("SQLite")) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
originalEmitWarning.call(process, warning, ...args);
|
|
758
|
+
});
|
|
759
|
+
try {
|
|
760
|
+
return await fn();
|
|
761
|
+
} finally {
|
|
762
|
+
process.emitWarning = originalEmitWarning;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
async function readSqliteRowsWithBuiltin(dbPath, query) {
|
|
766
|
+
try {
|
|
767
|
+
return await withSuppressedSqliteWarning(async () => {
|
|
768
|
+
const sqliteModuleId = "node:sqlite";
|
|
769
|
+
const sqlite = await import(sqliteModuleId);
|
|
770
|
+
const db = new sqlite.DatabaseSync(dbPath);
|
|
771
|
+
try {
|
|
772
|
+
return db.prepare(query).all();
|
|
773
|
+
} finally {
|
|
774
|
+
db.close();
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
} catch (err) {
|
|
778
|
+
const error = err;
|
|
779
|
+
const message = err.message;
|
|
780
|
+
if (error.code === "ERR_UNKNOWN_BUILTIN_MODULE" || message.includes("node:sqlite")) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
throw err;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function readSqliteRowsWithCli(dbPath, query) {
|
|
787
|
+
const candidates = [
|
|
788
|
+
process.env.TOKEN_ARENA_SQLITE3,
|
|
789
|
+
"sqlite3",
|
|
790
|
+
"sqlite3.exe"
|
|
791
|
+
].filter((value) => Boolean(value));
|
|
792
|
+
let lastError = null;
|
|
793
|
+
for (const command of candidates) {
|
|
794
|
+
try {
|
|
795
|
+
const output = execFileSync(command, ["-json", dbPath, query], {
|
|
796
|
+
encoding: "utf-8",
|
|
797
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
798
|
+
timeout: 3e4,
|
|
799
|
+
windowsHide: true
|
|
800
|
+
}).trim();
|
|
801
|
+
if (!output || output === "[]") {
|
|
802
|
+
return [];
|
|
803
|
+
}
|
|
804
|
+
return JSON.parse(output);
|
|
805
|
+
} catch (err) {
|
|
806
|
+
lastError = err;
|
|
807
|
+
const nodeError = err;
|
|
808
|
+
if (nodeError.status === 127 || nodeError.message?.includes("ENOENT")) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
throw err;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
throw new Error(
|
|
815
|
+
`sqlite3 CLI not found. Install sqlite3 or set TOKEN_ARENA_SQLITE3 to its full path. Last error: ${lastError?.message || "not found"}`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
695
818
|
var OpenCodeParser = class {
|
|
696
819
|
tool = TOOL5;
|
|
697
820
|
async parse() {
|
|
698
|
-
|
|
821
|
+
const buckets = [];
|
|
822
|
+
const sessions = [];
|
|
823
|
+
for (const rootDir of getOpenCodeDataDirs()) {
|
|
824
|
+
const result = await this.parseRoot(rootDir);
|
|
825
|
+
if (result.buckets.length > 0) {
|
|
826
|
+
buckets.push(...result.buckets);
|
|
827
|
+
}
|
|
828
|
+
if (result.sessions.length > 0) {
|
|
829
|
+
sessions.push(...result.sessions);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return { buckets, sessions };
|
|
833
|
+
}
|
|
834
|
+
isInstalled() {
|
|
835
|
+
return getOpenCodeDataDirs().some((dir) => existsSync6(dir));
|
|
836
|
+
}
|
|
837
|
+
async parseRoot(rootDir) {
|
|
838
|
+
const dbPath = join6(rootDir, "opencode.db");
|
|
839
|
+
const messagesDir = join6(rootDir, "storage", "message");
|
|
840
|
+
if (existsSync6(dbPath)) {
|
|
699
841
|
try {
|
|
700
|
-
return this.parseFromSqlite();
|
|
842
|
+
return await this.parseFromSqlite(dbPath);
|
|
701
843
|
} catch (err) {
|
|
702
844
|
process.stderr.write(
|
|
703
845
|
`warn: opencode sqlite parse failed (${err.message}), trying legacy json...
|
|
@@ -705,9 +847,9 @@ var OpenCodeParser = class {
|
|
|
705
847
|
);
|
|
706
848
|
}
|
|
707
849
|
}
|
|
708
|
-
return this.parseFromJson();
|
|
850
|
+
return this.parseFromJson(messagesDir);
|
|
709
851
|
}
|
|
710
|
-
parseFromSqlite() {
|
|
852
|
+
async parseFromSqlite(dbPath) {
|
|
711
853
|
const query = `SELECT
|
|
712
854
|
session_id as sessionID,
|
|
713
855
|
json_extract(data, '$.role') as role,
|
|
@@ -716,29 +858,10 @@ var OpenCodeParser = class {
|
|
|
716
858
|
json_extract(data, '$.tokens') as tokens,
|
|
717
859
|
json_extract(data, '$.path.root') as rootPath
|
|
718
860
|
FROM message`;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
maxBuffer: 100 * 1024 * 1024,
|
|
724
|
-
timeout: 3e4
|
|
725
|
-
});
|
|
726
|
-
} catch (err) {
|
|
727
|
-
const nodeErr = err;
|
|
728
|
-
if (nodeErr.status === 127 || nodeErr.message?.includes("ENOENT")) {
|
|
729
|
-
throw new Error(
|
|
730
|
-
"sqlite3 CLI not found. Install sqlite3 to sync opencode data."
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
throw err;
|
|
734
|
-
}
|
|
735
|
-
output = output.trim();
|
|
736
|
-
if (!output || output === "[]") return { buckets: [], sessions: [] };
|
|
737
|
-
let rows;
|
|
738
|
-
try {
|
|
739
|
-
rows = JSON.parse(output);
|
|
740
|
-
} catch {
|
|
741
|
-
throw new Error("Failed to parse sqlite3 JSON output");
|
|
861
|
+
const builtinRows = await readSqliteRowsWithBuiltin(dbPath, query);
|
|
862
|
+
const rows = builtinRows ?? readSqliteRowsWithCli(dbPath, query);
|
|
863
|
+
if (rows.length === 0) {
|
|
864
|
+
return { buckets: [], sessions: [] };
|
|
742
865
|
}
|
|
743
866
|
const entries = [];
|
|
744
867
|
const sessionEvents = [];
|
|
@@ -780,27 +903,29 @@ var OpenCodeParser = class {
|
|
|
780
903
|
sessions: extractSessions(sessionEvents, entries)
|
|
781
904
|
};
|
|
782
905
|
}
|
|
783
|
-
parseFromJson() {
|
|
784
|
-
if (!
|
|
906
|
+
parseFromJson(messagesDir) {
|
|
907
|
+
if (!existsSync6(messagesDir)) return { buckets: [], sessions: [] };
|
|
785
908
|
const entries = [];
|
|
786
909
|
const sessionEvents = [];
|
|
787
910
|
let sessionDirs;
|
|
788
911
|
try {
|
|
789
|
-
sessionDirs = readdirSync4(
|
|
790
|
-
(
|
|
912
|
+
sessionDirs = readdirSync4(messagesDir, { withFileTypes: true }).filter(
|
|
913
|
+
(dirent) => dirent.isDirectory() && dirent.name.startsWith("ses_")
|
|
791
914
|
);
|
|
792
915
|
} catch {
|
|
793
916
|
return { buckets: [], sessions: [] };
|
|
794
917
|
}
|
|
795
918
|
for (const sessionDir of sessionDirs) {
|
|
796
|
-
const sessionPath = join6(
|
|
797
|
-
let
|
|
919
|
+
const sessionPath = join6(messagesDir, sessionDir.name);
|
|
920
|
+
let messageFiles;
|
|
798
921
|
try {
|
|
799
|
-
|
|
922
|
+
messageFiles = readdirSync4(sessionPath).filter(
|
|
923
|
+
(file) => file.endsWith(".json")
|
|
924
|
+
);
|
|
800
925
|
} catch {
|
|
801
926
|
continue;
|
|
802
927
|
}
|
|
803
|
-
for (const file of
|
|
928
|
+
for (const file of messageFiles) {
|
|
804
929
|
const filePath = join6(sessionPath, file);
|
|
805
930
|
let data;
|
|
806
931
|
try {
|
|
@@ -845,7 +970,7 @@ var OpenCodeParser = class {
|
|
|
845
970
|
registerParser(new OpenCodeParser());
|
|
846
971
|
|
|
847
972
|
// src/parsers/openclaw.ts
|
|
848
|
-
import { existsSync as
|
|
973
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync5 } from "fs";
|
|
849
974
|
import { homedir as homedir6 } from "os";
|
|
850
975
|
import { join as join7 } from "path";
|
|
851
976
|
var POSSIBLE_ROOTS = [
|
|
@@ -874,7 +999,7 @@ var OpenClawParser = class {
|
|
|
874
999
|
const sessionEvents = [];
|
|
875
1000
|
for (const root of POSSIBLE_ROOTS) {
|
|
876
1001
|
const agentsDir = join7(root, "agents");
|
|
877
|
-
if (!
|
|
1002
|
+
if (!existsSync7(agentsDir)) continue;
|
|
878
1003
|
let agentDirs;
|
|
879
1004
|
try {
|
|
880
1005
|
agentDirs = readdirSync5(agentsDir, { withFileTypes: true }).filter(
|
|
@@ -886,7 +1011,7 @@ var OpenClawParser = class {
|
|
|
886
1011
|
for (const agentDir of agentDirs) {
|
|
887
1012
|
const project = agentDir.name;
|
|
888
1013
|
const sessionsDir = join7(agentsDir, agentDir.name, "sessions");
|
|
889
|
-
if (!
|
|
1014
|
+
if (!existsSync7(sessionsDir)) continue;
|
|
890
1015
|
let files;
|
|
891
1016
|
try {
|
|
892
1017
|
files = readdirSync5(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -968,7 +1093,7 @@ var OpenClawParser = class {
|
|
|
968
1093
|
}
|
|
969
1094
|
/** Check if any of the possible roots exist */
|
|
970
1095
|
isInstalled() {
|
|
971
|
-
return POSSIBLE_ROOTS.some((root) =>
|
|
1096
|
+
return POSSIBLE_ROOTS.some((root) => existsSync7(join7(root, "agents")));
|
|
972
1097
|
}
|
|
973
1098
|
};
|
|
974
1099
|
registerParser(new OpenClawParser());
|
|
@@ -979,7 +1104,7 @@ import { Command, Option } from "commander";
|
|
|
979
1104
|
// src/infrastructure/config/manager.ts
|
|
980
1105
|
import { randomUUID } from "crypto";
|
|
981
1106
|
import {
|
|
982
|
-
existsSync as
|
|
1107
|
+
existsSync as existsSync8,
|
|
983
1108
|
mkdirSync,
|
|
984
1109
|
readFileSync as readFileSync6,
|
|
985
1110
|
unlinkSync,
|
|
@@ -1004,7 +1129,7 @@ function getRuntimeDir() {
|
|
|
1004
1129
|
var CONFIG_DIR = join9(getConfigHome(), "tokenarena");
|
|
1005
1130
|
var isDev = process.env.TOKEN_ARENA_DEV === "1";
|
|
1006
1131
|
var CONFIG_FILE = join9(CONFIG_DIR, isDev ? "config.dev.json" : "config.json");
|
|
1007
|
-
var DEFAULT_API_URL = "
|
|
1132
|
+
var DEFAULT_API_URL = "https://token.poco-ai.com";
|
|
1008
1133
|
function getConfigPath() {
|
|
1009
1134
|
return CONFIG_FILE;
|
|
1010
1135
|
}
|
|
@@ -1012,7 +1137,7 @@ function getConfigDir() {
|
|
|
1012
1137
|
return CONFIG_DIR;
|
|
1013
1138
|
}
|
|
1014
1139
|
function loadConfig() {
|
|
1015
|
-
if (!
|
|
1140
|
+
if (!existsSync8(CONFIG_FILE)) return null;
|
|
1016
1141
|
try {
|
|
1017
1142
|
const raw = readFileSync6(CONFIG_FILE, "utf-8");
|
|
1018
1143
|
const config = JSON.parse(raw);
|
|
@@ -1030,7 +1155,7 @@ function saveConfig(config) {
|
|
|
1030
1155
|
`, "utf-8");
|
|
1031
1156
|
}
|
|
1032
1157
|
function deleteConfig() {
|
|
1033
|
-
if (
|
|
1158
|
+
if (existsSync8(CONFIG_FILE)) {
|
|
1034
1159
|
unlinkSync(CONFIG_FILE);
|
|
1035
1160
|
}
|
|
1036
1161
|
}
|
|
@@ -1041,7 +1166,7 @@ function getOrCreateDeviceId(config) {
|
|
|
1041
1166
|
return next;
|
|
1042
1167
|
}
|
|
1043
1168
|
function validateApiKey(key) {
|
|
1044
|
-
return key.startsWith("
|
|
1169
|
+
return key.startsWith("ta_");
|
|
1045
1170
|
}
|
|
1046
1171
|
function getDefaultApiUrl() {
|
|
1047
1172
|
return process.env.TOKEN_ARENA_API_URL || DEFAULT_API_URL;
|
|
@@ -1107,7 +1232,8 @@ function handleConfig(args) {
|
|
|
1107
1232
|
if (!config || !(key in config)) {
|
|
1108
1233
|
process.exit(0);
|
|
1109
1234
|
}
|
|
1110
|
-
|
|
1235
|
+
const record = config;
|
|
1236
|
+
console.log(record[key] ?? "");
|
|
1111
1237
|
break;
|
|
1112
1238
|
}
|
|
1113
1239
|
case "set": {
|
|
@@ -1124,7 +1250,7 @@ function handleConfig(args) {
|
|
|
1124
1250
|
}
|
|
1125
1251
|
const config = loadConfig() || {
|
|
1126
1252
|
apiKey: "",
|
|
1127
|
-
apiUrl: "
|
|
1253
|
+
apiUrl: "https://token.poco-ai.com"
|
|
1128
1254
|
};
|
|
1129
1255
|
if (key === "syncInterval") {
|
|
1130
1256
|
value = parseInt(value, 10);
|
|
@@ -1133,7 +1259,8 @@ function handleConfig(args) {
|
|
|
1133
1259
|
process.exit(1);
|
|
1134
1260
|
}
|
|
1135
1261
|
}
|
|
1136
|
-
|
|
1262
|
+
const record = config;
|
|
1263
|
+
record[key] = value;
|
|
1137
1264
|
saveConfig(config);
|
|
1138
1265
|
logger.info(`Set ${key} = ${value}`);
|
|
1139
1266
|
break;
|
|
@@ -1393,7 +1520,7 @@ var ApiClient = class {
|
|
|
1393
1520
|
// src/infrastructure/runtime/lock.ts
|
|
1394
1521
|
import {
|
|
1395
1522
|
closeSync,
|
|
1396
|
-
existsSync as
|
|
1523
|
+
existsSync as existsSync9,
|
|
1397
1524
|
openSync,
|
|
1398
1525
|
readFileSync as readFileSync7,
|
|
1399
1526
|
rmSync,
|
|
@@ -1432,7 +1559,7 @@ function isProcessAlive(pid) {
|
|
|
1432
1559
|
}
|
|
1433
1560
|
}
|
|
1434
1561
|
function readLockMetadata(lockPath) {
|
|
1435
|
-
if (!
|
|
1562
|
+
if (!existsSync9(lockPath)) {
|
|
1436
1563
|
return null;
|
|
1437
1564
|
}
|
|
1438
1565
|
try {
|
|
@@ -1506,13 +1633,13 @@ function describeExistingSyncLock() {
|
|
|
1506
1633
|
}
|
|
1507
1634
|
|
|
1508
1635
|
// src/infrastructure/runtime/state.ts
|
|
1509
|
-
import { existsSync as
|
|
1636
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
1510
1637
|
function getDefaultState() {
|
|
1511
1638
|
return { status: "idle" };
|
|
1512
1639
|
}
|
|
1513
1640
|
function loadSyncState() {
|
|
1514
1641
|
const path = getSyncStatePath();
|
|
1515
|
-
if (!
|
|
1642
|
+
if (!existsSync10(path)) {
|
|
1516
1643
|
return getDefaultState();
|
|
1517
1644
|
}
|
|
1518
1645
|
try {
|
|
@@ -1738,7 +1865,7 @@ async function runSync(config, opts = {}) {
|
|
|
1738
1865
|
logger.info(` ${p.source}: ${parts.join(", ")}`);
|
|
1739
1866
|
}
|
|
1740
1867
|
}
|
|
1741
|
-
const apiUrl = config.apiUrl || "
|
|
1868
|
+
const apiUrl = config.apiUrl || "https://token.poco-ai.com";
|
|
1742
1869
|
const apiClient = new ApiClient(apiUrl, config.apiKey);
|
|
1743
1870
|
let settings;
|
|
1744
1871
|
try {
|
|
@@ -1927,11 +2054,15 @@ async function runDaemon(opts = {}) {
|
|
|
1927
2054
|
}
|
|
1928
2055
|
|
|
1929
2056
|
// src/commands/init.ts
|
|
1930
|
-
import {
|
|
1931
|
-
import { existsSync as
|
|
1932
|
-
import { appendFile, readFile } from "fs/promises";
|
|
2057
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
2058
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2059
|
+
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
1933
2060
|
import { homedir as homedir8, platform } from "os";
|
|
2061
|
+
import { dirname as dirname2, join as join11, posix, win32 } from "path";
|
|
1934
2062
|
import { createInterface } from "readline";
|
|
2063
|
+
function joinForPlatform(currentPlatform, ...parts) {
|
|
2064
|
+
return currentPlatform === "win32" ? win32.join(...parts) : posix.join(...parts);
|
|
2065
|
+
}
|
|
1935
2066
|
function prompt(question) {
|
|
1936
2067
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1937
2068
|
return new Promise((resolve2) => {
|
|
@@ -1941,15 +2072,142 @@ function prompt(question) {
|
|
|
1941
2072
|
});
|
|
1942
2073
|
});
|
|
1943
2074
|
}
|
|
2075
|
+
function basenameLikeShell(input) {
|
|
2076
|
+
return input.split(/[\\/]+/).pop()?.replace(/\.exe$/i, "") ?? input;
|
|
2077
|
+
}
|
|
2078
|
+
function getBrowserLaunchCommand(url, currentPlatform = platform()) {
|
|
2079
|
+
switch (currentPlatform) {
|
|
2080
|
+
case "darwin":
|
|
2081
|
+
return {
|
|
2082
|
+
command: "open",
|
|
2083
|
+
args: [url]
|
|
2084
|
+
};
|
|
2085
|
+
case "win32":
|
|
2086
|
+
return {
|
|
2087
|
+
command: "cmd.exe",
|
|
2088
|
+
args: ["/d", "/s", "/c", "start", "", url]
|
|
2089
|
+
};
|
|
2090
|
+
default:
|
|
2091
|
+
return {
|
|
2092
|
+
command: "xdg-open",
|
|
2093
|
+
args: [url]
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1944
2097
|
function openBrowser(url) {
|
|
1945
|
-
const
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2098
|
+
const { command, args } = getBrowserLaunchCommand(url);
|
|
2099
|
+
try {
|
|
2100
|
+
const child = spawn(command, args, {
|
|
2101
|
+
detached: true,
|
|
2102
|
+
stdio: "ignore",
|
|
2103
|
+
windowsHide: true
|
|
2104
|
+
});
|
|
2105
|
+
child.on("error", () => {
|
|
2106
|
+
});
|
|
2107
|
+
child.unref();
|
|
2108
|
+
} catch {
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function resolvePowerShellProfilePath() {
|
|
2112
|
+
const systemRoot = process.env.SYSTEMROOT || "C:\\Windows";
|
|
2113
|
+
const candidates = [
|
|
2114
|
+
"pwsh.exe",
|
|
2115
|
+
join11(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
2116
|
+
];
|
|
2117
|
+
for (const command of candidates) {
|
|
2118
|
+
try {
|
|
2119
|
+
const output = execFileSync2(
|
|
2120
|
+
command,
|
|
2121
|
+
[
|
|
2122
|
+
"-NoLogo",
|
|
2123
|
+
"-NoProfile",
|
|
2124
|
+
"-Command",
|
|
2125
|
+
"$PROFILE.CurrentUserCurrentHost"
|
|
2126
|
+
],
|
|
2127
|
+
{
|
|
2128
|
+
encoding: "utf-8",
|
|
2129
|
+
timeout: 3e3,
|
|
2130
|
+
windowsHide: true
|
|
2131
|
+
}
|
|
2132
|
+
).trim();
|
|
2133
|
+
if (output) {
|
|
2134
|
+
return output;
|
|
2135
|
+
}
|
|
2136
|
+
} catch {
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return null;
|
|
2140
|
+
}
|
|
2141
|
+
function resolveShellAliasSetup(options = {}) {
|
|
2142
|
+
const currentPlatform = options.currentPlatform ?? platform();
|
|
2143
|
+
const env = options.env ?? process.env;
|
|
2144
|
+
const homeDir = options.homeDir ?? homedir8();
|
|
2145
|
+
const pathExists = options.exists ?? existsSync11;
|
|
2146
|
+
const shellFromEnv = env.SHELL ? basenameLikeShell(env.SHELL).toLowerCase() : "";
|
|
2147
|
+
const shellName = shellFromEnv || (currentPlatform === "win32" ? "powershell" : "");
|
|
2148
|
+
const aliasName = "ta";
|
|
2149
|
+
switch (shellName) {
|
|
2150
|
+
case "zsh":
|
|
2151
|
+
return {
|
|
2152
|
+
aliasLine: `alias ${aliasName}="tokenarena"`,
|
|
2153
|
+
aliasPatterns: [`alias ${aliasName}=`, `function ${aliasName}`],
|
|
2154
|
+
configFile: joinForPlatform(currentPlatform, homeDir, ".zshrc"),
|
|
2155
|
+
shellLabel: "zsh",
|
|
2156
|
+
sourceHint: "source ~/.zshrc"
|
|
2157
|
+
};
|
|
2158
|
+
case "bash": {
|
|
2159
|
+
const bashProfile = joinForPlatform(
|
|
2160
|
+
currentPlatform,
|
|
2161
|
+
homeDir,
|
|
2162
|
+
".bash_profile"
|
|
2163
|
+
);
|
|
2164
|
+
const useBashProfile = currentPlatform === "darwin" && pathExists(bashProfile);
|
|
2165
|
+
return {
|
|
2166
|
+
aliasLine: `alias ${aliasName}="tokenarena"`,
|
|
2167
|
+
aliasPatterns: [`alias ${aliasName}=`, `function ${aliasName}`],
|
|
2168
|
+
configFile: useBashProfile ? bashProfile : joinForPlatform(currentPlatform, homeDir, ".bashrc"),
|
|
2169
|
+
shellLabel: "bash",
|
|
2170
|
+
sourceHint: useBashProfile ? "source ~/.bash_profile" : "source ~/.bashrc"
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
case "fish":
|
|
2174
|
+
return {
|
|
2175
|
+
aliasLine: `alias ${aliasName} "tokenarena"`,
|
|
2176
|
+
aliasPatterns: [`alias ${aliasName}`, `function ${aliasName}`],
|
|
2177
|
+
configFile: joinForPlatform(
|
|
2178
|
+
currentPlatform,
|
|
2179
|
+
homeDir,
|
|
2180
|
+
".config",
|
|
2181
|
+
"fish",
|
|
2182
|
+
"config.fish"
|
|
2183
|
+
),
|
|
2184
|
+
shellLabel: "fish",
|
|
2185
|
+
sourceHint: "source ~/.config/fish/config.fish"
|
|
2186
|
+
};
|
|
2187
|
+
case "powershell": {
|
|
2188
|
+
const configFile = options.resolvePowerShellProfilePath?.() || resolvePowerShellProfilePath() || joinForPlatform(
|
|
2189
|
+
currentPlatform,
|
|
2190
|
+
homeDir,
|
|
2191
|
+
"Documents",
|
|
2192
|
+
"PowerShell",
|
|
2193
|
+
"Microsoft.PowerShell_profile.ps1"
|
|
2194
|
+
);
|
|
2195
|
+
return {
|
|
2196
|
+
aliasLine: `Set-Alias -Name ${aliasName} -Value tokenarena`,
|
|
2197
|
+
aliasPatterns: [
|
|
2198
|
+
`set-alias -name ${aliasName}`,
|
|
2199
|
+
`set-alias ${aliasName}`,
|
|
2200
|
+
`new-alias ${aliasName}`,
|
|
2201
|
+
`function ${aliasName}`
|
|
2202
|
+
],
|
|
2203
|
+
configFile,
|
|
2204
|
+
shellLabel: "PowerShell",
|
|
2205
|
+
sourceHint: ". $PROFILE"
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
default:
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
1953
2211
|
}
|
|
1954
2212
|
async function runInit(opts = {}) {
|
|
1955
2213
|
logger.info("\n tokenarena - Token Usage Tracker\n");
|
|
@@ -1969,7 +2227,7 @@ async function runInit(opts = {}) {
|
|
|
1969
2227
|
while (true) {
|
|
1970
2228
|
apiKey = await prompt("Paste your API key: ");
|
|
1971
2229
|
if (validateApiKey(apiKey)) break;
|
|
1972
|
-
logger.info('Invalid key
|
|
2230
|
+
logger.info('Invalid key - must start with "ta_". Try again.');
|
|
1973
2231
|
}
|
|
1974
2232
|
logger.info(`
|
|
1975
2233
|
Verifying key ${apiKey.slice(0, 8)}...`);
|
|
@@ -2001,7 +2259,7 @@ Verifying key ${apiKey.slice(0, 8)}...`);
|
|
|
2001
2259
|
logger.info(`Device registered: ${deviceId.slice(0, 8)}...`);
|
|
2002
2260
|
const tools = getDetectedTools();
|
|
2003
2261
|
if (tools.length > 0) {
|
|
2004
|
-
logger.info(`Detected tools: ${tools.map((
|
|
2262
|
+
logger.info(`Detected tools: ${tools.map((tool) => tool.name).join(", ")}`);
|
|
2005
2263
|
} else {
|
|
2006
2264
|
logger.info("No AI coding tools detected. Install one and re-run init.");
|
|
2007
2265
|
}
|
|
@@ -2012,85 +2270,55 @@ Setup complete! View your dashboard at: ${apiUrl}/usage`);
|
|
|
2012
2270
|
await setupShellAlias();
|
|
2013
2271
|
}
|
|
2014
2272
|
async function setupShellAlias() {
|
|
2015
|
-
const
|
|
2016
|
-
if (!
|
|
2273
|
+
const setup = resolveShellAliasSetup();
|
|
2274
|
+
if (!setup) {
|
|
2017
2275
|
return;
|
|
2018
2276
|
}
|
|
2019
|
-
const shellName = shell.split("/").pop() ?? "";
|
|
2020
|
-
const aliasName = "ta";
|
|
2021
|
-
let configFile;
|
|
2022
|
-
let aliasLine;
|
|
2023
|
-
let sourceHint;
|
|
2024
|
-
switch (shellName) {
|
|
2025
|
-
case "zsh":
|
|
2026
|
-
configFile = `${homedir8()}/.zshrc`;
|
|
2027
|
-
aliasLine = `alias ${aliasName}="tokenarena"`;
|
|
2028
|
-
sourceHint = "source ~/.zshrc";
|
|
2029
|
-
break;
|
|
2030
|
-
case "bash":
|
|
2031
|
-
if (platform() === "darwin" && existsSync10(`${homedir8()}/.bash_profile`)) {
|
|
2032
|
-
configFile = `${homedir8()}/.bash_profile`;
|
|
2033
|
-
} else {
|
|
2034
|
-
configFile = `${homedir8()}/.bashrc`;
|
|
2035
|
-
}
|
|
2036
|
-
aliasLine = `alias ${aliasName}="tokenarena"`;
|
|
2037
|
-
sourceHint = `source ${configFile}`;
|
|
2038
|
-
break;
|
|
2039
|
-
case "fish":
|
|
2040
|
-
configFile = `${homedir8()}/.config/fish/config.fish`;
|
|
2041
|
-
aliasLine = `alias ${aliasName} "tokenarena"`;
|
|
2042
|
-
sourceHint = "source ~/.config/fish/config.fish";
|
|
2043
|
-
break;
|
|
2044
|
-
default:
|
|
2045
|
-
return;
|
|
2046
|
-
}
|
|
2047
2277
|
const answer = await prompt(
|
|
2048
2278
|
`
|
|
2049
|
-
Set up
|
|
2279
|
+
Set up ${setup.shellLabel} alias 'ta' for 'tokenarena'? (Y/n) `
|
|
2050
2280
|
);
|
|
2051
2281
|
if (answer.toLowerCase() === "n") {
|
|
2052
2282
|
return;
|
|
2053
2283
|
}
|
|
2054
2284
|
try {
|
|
2285
|
+
await mkdir(dirname2(setup.configFile), { recursive: true });
|
|
2055
2286
|
let existingContent = "";
|
|
2056
|
-
if (
|
|
2057
|
-
existingContent = await readFile(configFile, "utf-8");
|
|
2287
|
+
if (existsSync11(setup.configFile)) {
|
|
2288
|
+
existingContent = await readFile(setup.configFile, "utf-8");
|
|
2058
2289
|
}
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
`alias ${aliasName}=`
|
|
2063
|
-
];
|
|
2064
|
-
const aliasExists = aliasPatterns.some(
|
|
2065
|
-
(pattern) => existingContent.includes(pattern)
|
|
2290
|
+
const normalizedContent = existingContent.toLowerCase();
|
|
2291
|
+
const aliasExists = setup.aliasPatterns.some(
|
|
2292
|
+
(pattern) => normalizedContent.includes(pattern.toLowerCase())
|
|
2066
2293
|
);
|
|
2067
2294
|
if (aliasExists) {
|
|
2068
2295
|
logger.info(
|
|
2069
2296
|
`
|
|
2070
|
-
Alias '
|
|
2297
|
+
Alias 'ta' already exists in ${setup.configFile}. Skipping.`
|
|
2071
2298
|
);
|
|
2072
2299
|
return;
|
|
2073
2300
|
}
|
|
2074
2301
|
const aliasWithComment = `
|
|
2075
2302
|
# TokenArena alias
|
|
2076
|
-
${aliasLine}
|
|
2303
|
+
${setup.aliasLine}
|
|
2077
2304
|
`;
|
|
2078
|
-
await appendFile(configFile, aliasWithComment);
|
|
2305
|
+
await appendFile(setup.configFile, aliasWithComment, "utf-8");
|
|
2079
2306
|
logger.info(`
|
|
2080
|
-
Added alias to ${configFile}`);
|
|
2081
|
-
logger.info(
|
|
2082
|
-
|
|
2307
|
+
Added alias to ${setup.configFile}`);
|
|
2308
|
+
logger.info(
|
|
2309
|
+
` Run '${setup.sourceHint}' or restart your terminal to use it.`
|
|
2310
|
+
);
|
|
2311
|
+
logger.info(" Then you can use: ta sync");
|
|
2083
2312
|
} catch (err) {
|
|
2084
2313
|
logger.info(
|
|
2085
2314
|
`
|
|
2086
|
-
Could not write to ${configFile}: ${err.message}`
|
|
2315
|
+
Could not write to ${setup.configFile}: ${err.message}`
|
|
2087
2316
|
);
|
|
2088
|
-
logger.info(` Add this line manually: ${aliasLine}`);
|
|
2317
|
+
logger.info(` Add this line manually: ${setup.aliasLine}`);
|
|
2089
2318
|
}
|
|
2090
2319
|
}
|
|
2091
2320
|
|
|
2092
2321
|
// src/commands/status.ts
|
|
2093
|
-
import { existsSync as existsSync11 } from "fs";
|
|
2094
2322
|
function formatMaybe(value) {
|
|
2095
2323
|
return value || "(never)";
|
|
2096
2324
|
}
|
|
@@ -2099,12 +2327,11 @@ async function runStatus() {
|
|
|
2099
2327
|
logger.info("\ntokenarena status\n");
|
|
2100
2328
|
if (!config?.apiKey) {
|
|
2101
2329
|
logger.info(" Config: not configured");
|
|
2102
|
-
logger.info(
|
|
2103
|
-
`);
|
|
2330
|
+
logger.info(" Run `tokenarena init` to set up.\n");
|
|
2104
2331
|
} else {
|
|
2105
2332
|
logger.info(` Config: ${getConfigPath()}`);
|
|
2106
2333
|
logger.info(` API key: ${config.apiKey.slice(0, 8)}...`);
|
|
2107
|
-
logger.info(` API URL: ${config.apiUrl || "
|
|
2334
|
+
logger.info(` API URL: ${config.apiUrl || "https://token.poco-ai.com"}`);
|
|
2108
2335
|
if (config.syncInterval) {
|
|
2109
2336
|
logger.info(
|
|
2110
2337
|
` Sync interval: ${Math.round(config.syncInterval / 6e4)}m`
|
|
@@ -2123,7 +2350,7 @@ async function runStatus() {
|
|
|
2123
2350
|
}
|
|
2124
2351
|
logger.info(" All supported tools:");
|
|
2125
2352
|
for (const tool of getAllTools()) {
|
|
2126
|
-
const installed =
|
|
2353
|
+
const installed = isToolInstalled(tool.id) ? "installed" : "not found";
|
|
2127
2354
|
logger.info(` ${tool.name}: ${installed}`);
|
|
2128
2355
|
}
|
|
2129
2356
|
const syncState = loadSyncState();
|
|
@@ -2270,7 +2497,7 @@ async function runUninstall() {
|
|
|
2270
2497
|
|
|
2271
2498
|
// src/infrastructure/runtime/cli-version.ts
|
|
2272
2499
|
import { readFileSync as readFileSync10 } from "fs";
|
|
2273
|
-
import { dirname, join as
|
|
2500
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
2274
2501
|
import { fileURLToPath } from "url";
|
|
2275
2502
|
var FALLBACK_VERSION = "0.0.0";
|
|
2276
2503
|
var cachedVersion;
|
|
@@ -2278,8 +2505,8 @@ function getCliVersion(metaUrl = import.meta.url) {
|
|
|
2278
2505
|
if (cachedVersion) {
|
|
2279
2506
|
return cachedVersion;
|
|
2280
2507
|
}
|
|
2281
|
-
const packageJsonPath =
|
|
2282
|
-
|
|
2508
|
+
const packageJsonPath = join12(
|
|
2509
|
+
dirname3(fileURLToPath(metaUrl)),
|
|
2283
2510
|
"..",
|
|
2284
2511
|
"package.json"
|
|
2285
2512
|
);
|