@juliantanx/aiusage 1.5.5 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +28 -1
  2. package/dist/index.js +701 -133
  3. package/dist/web/_app/immutable/chunks/{ToolSelector.0iI1cXVj.js → ToolSelector.CO-QGau6.js} +1 -1
  4. package/dist/web/_app/immutable/chunks/entry.NbJb2eQR.js +3 -0
  5. package/dist/web/_app/immutable/chunks/{stores.YDlXfj0w.js → stores.BYZRdBSl.js} +1 -1
  6. package/dist/web/_app/immutable/chunks/{stores.BSI_cVy6.js → stores.C6g63Ng7.js} +1 -1
  7. package/dist/web/_app/immutable/entry/{app.RcQYJvro.js → app.CM2PdbpC.js} +2 -2
  8. package/dist/web/_app/immutable/entry/start.Cj8FkSB_.js +1 -0
  9. package/dist/web/_app/immutable/nodes/{0.DssNB1JP.js → 0.DDrgmg6l.js} +1 -1
  10. package/dist/web/_app/immutable/nodes/{1.D4HMlRNK.js → 1.DTUl4JbV.js} +1 -1
  11. package/dist/web/_app/immutable/nodes/{10.CnTuhaua.js → 10.CP1F4_ac.js} +1 -1
  12. package/dist/web/_app/immutable/nodes/{11.8-cT1ns9.js → 11.DB5iERgm.js} +1 -1
  13. package/dist/web/_app/immutable/nodes/{12.BCT8Dv1G.js → 12.RfrZJ-XB.js} +1 -1
  14. package/dist/web/_app/immutable/nodes/{14.u96Ucsue.js → 14.Cbg9FBSP.js} +1 -1
  15. package/dist/web/_app/immutable/nodes/{15.CMhy_NGl.js → 15.CQoUgLeT.js} +1 -1
  16. package/dist/web/_app/immutable/nodes/{2.CJH1Fs0K.js → 2.DLTFcQ9L.js} +1 -1
  17. package/dist/web/_app/immutable/nodes/{3.Chtgl9hh.js → 3.jLX2a-Ic.js} +1 -1
  18. package/dist/web/_app/immutable/nodes/{5.DtYG5yJy.js → 5.CVQN-3_l.js} +1 -1
  19. package/dist/web/_app/immutable/nodes/{6.B-ud4zCO.js → 6.C-WRTBwg.js} +1 -1
  20. package/dist/web/_app/immutable/nodes/{7.7Fm6yzqA.js → 7.DFkuOTpF.js} +1 -1
  21. package/dist/web/_app/immutable/nodes/{8.Chp3dlzK.js → 8.BNiBE_e2.js} +1 -1
  22. package/dist/web/_app/version.json +1 -1
  23. package/dist/web/index.html +6 -6
  24. package/package.json +3 -3
  25. package/dist/web/_app/immutable/chunks/entry.BtjCrbiQ.js +0 -3
  26. package/dist/web/_app/immutable/entry/start.DAyKzWev.js +0 -1
package/dist/index.js CHANGED
@@ -13,8 +13,8 @@ import { execSync as execSync5 } from "child_process";
13
13
 
14
14
  // src/commands/serve.ts
15
15
  import http2 from "http";
16
- import { readFileSync as readFileSync9, existsSync as existsSync10, statSync as statSync3, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
17
- import { join as join11, extname as extname2, dirname as dirname4 } from "path";
16
+ import { readFileSync as readFileSync10, existsSync as existsSync11, statSync as statSync4, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "fs";
17
+ import { join as join12, extname as extname2, dirname as dirname5 } from "path";
18
18
  import { fileURLToPath } from "url";
19
19
 
20
20
  // src/api/server.ts
@@ -50,7 +50,8 @@ var TOOLS = [
50
50
  "pi",
51
51
  "craft",
52
52
  "droid",
53
- "zcode"
53
+ "zcode",
54
+ "trae"
54
55
  ];
55
56
  var MODEL_PROVIDER_MAP = [
56
57
  ["claude-", "anthropic"],
@@ -451,13 +452,11 @@ var OpenClawParser = class {
451
452
  const cacheReadTokens = usage.cacheRead ?? 0;
452
453
  const cacheWriteTokens = usage.cacheWrite ?? 0;
453
454
  const thinkingTokens = 0;
455
+ const loggedCost = usage.cost != null && typeof usage.cost === "object" ? Number(usage.cost.total ?? 0) : typeof usage.cost === "number" ? usage.cost : NaN;
454
456
  let cost;
455
457
  let costSource;
456
- if (usage.cost != null && typeof usage.cost === "object") {
457
- cost = usage.cost.total ?? 0;
458
- costSource = "log";
459
- } else if (typeof usage.cost === "number") {
460
- cost = usage.cost;
458
+ if (Number.isFinite(loggedCost) && loggedCost > 0) {
459
+ cost = loggedCost;
461
460
  costSource = "log";
462
461
  } else if (model === "unknown") {
463
462
  cost = 0;
@@ -470,7 +469,7 @@ var OpenClawParser = class {
470
469
  cacheWriteTokens,
471
470
  thinkingTokens
472
471
  }, context.exchangeRate);
473
- costSource = "pricing";
472
+ costSource = cost > 0 ? "pricing" : "unknown";
474
473
  }
475
474
  const provider = parsed.message.provider ?? inferProvider(model);
476
475
  const record = {
@@ -1436,7 +1435,12 @@ function defaultCursorDbPath() {
1436
1435
  return join3(xdgConfigHome, "Cursor", "User", "globalStorage", "state.vscdb");
1437
1436
  }
1438
1437
  function defaultKiloDbPath() {
1439
- return join3(xdgDataDir(homedir2(), "kilo"), "kilo.db");
1438
+ const home = homedir2();
1439
+ if (platform() === "win32") {
1440
+ const appData = process.env.APPDATA ?? join3(home, "AppData", "Roaming");
1441
+ return join3(appData, "kilo", "kilo.db");
1442
+ }
1443
+ return join3(xdgDataDir(home, "kilo"), "kilo.db");
1440
1444
  }
1441
1445
  function defaultGooseDbPath() {
1442
1446
  const home = homedir2();
@@ -1604,24 +1608,47 @@ function probeKiro(ctx) {
1604
1608
  if (override) return override;
1605
1609
  const legacy = ctx.legacySources?.["kiro"];
1606
1610
  if (legacy) return legacy;
1607
- const ideDb = join3(
1608
- ctx.home,
1609
- "Library",
1610
- "Application Support",
1611
- "Kiro",
1612
- "User",
1613
- "globalStorage",
1614
- "kiro.kiroagent",
1615
- "dev_data",
1616
- "devdata.sqlite"
1617
- );
1611
+ return probeKiroDefaultPath(ctx);
1612
+ }
1613
+ function probeKiroOverridePath(ctx) {
1614
+ const override = envOverride("kiro", ctx.env);
1615
+ if (override) return override;
1616
+ const legacy = ctx.legacySources?.["kiro"];
1617
+ if (legacy) return legacy;
1618
+ return null;
1619
+ }
1620
+ function probeKiroDefaultPath(ctx) {
1621
+ const devDataBase = kiroDevDataDir(ctx);
1622
+ const ideDb = join3(devDataBase, "devdata.sqlite");
1623
+ const tokensJsonl = join3(devDataBase, "tokens_generated.jsonl");
1618
1624
  const cliSessions = join3(ctx.env.KIRO_HOME ?? join3(ctx.home, ".kiro"), "sessions", "cli");
1619
- const appSupport = platform() === "darwin" ? join3(ctx.home, "Library", "Application Support", "kiro-cli", "data.sqlite3") : join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "kiro-cli", "data.sqlite3");
1620
- if (existsSync3(ideDb)) return ideDb;
1625
+ const appSupport = platform() === "darwin" ? join3(ctx.home, "Library", "Application Support", "kiro-cli", "data.sqlite3") : platform() === "win32" ? join3(ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming"), "kiro-cli", "data.sqlite3") : join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "kiro-cli", "data.sqlite3");
1626
+ if (existsSync3(ideDb) && statSync(ideDb).size > 0) return ideDb;
1621
1627
  if (existsSync3(appSupport)) return appSupport;
1622
1628
  if (existsSync3(cliSessions)) return cliSessions;
1629
+ if (existsSync3(tokensJsonl)) return devDataBase;
1623
1630
  return null;
1624
1631
  }
1632
+ function kiroDevDataDir(ctx) {
1633
+ if (platform() === "darwin") {
1634
+ return join3(ctx.home, "Library", "Application Support", "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1635
+ }
1636
+ if (platform() === "win32") {
1637
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1638
+ return join3(appData, "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1639
+ }
1640
+ return join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1641
+ }
1642
+ function kiroWorkspaceSessionsDir(ctx) {
1643
+ if (platform() === "darwin") {
1644
+ return join3(ctx.home, "Library", "Application Support", "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1645
+ }
1646
+ if (platform() === "win32") {
1647
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1648
+ return join3(appData, "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1649
+ }
1650
+ return join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1651
+ }
1625
1652
  function probeGrok(ctx) {
1626
1653
  const override = envOverride("grok", ctx.env);
1627
1654
  if (override) return override;
@@ -1725,6 +1752,27 @@ function probeDroid(ctx) {
1725
1752
  const dir = join3(ctx.home, ".droid", "sessions");
1726
1753
  return existsSync3(dir) ? dir : null;
1727
1754
  }
1755
+ function probeTrae(ctx) {
1756
+ const override = envOverride("trae", ctx.env);
1757
+ if (override) return override;
1758
+ const legacy = ctx.legacySources?.["trae"];
1759
+ if (legacy) return legacy;
1760
+ const appNames = platform() === "darwin" ? ["Trae CN", "TRAE SOLO CN", "Trae"] : platform() === "win32" ? ["Trae CN", "TRAE SOLO CN", "Trae"] : ["Trae CN", "TRAE SOLO CN", "Trae"];
1761
+ for (const appName of appNames) {
1762
+ let dbPath;
1763
+ if (platform() === "darwin") {
1764
+ dbPath = join3(ctx.home, "Library", "Application Support", appName, "User", "globalStorage", "state.vscdb");
1765
+ } else if (platform() === "win32") {
1766
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1767
+ dbPath = join3(appData, appName, "User", "globalStorage", "state.vscdb");
1768
+ } else {
1769
+ const config = ctx.env.XDG_CONFIG_HOME ?? join3(ctx.home, ".config");
1770
+ dbPath = join3(config, appName, "User", "globalStorage", "state.vscdb");
1771
+ }
1772
+ if (existsSync3(dbPath)) return dbPath;
1773
+ }
1774
+ return null;
1775
+ }
1728
1776
  function defaultQoderSessionDirs(home) {
1729
1777
  const windowsHomes = [
1730
1778
  process.env.USERPROFILE,
@@ -1772,7 +1820,8 @@ var TOOL_REGISTRY = [
1772
1820
  { tool: "omp", sourceKey: "omp", label: "oh-my-pi", probe: probeOmp },
1773
1821
  { tool: "pi", sourceKey: "pi", label: "pi", probe: probePi },
1774
1822
  { tool: "craft", sourceKey: "craft", label: "Craft", probe: probeCraft },
1775
- { tool: "droid", sourceKey: "droid", label: "Droid", probe: probeDroid }
1823
+ { tool: "droid", sourceKey: "droid", label: "Droid", probe: probeDroid },
1824
+ { tool: "trae", sourceKey: "trae", label: "Trae", probe: probeTrae }
1776
1825
  ];
1777
1826
  function discoverTools(env = process.env) {
1778
1827
  const home = homedir2();
@@ -1889,7 +1938,9 @@ function discoverLogFiles(env = process.env) {
1889
1938
  { tool: "gemini", path: probeGemini(ctx) },
1890
1939
  { tool: "kimi", path: probeKimi(ctx), filter: (p) => basename(p) === "wire.jsonl" },
1891
1940
  { tool: "codebuddy", path: probeCodeBuddy(ctx) },
1892
- { tool: "kiro", path: probeKiro(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
1941
+ { tool: "kiro", path: kiroDevDataDir(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
1942
+ { tool: "kiro", path: probeKiroOverridePath(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
1943
+ { tool: "kiro", path: kiroWorkspaceSessionsDir(ctx), filter: (p) => extname(p) === ".json" && basename(p) !== "sessions.json" },
1893
1944
  { tool: "grok", path: probeGrok(ctx), filter: (p) => extname(p) === ".jsonl" },
1894
1945
  { tool: "antigravity", path: probeAntigravity(ctx) },
1895
1946
  { tool: "omp", path: probeOmp(ctx) },
@@ -1918,6 +1969,49 @@ function discoverLogFiles(env = process.env) {
1918
1969
  const paths = statSync(kelivoPath).isDirectory() ? [...findJsonFiles(kelivoPath).filter((p) => basename(p) === "chats.json"), ...findZipFiles(kelivoPath)] : basename(kelivoPath) === "chats.json" || basename(kelivoPath).endsWith(".zip") ? [kelivoPath] : [];
1919
1970
  if (paths.length > 0) results.push({ tool: "kelivo", paths: unique(paths) });
1920
1971
  }
1972
+ const cursorProjectDirs = cursorProjectRoots(ctx);
1973
+ for (const cursorHome of cursorProjectDirs) {
1974
+ if (!existsSync3(cursorHome)) continue;
1975
+ const transcriptPaths = findCursorTranscriptFiles(cursorHome);
1976
+ if (transcriptPaths.length > 0) {
1977
+ const existing = results.find((r) => r.tool === "cursor");
1978
+ if (existing) {
1979
+ existing.paths = unique([...existing.paths, ...transcriptPaths]);
1980
+ } else {
1981
+ results.push({ tool: "cursor", paths: transcriptPaths });
1982
+ }
1983
+ }
1984
+ }
1985
+ return results;
1986
+ }
1987
+ function cursorProjectRoots(ctx) {
1988
+ const roots = [join3(ctx.home, ".cursor", "projects")];
1989
+ if (platform() === "win32") {
1990
+ const userProfile = ctx.env.USERPROFILE ?? ctx.home;
1991
+ roots.push(join3(userProfile, ".cursor", "projects"));
1992
+ }
1993
+ return unique(roots);
1994
+ }
1995
+ function findCursorTranscriptFiles(dir) {
1996
+ const results = [];
1997
+ try {
1998
+ const entries = readdirSync(dir, { withFileTypes: true });
1999
+ for (const entry of entries) {
2000
+ if (!entry.isDirectory()) continue;
2001
+ const agentDir = join3(dir, entry.name, "agent-transcripts");
2002
+ if (!existsSync3(agentDir)) continue;
2003
+ try {
2004
+ const sessions = readdirSync(agentDir, { withFileTypes: true });
2005
+ for (const session of sessions) {
2006
+ if (!session.isDirectory()) continue;
2007
+ const jsonlPath = join3(agentDir, session.name, `${session.name}.jsonl`);
2008
+ if (existsSync3(jsonlPath)) results.push(jsonlPath);
2009
+ }
2010
+ } catch {
2011
+ }
2012
+ }
2013
+ } catch {
2014
+ }
1921
2015
  return results;
1922
2016
  }
1923
2017
  function findJsonFiles(dir) {
@@ -2709,7 +2803,7 @@ async function fetchLeaderboardStatus(serverUrl) {
2709
2803
 
2710
2804
  // src/db/index.ts
2711
2805
  import Database from "better-sqlite3";
2712
- import { mkdirSync as mkdirSync4 } from "fs";
2806
+ import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
2713
2807
  import { dirname as dirname3 } from "path";
2714
2808
 
2715
2809
  // src/db/schema.ts
@@ -3201,6 +3295,21 @@ function loadPricingRuntime(db, config) {
3201
3295
  for (const [model, entry] of Object.entries(configOverrides)) overrides[model] = entry;
3202
3296
  setRuntimePriceTable(builtin, overrides);
3203
3297
  }
3298
+ function importConfigPriceOverrides(db, overrides) {
3299
+ const imported = [];
3300
+ for (const [modelKey, entry] of Object.entries(overrides)) {
3301
+ if (!modelKey.trim()) continue;
3302
+ const existingUserPrice = db.prepare(`
3303
+ SELECT 1 FROM model_prices
3304
+ WHERE model_key = ? AND origin = 'user' AND status = 'active'
3305
+ LIMIT 1
3306
+ `).get(modelKey);
3307
+ if (existingUserPrice) continue;
3308
+ setUserPrice(db, modelKey, entry);
3309
+ imported.push(modelKey);
3310
+ }
3311
+ return imported;
3312
+ }
3204
3313
  function setUserPrice(db, modelKey, entry) {
3205
3314
  const now = Date.now();
3206
3315
  const baseline = db.prepare(`
@@ -3455,6 +3564,14 @@ function priceTableFromDb(db) {
3455
3564
  for (const row of rows) table[row.model_key] = rowToPrice(row);
3456
3565
  return table;
3457
3566
  }
3567
+ function hasUserPrice(db, model) {
3568
+ const row = db.prepare(`
3569
+ SELECT 1 FROM model_prices
3570
+ WHERE model_key = ? AND origin = 'user' AND status = 'active'
3571
+ LIMIT 1
3572
+ `).get(model);
3573
+ return Boolean(row);
3574
+ }
3458
3575
  function resolvePriceFromRegistry(db, model) {
3459
3576
  const directUserPrice = db.prepare(`
3460
3577
  SELECT input, output, cache_read, cache_write, currency
@@ -3694,11 +3811,31 @@ function initializeDatabase(db) {
3694
3811
  runMigrations(db);
3695
3812
  loadPricingRuntime(db, loadConfig());
3696
3813
  }
3814
+ function removeCorruptedDb(path3) {
3815
+ for (const suffix of ["", "-shm", "-wal"]) {
3816
+ try {
3817
+ unlinkSync2(path3 + suffix);
3818
+ } catch {
3819
+ }
3820
+ }
3821
+ }
3697
3822
  function createDatabase(path3) {
3698
3823
  mkdirSync4(dirname3(path3), { recursive: true, mode: 448 });
3699
- const db = new Database(path3);
3700
- initializeDatabase(db);
3701
- return db;
3824
+ try {
3825
+ const db = new Database(path3);
3826
+ initializeDatabase(db);
3827
+ return db;
3828
+ } catch (err) {
3829
+ const code = err.code;
3830
+ if (code === "SQLITE_CORRUPT" || code === "SQLITE_NOTADB") {
3831
+ console.warn(`Database corrupted, recreating: ${path3}`);
3832
+ removeCorruptedDb(path3);
3833
+ const db = new Database(path3);
3834
+ initializeDatabase(db);
3835
+ return db;
3836
+ }
3837
+ throw err;
3838
+ }
3702
3839
  }
3703
3840
 
3704
3841
  // src/db/tool-calls.ts
@@ -3894,10 +4031,10 @@ function getClientPlatform() {
3894
4031
  }
3895
4032
  function getCliVersion() {
3896
4033
  try {
3897
- const { readFileSync: readFileSync12 } = __require("fs");
3898
- const { join: join16 } = __require("path");
3899
- const pkgPath = join16(__dirname, "../../package.json");
3900
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
4034
+ const { readFileSync: readFileSync13 } = __require("fs");
4035
+ const { join: join17 } = __require("path");
4036
+ const pkgPath = join17(__dirname, "../../package.json");
4037
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3901
4038
  return pkg.version || "0.0.0";
3902
4039
  } catch {
3903
4040
  return "0.0.0";
@@ -4509,12 +4646,12 @@ async function recalcCosts(db, onProgress) {
4509
4646
  const tx = db.transaction((batch) => {
4510
4647
  for (const r of batch) {
4511
4648
  processed++;
4512
- if (r.cost_source === "log") {
4649
+ const rawModel = r.tool === "qoder" ? normalizeQoderModel(r.model) : r.model;
4650
+ const model = rawModel === "unknown" ? r.tool === "qoder" ? "qoder-auto" : r.model : rawModel;
4651
+ if (r.cost_source === "log" && r.cost > 0 && !hasUserPrice(db, model)) {
4513
4652
  skipped++;
4514
4653
  continue;
4515
4654
  }
4516
- const rawModel = r.tool === "qoder" ? normalizeQoderModel(r.model) : r.model;
4517
- const model = rawModel === "unknown" ? r.tool === "qoder" ? "qoder-auto" : r.model : rawModel;
4518
4655
  const provider = model !== r.model ? inferProvider(model) : r.provider;
4519
4656
  const price = resolveCachedPrice(model);
4520
4657
  const cost = price ? calculateCostForPrice(price, {
@@ -6108,8 +6245,8 @@ function createApiServer(db, options) {
6108
6245
 
6109
6246
  // src/commands/parse.ts
6110
6247
  import Database2 from "better-sqlite3";
6111
- import { readFileSync as readFileSync8, statSync as statSync2, existsSync as existsSync8, openSync, readSync, closeSync } from "fs";
6112
- import { join as join7 } from "path";
6248
+ import { readFileSync as readFileSync9, statSync as statSync3, existsSync as existsSync9, openSync, readSync, closeSync } from "fs";
6249
+ import { join as join8, dirname as dirname4 } from "path";
6113
6250
  import { hostname as hostname2 } from "os";
6114
6251
 
6115
6252
  // src/watermark.ts
@@ -6139,7 +6276,8 @@ function defaultFileData() {
6139
6276
  "pi": {},
6140
6277
  "craft": {},
6141
6278
  "droid": {},
6142
- "zcode": {}
6279
+ "zcode": {},
6280
+ "trae": {}
6143
6281
  };
6144
6282
  }
6145
6283
  var WatermarkManager = class {
@@ -6159,7 +6297,7 @@ var WatermarkManager = class {
6159
6297
  if (parsed && typeof parsed === "object" && !("files" in parsed)) {
6160
6298
  return { files: { ...defaultFileData(), ...parsed } };
6161
6299
  }
6162
- return { files: { ...defaultFileData(), ...parsed.files ?? {} }, opencode: parsed.opencode ?? null, hermes: parsed.hermes ?? null, qoder: parsed.qoder ?? null, cursor: parsed.cursor ?? null, goose: parsed.goose ?? null, zed: parsed.zed ?? null, kiro: parsed.kiro ?? null, zcode: parsed.zcode ?? null, zcodeTools: parsed.zcodeTools ?? null };
6300
+ return { files: { ...defaultFileData(), ...parsed.files ?? {} }, opencode: parsed.opencode ?? null, hermes: parsed.hermes ?? null, qoder: parsed.qoder ?? null, cursor: parsed.cursor ?? null, goose: parsed.goose ?? null, zed: parsed.zed ?? null, kiro: parsed.kiro ?? null, zcode: parsed.zcode ?? null, zcodeTools: parsed.zcodeTools ?? null, trae: parsed.trae ?? null };
6163
6301
  } catch {
6164
6302
  return { files: defaultFileData() };
6165
6303
  }
@@ -6240,6 +6378,12 @@ var WatermarkManager = class {
6240
6378
  setZcodeToolCursor(cursor) {
6241
6379
  this.data.zcodeTools = cursor;
6242
6380
  }
6381
+ getTraeLastImported() {
6382
+ return this.data.trae ?? 0;
6383
+ }
6384
+ setTraeLastImported(ts3) {
6385
+ this.data.trae = ts3;
6386
+ }
6243
6387
  };
6244
6388
 
6245
6389
  // src/commands/parse-opencode.ts
@@ -6534,6 +6678,42 @@ function runParseQoder(db, options) {
6534
6678
  }
6535
6679
 
6536
6680
  // src/commands/parse-cursor.ts
6681
+ import { readFileSync as readFileSync8 } from "fs";
6682
+ function runParseCursorTranscript(options) {
6683
+ const charsPerToken = 3;
6684
+ try {
6685
+ const content = readFileSync8(options.jsonlPath, "utf-8");
6686
+ const lines = content.split("\n");
6687
+ let userChars = 0;
6688
+ let assistChars = 0;
6689
+ for (const line of lines) {
6690
+ if (!line.trim()) continue;
6691
+ let data;
6692
+ try {
6693
+ data = JSON.parse(line);
6694
+ } catch {
6695
+ continue;
6696
+ }
6697
+ const role = data?.role ?? "";
6698
+ const blocks = data?.message?.content ?? [];
6699
+ if (!Array.isArray(blocks)) continue;
6700
+ for (const block of blocks) {
6701
+ if (typeof block !== "object" || block === null || block.type !== "text") continue;
6702
+ const text = block.text ?? "";
6703
+ const clean = text.replace(/\[REDACTED\]/g, "").trim();
6704
+ if (!clean) continue;
6705
+ if (role === "user") userChars += clean.length;
6706
+ else if (role === "assistant") assistChars += clean.length;
6707
+ }
6708
+ }
6709
+ return {
6710
+ inputTextChars: Math.floor(userChars / charsPerToken),
6711
+ outputTextChars: Math.floor(assistChars / charsPerToken)
6712
+ };
6713
+ } catch {
6714
+ return { inputTextChars: 0, outputTextChars: 0 };
6715
+ }
6716
+ }
6537
6717
  function runParseCursor(db, options) {
6538
6718
  const { dbPath, device, deviceInstanceId, platform: platform5, now, cursor } = options;
6539
6719
  const records = [];
@@ -7117,6 +7297,139 @@ function runParseZcode(db, options) {
7117
7297
  };
7118
7298
  }
7119
7299
 
7300
+ // src/commands/parse-trae.ts
7301
+ import { existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
7302
+ import { join as join7, basename as basename3 } from "path";
7303
+ import { spawnSync } from "child_process";
7304
+ function deriveDataDir(dbPath) {
7305
+ const normalized = dbPath.replace(/\\/g, "/");
7306
+ const appDir = normalized.replace(/\/User\/globalStorage\/state\.vscdb$/, "");
7307
+ const dataDir = join7(appDir, "ModularData", "ai-agent", "snapshot");
7308
+ return existsSync8(dataDir) ? dataDir : null;
7309
+ }
7310
+ function parseSessionTags(repoPath) {
7311
+ try {
7312
+ const sessionId = basename3(basename3(repoPath) === "v2" ? join7(repoPath, "..") : repoPath);
7313
+ if (!/^[0-9a-f]{24,}$/.test(sessionId)) return null;
7314
+ const tagResult = spawnSync("git", ["tag", "-l"], { cwd: repoPath, encoding: "utf-8", timeout: 5e3 });
7315
+ if (tagResult.status !== 0 || !tagResult.stdout?.trim()) return null;
7316
+ const tags = tagResult.stdout;
7317
+ const lines = tags.trim().split("\n");
7318
+ let chatTurns = 0;
7319
+ let toolCalls = 0;
7320
+ let startTs = 0;
7321
+ for (const tag of lines) {
7322
+ const t = tag.trim();
7323
+ if (!t) continue;
7324
+ let tagTs = 0;
7325
+ try {
7326
+ const logResult = spawnSync("git", ["log", "-1", "--format=%at", t], {
7327
+ cwd: repoPath,
7328
+ encoding: "utf-8",
7329
+ timeout: 3e3
7330
+ });
7331
+ if (logResult.status !== 0 || !logResult.stdout?.trim()) continue;
7332
+ const tsStr = logResult.stdout.trim();
7333
+ tagTs = parseInt(tsStr, 10) * 1e3;
7334
+ } catch {
7335
+ continue;
7336
+ }
7337
+ if (t.startsWith("chain-start-")) {
7338
+ startTs = tagTs;
7339
+ } else if (t.startsWith("before-chat-turn-")) {
7340
+ chatTurns++;
7341
+ } else if (t.startsWith("toolcall-")) {
7342
+ toolCalls++;
7343
+ }
7344
+ }
7345
+ if (startTs === 0) {
7346
+ let earliest = Infinity;
7347
+ for (const tag of lines) {
7348
+ const t = tag.trim();
7349
+ if (!t) continue;
7350
+ const logResult = spawnSync("git", ["log", "-1", "--format=%at", t], {
7351
+ cwd: repoPath,
7352
+ encoding: "utf-8",
7353
+ timeout: 3e3
7354
+ });
7355
+ if (logResult.status !== 0 || !logResult.stdout?.trim()) continue;
7356
+ const ts3 = parseInt(logResult.stdout.trim(), 10) * 1e3;
7357
+ if (ts3 > 0 && ts3 < earliest) earliest = ts3;
7358
+ }
7359
+ if (earliest < Infinity) startTs = earliest;
7360
+ }
7361
+ if (chatTurns === 0 && toolCalls === 0) return null;
7362
+ if (startTs === 0) return null;
7363
+ return { sessionId, startTs, chatTurns, toolCalls };
7364
+ } catch {
7365
+ return null;
7366
+ }
7367
+ }
7368
+ function runParseTrae(options) {
7369
+ const { dbPath, device, deviceInstanceId, platform: platform5, now, lastImportedAt } = options;
7370
+ const records = [];
7371
+ const toolCalls = [];
7372
+ const errors = [];
7373
+ let latestTs = lastImportedAt ?? 0;
7374
+ const dataDir = deriveDataDir(dbPath);
7375
+ if (!dataDir) {
7376
+ return { records, toolCalls, lastImportedAt: latestTs, errors: ["Trae ModularData not found"] };
7377
+ }
7378
+ let entries;
7379
+ try {
7380
+ entries = readdirSync2(dataDir, { withFileTypes: true });
7381
+ } catch (e) {
7382
+ return { records, toolCalls, lastImportedAt: latestTs, errors: [String(e)] };
7383
+ }
7384
+ for (const entry of entries) {
7385
+ if (!entry.isDirectory()) continue;
7386
+ const repoPath = join7(dataDir, entry.name, "v2");
7387
+ if (!existsSync8(join7(repoPath, ".git"))) continue;
7388
+ const session = parseSessionTags(repoPath);
7389
+ if (!session) continue;
7390
+ if (session.startTs < latestTs) continue;
7391
+ if (session.startTs > latestTs) latestTs = session.startTs;
7392
+ const estInputTokens = session.chatTurns * 200;
7393
+ const estOutputTokens = session.chatTurns * 800;
7394
+ const recordId = generateRecordId(deviceInstanceId, `${dbPath}:${session.sessionId}`, session.startTs);
7395
+ const record = {
7396
+ id: recordId,
7397
+ ts: session.startTs,
7398
+ ingestedAt: now,
7399
+ updatedAt: now,
7400
+ lineOffset: 0,
7401
+ tool: "trae",
7402
+ model: "trae-agent",
7403
+ provider: "trae",
7404
+ inputTokens: estInputTokens,
7405
+ outputTokens: estOutputTokens,
7406
+ cacheReadTokens: 0,
7407
+ cacheWriteTokens: 0,
7408
+ thinkingTokens: 0,
7409
+ cost: 0,
7410
+ costSource: "unknown",
7411
+ sessionId: session.sessionId,
7412
+ sourceFile: dbPath,
7413
+ device,
7414
+ deviceInstanceId,
7415
+ platform: platform5
7416
+ };
7417
+ records.push(record);
7418
+ let callIndex = 0;
7419
+ for (let i = 0; i < session.toolCalls; i++) {
7420
+ toolCalls.push({
7421
+ id: generateRecordId(recordId, "tool_call", session.startTs + callIndex),
7422
+ recordId,
7423
+ name: "trae-tool",
7424
+ ts: session.startTs + callIndex,
7425
+ callIndex
7426
+ });
7427
+ callIndex++;
7428
+ }
7429
+ }
7430
+ return { records, toolCalls, lastImportedAt: latestTs, errors };
7431
+ }
7432
+
7120
7433
  // src/commands/parse.ts
7121
7434
  function extractCwdFromJson(data) {
7122
7435
  if (typeof data.cwd === "string" && data.cwd) return data.cwd;
@@ -7160,6 +7473,7 @@ function extractSessionId2(filePath, tool) {
7160
7473
  }
7161
7474
  return "unknown";
7162
7475
  }
7476
+ var CHARS_PER_TOKEN2 = 4;
7163
7477
  function toNumber2(value) {
7164
7478
  const n = Number(value);
7165
7479
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
@@ -7170,7 +7484,7 @@ function parseUiMessagesFile(options) {
7170
7484
  const errors = [];
7171
7485
  let messages;
7172
7486
  try {
7173
- const parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
7487
+ const parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
7174
7488
  messages = Array.isArray(parsed) ? parsed : [];
7175
7489
  } catch (e) {
7176
7490
  return { records, errors: [`${filePath}: ${e instanceof Error ? e.message : e}`] };
@@ -7237,7 +7551,7 @@ function parseTimestamp2(value, fallback) {
7237
7551
  }
7238
7552
  function pathIsFile(filePath) {
7239
7553
  try {
7240
- return statSync2(filePath).isFile();
7554
+ return statSync3(filePath).isFile();
7241
7555
  } catch {
7242
7556
  return false;
7243
7557
  }
@@ -7248,10 +7562,13 @@ function parseKiroSessionFile(options) {
7248
7562
  const errors = [];
7249
7563
  let parsed;
7250
7564
  try {
7251
- parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
7565
+ parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
7252
7566
  } catch (e) {
7253
7567
  return { records, errors: [`${filePath}: ${e instanceof Error ? e.message : e}`] };
7254
7568
  }
7569
+ if (Array.isArray(parsed?.history) && !parsed?.session_state) {
7570
+ return parseKiroWorkspaceSession({ filePath, parsed, device, deviceInstanceId, platform: platform5, now, exchangeRate });
7571
+ }
7255
7572
  const turns = parsed?.session_state?.conversation_metadata?.user_turn_metadatas;
7256
7573
  if (!Array.isArray(turns)) return { records, errors };
7257
7574
  const sessionId = typeof parsed?.session_id === "string" && parsed.session_id ? parsed.session_id : extractSessionId2(filePath, "kiro");
@@ -7293,6 +7610,83 @@ function parseKiroSessionFile(options) {
7293
7610
  }
7294
7611
  return { records, errors };
7295
7612
  }
7613
+ function parseKiroWorkspaceSession(options) {
7614
+ const { filePath, parsed, device, deviceInstanceId, platform: platform5, now, exchangeRate } = options;
7615
+ const records = [];
7616
+ const errors = [];
7617
+ const sessionId = parsed?.sessionId ?? extractSessionId2(filePath, "kiro");
7618
+ let sessionTs = now;
7619
+ try {
7620
+ const dir = dirname4(filePath);
7621
+ const sessionsJson = readFileSync9(join8(dir, "sessions.json"), "utf-8");
7622
+ const sessions = JSON.parse(sessionsJson);
7623
+ const match = Array.isArray(sessions) && sessions.find((s) => s.sessionId === sessionId);
7624
+ if (match?.dateCreated) {
7625
+ const dc = Number(match.dateCreated);
7626
+ if (Number.isFinite(dc) && dc > 0) sessionTs = dc < 1e12 ? dc * 1e3 : dc;
7627
+ }
7628
+ } catch {
7629
+ }
7630
+ const allLogs = [];
7631
+ let logIndex = 0;
7632
+ for (const entry of parsed.history ?? []) {
7633
+ for (const pl of entry?.promptLogs ?? []) {
7634
+ allLogs.push({ log: pl, index: logIndex++ });
7635
+ }
7636
+ }
7637
+ const contextLength = parsed?.config?.models?.[0]?.contextLength ?? 4e4;
7638
+ const maxTokens = parsed?.config?.models?.[0]?.completionOptions?.maxTokens ?? 4e3;
7639
+ const contextPct = Number(parsed?.contextUsagePercentageBySession?.[sessionId] ?? parsed?.contextUsagePercentage ?? 0) / 100;
7640
+ const totalInputEstimate = contextPct > 0 ? Math.floor(contextLength * contextPct) : 0;
7641
+ const hasNoTokenData = allLogs.every(({ log }) => {
7642
+ const pl = typeof log.prompt === "string" ? log.prompt.length : 0;
7643
+ const cl = typeof log.completion === "string" ? log.completion.length : 0;
7644
+ return pl < 100 && cl === 0;
7645
+ });
7646
+ for (const { log, index } of allLogs) {
7647
+ const promptLen = typeof log.prompt === "string" ? log.prompt.length : 0;
7648
+ const completionLen = typeof log.completion === "string" ? log.completion.length : 0;
7649
+ let inputTokens;
7650
+ let outputTokens;
7651
+ if (hasNoTokenData) {
7652
+ inputTokens = totalInputEstimate > 0 && allLogs.length > 0 ? Math.max(1, Math.floor(totalInputEstimate / allLogs.length)) : 200;
7653
+ outputTokens = Math.floor(maxTokens * 0.3);
7654
+ } else {
7655
+ inputTokens = totalInputEstimate > 0 && allLogs.length > 0 ? Math.max(1, Math.floor(totalInputEstimate / allLogs.length)) : Math.max(1, Math.floor(promptLen / CHARS_PER_TOKEN2));
7656
+ outputTokens = completionLen > 0 ? Math.floor(completionLen / CHARS_PER_TOKEN2) : Math.floor(maxTokens * 0.3);
7657
+ }
7658
+ if (inputTokens + outputTokens === 0) continue;
7659
+ const model = normalizeKiroModel(log.completionOptions?.model ?? log.modelTitle);
7660
+ const provider = typeof log.provider === "string" && log.provider.trim() ? log.provider.trim().toLowerCase() : inferProvider(model);
7661
+ const recordTs = sessionTs + index;
7662
+ const tokenArgs = { inputTokens, outputTokens, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
7663
+ const cost = calculateCost(model, tokenArgs, exchangeRate);
7664
+ const sourceId = `${sessionId}:${index}`;
7665
+ records.push({
7666
+ id: generateRecordId(deviceInstanceId, `${filePath}:${sourceId}`, recordTs),
7667
+ ts: recordTs,
7668
+ ingestedAt: now,
7669
+ updatedAt: now,
7670
+ lineOffset: index,
7671
+ tool: "kiro",
7672
+ model,
7673
+ provider,
7674
+ inputTokens,
7675
+ outputTokens,
7676
+ cacheReadTokens: 0,
7677
+ cacheWriteTokens: 0,
7678
+ thinkingTokens: 0,
7679
+ cost,
7680
+ costSource: cost > 0 ? "pricing" : "unknown",
7681
+ sessionId,
7682
+ sourceFile: filePath,
7683
+ device,
7684
+ deviceInstanceId,
7685
+ platform: platform5
7686
+ });
7687
+ }
7688
+ return { records, errors };
7689
+ }
7296
7690
  async function runParse(db, filterTool, options) {
7297
7691
  const state = getState(AIUSAGE_DIR);
7298
7692
  const config = loadConfig();
@@ -7300,7 +7694,7 @@ async function runParse(db, filterTool, options) {
7300
7694
  const device = config?.device || hostname2() || state?.deviceInstanceId?.slice(0, 8) || "unknown";
7301
7695
  const deviceInstanceId = state?.deviceInstanceId ?? "unknown";
7302
7696
  const devicePlatform = config?.platform;
7303
- const watermarkPath = join7(AIUSAGE_DIR, "watermark.json");
7697
+ const watermarkPath = join8(AIUSAGE_DIR, "watermark.json");
7304
7698
  const wm = new WatermarkManager(watermarkPath);
7305
7699
  const toolPaths = discoverLogFiles();
7306
7700
  const aggregator = new Aggregator();
@@ -7309,20 +7703,18 @@ async function runParse(db, filterTool, options) {
7309
7703
  const errors = [];
7310
7704
  const onProgress = options?.onProgress ?? (() => {
7311
7705
  });
7312
- const totalFiles = toolPaths.filter(({ tool }) => !filterTool || tool === filterTool).reduce((sum, { paths }) => sum + paths.length, 0);
7313
- let fileIndex = 0;
7314
- let currentTool;
7315
7706
  for (const { tool, paths } of toolPaths) {
7316
7707
  if (filterTool && tool !== filterTool) continue;
7317
- currentTool = tool;
7708
+ const toolTotal = paths.length;
7709
+ let toolIndex = 0;
7318
7710
  for (const filePath of paths) {
7319
- fileIndex++;
7711
+ toolIndex++;
7320
7712
  try {
7321
- const stat2 = statSync2(filePath);
7713
+ const stat2 = statSync3(filePath);
7322
7714
  const entry = wm.getEntry(tool, filePath);
7323
7715
  const offset = entry?.offset ?? 0;
7324
7716
  if (offset >= stat2.size) {
7325
- onProgress({ phase: "Parsing logs", tool, current: fileIndex, total: totalFiles, records: parsedCount, toolCalls: toolCallCount });
7717
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7326
7718
  continue;
7327
7719
  }
7328
7720
  if ((tool === "roocode" || tool === "kilocode") && filePath.endsWith("ui_messages.json")) {
@@ -7346,7 +7738,7 @@ async function runParse(db, filterTool, options) {
7346
7738
  mtime: stat2.mtimeMs
7347
7739
  });
7348
7740
  wm.save();
7349
- onProgress({ phase: "Parsing logs", tool, current: fileIndex, total: totalFiles, records: parsedCount, toolCalls: toolCallCount });
7741
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7350
7742
  continue;
7351
7743
  }
7352
7744
  if (tool === "kiro" && filePath.endsWith(".json")) {
@@ -7369,7 +7761,67 @@ async function runParse(db, filterTool, options) {
7369
7761
  mtime: stat2.mtimeMs
7370
7762
  });
7371
7763
  wm.save();
7372
- onProgress({ phase: "Parsing logs", tool, current: fileIndex, total: totalFiles, records: parsedCount, toolCalls: toolCallCount });
7764
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7765
+ continue;
7766
+ }
7767
+ if (tool === "kiro" && filePath.endsWith(".jsonl")) {
7768
+ const content2 = readFileSync9(filePath, "utf-8");
7769
+ const lines2 = content2.split("\n");
7770
+ let byteOffset2 = 0;
7771
+ for (const line of lines2) {
7772
+ if (!line.trim()) {
7773
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
7774
+ continue;
7775
+ }
7776
+ try {
7777
+ const data = JSON.parse(line);
7778
+ const inputTokens = toNumber2(data.promptTokens ?? data.inputTokens ?? data.tokensIn);
7779
+ const outputTokens = toNumber2(data.generatedTokens ?? data.outputTokens ?? data.tokensOut);
7780
+ if (inputTokens + outputTokens === 0) {
7781
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
7782
+ continue;
7783
+ }
7784
+ const model = normalizeKiroModel(data.model);
7785
+ const provider = typeof data.provider === "string" && data.provider.trim() ? data.provider.trim().toLowerCase() : inferProvider(model);
7786
+ const recordTs = stat2.mtimeMs;
7787
+ const tokenArgs = { inputTokens, outputTokens, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
7788
+ const cost = calculateCost(model, tokenArgs, exchangeRate);
7789
+ const recordId = generateRecordId(deviceInstanceId, `${filePath}:${byteOffset2}`, recordTs);
7790
+ const record = {
7791
+ id: recordId,
7792
+ ts: recordTs,
7793
+ ingestedAt: Date.now(),
7794
+ updatedAt: Date.now(),
7795
+ lineOffset: byteOffset2,
7796
+ tool: "kiro",
7797
+ model,
7798
+ provider,
7799
+ inputTokens,
7800
+ outputTokens,
7801
+ cacheReadTokens: 0,
7802
+ cacheWriteTokens: 0,
7803
+ thinkingTokens: 0,
7804
+ cost,
7805
+ costSource: cost > 0 ? "pricing" : "unknown",
7806
+ sessionId: filePath,
7807
+ sourceFile: filePath,
7808
+ device,
7809
+ deviceInstanceId,
7810
+ platform: devicePlatform
7811
+ };
7812
+ insertRecord(db, record);
7813
+ parsedCount++;
7814
+ } catch {
7815
+ }
7816
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
7817
+ }
7818
+ wm.setEntry(tool, filePath, {
7819
+ offset: stat2.size,
7820
+ size: stat2.size,
7821
+ mtime: stat2.mtimeMs
7822
+ });
7823
+ wm.save();
7824
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7373
7825
  continue;
7374
7826
  }
7375
7827
  if (tool === "kelivo" && (filePath.endsWith("chats.json") || filePath.endsWith(".zip"))) {
@@ -7392,10 +7844,58 @@ async function runParse(db, filterTool, options) {
7392
7844
  mtime: stat2.mtimeMs
7393
7845
  });
7394
7846
  wm.save();
7395
- onProgress({ phase: "Parsing logs", tool, current: fileIndex, total: totalFiles, records: parsedCount, toolCalls: toolCallCount });
7847
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7848
+ continue;
7849
+ }
7850
+ if (tool === "cursor" && filePath.endsWith(".jsonl") && filePath.includes("agent-transcripts")) {
7851
+ const est = runParseCursorTranscript({
7852
+ jsonlPath: filePath,
7853
+ device,
7854
+ deviceInstanceId,
7855
+ platform: devicePlatform,
7856
+ now: Date.now()
7857
+ });
7858
+ if (est.inputTextChars + est.outputTextChars > 0) {
7859
+ const sessionId2 = filePath.split("/").slice(-2, -1)[0] || "unknown";
7860
+ const recordTs = stat2.mtimeMs;
7861
+ const tokenArgs = { inputTokens: est.inputTextChars, outputTokens: est.outputTextChars, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
7862
+ const cost = calculateCost("cursor-composer", tokenArgs, exchangeRate);
7863
+ const recordId = generateRecordId(deviceInstanceId, filePath, recordTs);
7864
+ const record = {
7865
+ id: recordId,
7866
+ ts: recordTs,
7867
+ ingestedAt: Date.now(),
7868
+ updatedAt: Date.now(),
7869
+ lineOffset: 0,
7870
+ tool: "cursor",
7871
+ model: "cursor-composer",
7872
+ provider: "cursor",
7873
+ inputTokens: est.inputTextChars,
7874
+ outputTokens: est.outputTextChars,
7875
+ cacheReadTokens: 0,
7876
+ cacheWriteTokens: 0,
7877
+ thinkingTokens: 0,
7878
+ cost,
7879
+ costSource: cost > 0 ? "pricing" : "unknown",
7880
+ sessionId: sessionId2,
7881
+ sourceFile: filePath,
7882
+ device,
7883
+ deviceInstanceId,
7884
+ platform: devicePlatform
7885
+ };
7886
+ insertRecord(db, record);
7887
+ parsedCount++;
7888
+ }
7889
+ wm.setEntry(tool, filePath, {
7890
+ offset: stat2.size,
7891
+ size: stat2.size,
7892
+ mtime: stat2.mtimeMs
7893
+ });
7894
+ wm.save();
7895
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7396
7896
  continue;
7397
7897
  }
7398
- const content = readFileSync8(filePath, "utf-8");
7898
+ const content = readFileSync9(filePath, "utf-8");
7399
7899
  const lines = content.split("\n");
7400
7900
  let byteOffset = 0;
7401
7901
  let fileCwd;
@@ -7467,14 +7967,14 @@ async function runParse(db, filterTool, options) {
7467
7967
  mtime: stat2.mtimeMs
7468
7968
  });
7469
7969
  wm.save();
7470
- onProgress({ phase: "Parsing logs", tool, current: fileIndex, total: totalFiles, records: parsedCount, toolCalls: toolCallCount });
7970
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7471
7971
  } catch (e) {
7472
7972
  errors.push(`${filePath}: ${e instanceof Error ? e.message : e}`);
7473
7973
  }
7474
7974
  }
7475
7975
  }
7476
7976
  const openCodeDbPath = options?.openCodeDbPath ?? getDbPath("opencode") ?? "";
7477
- if ((!filterTool || filterTool === "opencode") && existsSync8(openCodeDbPath)) {
7977
+ if ((!filterTool || filterTool === "opencode") && existsSync9(openCodeDbPath)) {
7478
7978
  try {
7479
7979
  const openCodeDb = new Database2(openCodeDbPath, { readonly: true });
7480
7980
  try {
@@ -7505,7 +8005,7 @@ async function runParse(db, filterTool, options) {
7505
8005
  }
7506
8006
  }
7507
8007
  const hermesDbPath = options?.hermesDbPath ?? getDbPath("hermes") ?? "";
7508
- if ((!filterTool || filterTool === "hermes") && existsSync8(hermesDbPath)) {
8008
+ if ((!filterTool || filterTool === "hermes") && existsSync9(hermesDbPath)) {
7509
8009
  try {
7510
8010
  const hermesDb = new Database2(hermesDbPath, { readonly: true });
7511
8011
  try {
@@ -7536,7 +8036,7 @@ async function runParse(db, filterTool, options) {
7536
8036
  }
7537
8037
  }
7538
8038
  const qoderDbPath = options?.qoderDbPath ?? getDbPath("qoder-db") ?? "";
7539
- if ((!filterTool || filterTool === "qoder") && existsSync8(qoderDbPath)) {
8039
+ if ((!filterTool || filterTool === "qoder") && existsSync9(qoderDbPath)) {
7540
8040
  try {
7541
8041
  const qoderDb = new Database2(qoderDbPath, { readonly: true });
7542
8042
  try {
@@ -7567,7 +8067,7 @@ async function runParse(db, filterTool, options) {
7567
8067
  }
7568
8068
  }
7569
8069
  const cursorDbPath = options?.cursorDbPath ?? getDbPath("cursor") ?? "";
7570
- if ((!filterTool || filterTool === "cursor") && existsSync8(cursorDbPath)) {
8070
+ if ((!filterTool || filterTool === "cursor") && existsSync9(cursorDbPath)) {
7571
8071
  try {
7572
8072
  const cursorDb = new Database2(cursorDbPath, { readonly: true });
7573
8073
  try {
@@ -7597,7 +8097,7 @@ async function runParse(db, filterTool, options) {
7597
8097
  }
7598
8098
  }
7599
8099
  const kiloDbPath = getDbPath("kilocode-db") ?? "";
7600
- if ((!filterTool || filterTool === "kilocode") && existsSync8(kiloDbPath)) {
8100
+ if ((!filterTool || filterTool === "kilocode") && existsSync9(kiloDbPath)) {
7601
8101
  try {
7602
8102
  const kiloDb = new Database2(kiloDbPath, { readonly: true });
7603
8103
  try {
@@ -7637,7 +8137,7 @@ async function runParse(db, filterTool, options) {
7637
8137
  }
7638
8138
  }
7639
8139
  const gooseDbPath = getDbPath("goose") ?? "";
7640
- if ((!filterTool || filterTool === "goose") && existsSync8(gooseDbPath)) {
8140
+ if ((!filterTool || filterTool === "goose") && existsSync9(gooseDbPath)) {
7641
8141
  try {
7642
8142
  const gooseDb = new Database2(gooseDbPath, { readonly: true });
7643
8143
  try {
@@ -7695,7 +8195,7 @@ async function runParse(db, filterTool, options) {
7695
8195
  }
7696
8196
  }
7697
8197
  const zedDbPath = getDbPath("zed") ?? "";
7698
- if ((!filterTool || filterTool === "zed") && existsSync8(zedDbPath)) {
8198
+ if ((!filterTool || filterTool === "zed") && existsSync9(zedDbPath)) {
7699
8199
  try {
7700
8200
  const zedDb = new Database2(zedDbPath, { readonly: true });
7701
8201
  try {
@@ -7725,7 +8225,7 @@ async function runParse(db, filterTool, options) {
7725
8225
  }
7726
8226
  if (!filterTool || filterTool === "zcode") {
7727
8227
  const zcodeDbPath = getDbPath("zcode") ?? "";
7728
- if (existsSync8(zcodeDbPath)) {
8228
+ if (existsSync9(zcodeDbPath)) {
7729
8229
  try {
7730
8230
  const zcodeDb = new Database2(zcodeDbPath, { readonly: true });
7731
8231
  try {
@@ -7762,6 +8262,28 @@ async function runParse(db, filterTool, options) {
7762
8262
  }
7763
8263
  }
7764
8264
  }
8265
+ const traeDbPath = getDbPath("trae") ?? "";
8266
+ if ((!filterTool || filterTool === "trae") && pathIsFile(traeDbPath)) {
8267
+ const traeLastImported = wm.getTraeLastImported?.() ?? 0;
8268
+ const result = runParseTrae({
8269
+ dbPath: traeDbPath,
8270
+ device,
8271
+ deviceInstanceId,
8272
+ platform: devicePlatform,
8273
+ now: Date.now(),
8274
+ lastImportedAt: traeLastImported
8275
+ });
8276
+ for (const record of result.records) insertRecord(db, record);
8277
+ for (const tc of result.toolCalls) insertToolCall(db, tc);
8278
+ parsedCount += result.records.length;
8279
+ toolCallCount += result.toolCalls.length;
8280
+ errors.push(...result.errors);
8281
+ if (result.lastImportedAt > traeLastImported) {
8282
+ wm.setTraeLastImported?.(result.lastImportedAt);
8283
+ wm.save();
8284
+ }
8285
+ onProgress({ phase: "Parsing SQLite", tool: "trae", current: 1, total: 1, records: parsedCount, toolCalls: toolCallCount });
8286
+ }
7765
8287
  if (deviceInstanceId !== "unknown") {
7766
8288
  db.prepare(
7767
8289
  `UPDATE records SET device_instance_id = ?, device = ? WHERE device_instance_id = 'unknown'`
@@ -7808,7 +8330,7 @@ function backfillHermesSourceFiles(db) {
7808
8330
  const dbPath = rows[0].source_file;
7809
8331
  let titleMap = /* @__PURE__ */ new Map();
7810
8332
  try {
7811
- if (existsSync8(dbPath)) {
8333
+ if (existsSync9(dbPath)) {
7812
8334
  const hermesDb = new Database2(dbPath, { readonly: true });
7813
8335
  try {
7814
8336
  const sessions = hermesDb.prepare(
@@ -7889,7 +8411,7 @@ function backfillCodexModels(db) {
7889
8411
  );
7890
8412
  for (const [sourceFile, fileRows] of byFile) {
7891
8413
  try {
7892
- const content = readFileSync8(sourceFile, "utf-8");
8414
+ const content = readFileSync9(sourceFile, "utf-8");
7893
8415
  const lines = content.split("\n");
7894
8416
  const offsetToModel = /* @__PURE__ */ new Map();
7895
8417
  let currentModel = "";
@@ -7937,7 +8459,7 @@ function backfillMissingToolCalls(db, exchangeRate) {
7937
8459
  }
7938
8460
  for (const [sourceFile, fileRows] of rowsByFile) {
7939
8461
  try {
7940
- const content = readFileSync8(sourceFile, "utf-8");
8462
+ const content = readFileSync9(sourceFile, "utf-8");
7941
8463
  const lines = content.split("\n");
7942
8464
  const aggregator = new Aggregator();
7943
8465
  const recordIdByOffset = /* @__PURE__ */ new Map();
@@ -7999,7 +8521,7 @@ function deriveSessionId(tool, sourceFile) {
7999
8521
  }
8000
8522
 
8001
8523
  // src/commands/sync.ts
8002
- import { join as join9 } from "path";
8524
+ import { join as join10 } from "path";
8003
8525
 
8004
8526
  // src/db/synced-records.ts
8005
8527
  function insertSyncedRecord(db, record) {
@@ -8432,10 +8954,10 @@ async function cloudClear() {
8432
8954
  }
8433
8955
  function getClientVersion() {
8434
8956
  try {
8435
- const { readFileSync: readFileSync12 } = __require("fs");
8436
- const { join: join16 } = __require("path");
8437
- const pkgPath = join16(__dirname, "../../package.json");
8438
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
8957
+ const { readFileSync: readFileSync13 } = __require("fs");
8958
+ const { join: join17 } = __require("path");
8959
+ const pkgPath = join17(__dirname, "../../package.json");
8960
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
8439
8961
  return pkg.version || "0.0.0";
8440
8962
  } catch {
8441
8963
  return "0.0.0";
@@ -8526,7 +9048,7 @@ var CloudSyncOrchestrator = class {
8526
9048
  import { execFile } from "child_process";
8527
9049
  import { promisify } from "util";
8528
9050
  import { readFile, writeFile, mkdir, readdir, stat, unlink, rm } from "fs/promises";
8529
- import { join as join8 } from "path";
9051
+ import { join as join9 } from "path";
8530
9052
  var exec = promisify(execFile);
8531
9053
  var GitSyncBackend = class _GitSyncBackend {
8532
9054
  repo;
@@ -8538,7 +9060,7 @@ var GitSyncBackend = class _GitSyncBackend {
8538
9060
  this.repo = config.repo;
8539
9061
  this.token = config.token;
8540
9062
  this.cacheDir = config.cacheDir;
8541
- this.dataDir = join8(config.cacheDir, "data");
9063
+ this.dataDir = join9(config.cacheDir, "data");
8542
9064
  this.branch = config.branch ?? "main";
8543
9065
  }
8544
9066
  get remoteUrl() {
@@ -8570,7 +9092,7 @@ var GitSyncBackend = class _GitSyncBackend {
8570
9092
  /** Clone or pull the repo to get latest remote state */
8571
9093
  async prepare() {
8572
9094
  try {
8573
- await stat(join8(this.cacheDir, ".git"));
9095
+ await stat(join9(this.cacheDir, ".git"));
8574
9096
  await this.git(["fetch", "origin", this.branch, "--depth=1"]);
8575
9097
  await this.git(["reset", "--hard", `origin/${this.branch}`]);
8576
9098
  } catch {
@@ -8587,15 +9109,15 @@ var GitSyncBackend = class _GitSyncBackend {
8587
9109
  }
8588
9110
  async readFile(path3) {
8589
9111
  try {
8590
- const fullPath = join8(this.dataDir, path3);
9112
+ const fullPath = join9(this.dataDir, path3);
8591
9113
  return await readFile(fullPath, "utf-8");
8592
9114
  } catch {
8593
9115
  return null;
8594
9116
  }
8595
9117
  }
8596
9118
  async writeFile(path3, content) {
8597
- const fullPath = join8(this.dataDir, path3);
8598
- await mkdir(join8(fullPath, ".."), { recursive: true });
9119
+ const fullPath = join9(this.dataDir, path3);
9120
+ await mkdir(join9(fullPath, ".."), { recursive: true });
8599
9121
  await writeFile(fullPath, content, "utf-8");
8600
9122
  }
8601
9123
  async listFiles() {
@@ -8606,7 +9128,7 @@ var GitSyncBackend = class _GitSyncBackend {
8606
9128
  }
8607
9129
  }
8608
9130
  async deleteFile(path3) {
8609
- const fullPath = join8(this.dataDir, path3);
9131
+ const fullPath = join9(this.dataDir, path3);
8610
9132
  try {
8611
9133
  await unlink(fullPath);
8612
9134
  } catch {
@@ -8645,7 +9167,7 @@ var GitSyncBackend = class _GitSyncBackend {
8645
9167
  for (const entry of entries) {
8646
9168
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
8647
9169
  if (entry.isDirectory()) {
8648
- files.push(...await this.walkDir(join8(dir, entry.name), relPath));
9170
+ files.push(...await this.walkDir(join9(dir, entry.name), relPath));
8649
9171
  } else if (entry.name.endsWith(".ndjson")) {
8650
9172
  files.push(relPath);
8651
9173
  }
@@ -8779,7 +9301,7 @@ function createBackend(config) {
8779
9301
  return new GitSyncBackend({
8780
9302
  repo: sync.repo,
8781
9303
  token,
8782
- cacheDir: join9(AIUSAGE_DIR, "sync-repo")
9304
+ cacheDir: join10(AIUSAGE_DIR, "sync-repo")
8783
9305
  });
8784
9306
  }
8785
9307
  if (sync.backend === "s3") {
@@ -8874,8 +9396,8 @@ async function runSync(db, options) {
8874
9396
  }
8875
9397
 
8876
9398
  // src/commands/clean.ts
8877
- import { unlinkSync as unlinkSync2, existsSync as existsSync9 } from "fs";
8878
- import { join as join10 } from "path";
9399
+ import { unlinkSync as unlinkSync3, existsSync as existsSync10 } from "fs";
9400
+ import { join as join11 } from "path";
8879
9401
  function cleanOldData(db, days) {
8880
9402
  const cutoff = Date.now() - days * 864e5;
8881
9403
  const recordsResult = db.prepare("DELETE FROM records WHERE ts < ?").run(cutoff);
@@ -8896,10 +9418,10 @@ function cleanAll(db) {
8896
9418
  const syncedResult = db.prepare("DELETE FROM synced_records").run();
8897
9419
  const syncStateResult = db.prepare("DELETE FROM sync_record_state").run();
8898
9420
  const tombstonesResult = db.prepare("DELETE FROM sync_tombstones").run();
8899
- const watermarkPath = join10(AIUSAGE_DIR, "watermark.json");
9421
+ const watermarkPath = join11(AIUSAGE_DIR, "watermark.json");
8900
9422
  let watermarkRemoved = false;
8901
- if (existsSync9(watermarkPath)) {
8902
- unlinkSync2(watermarkPath);
9423
+ if (existsSync10(watermarkPath)) {
9424
+ unlinkSync3(watermarkPath);
8903
9425
  watermarkRemoved = true;
8904
9426
  }
8905
9427
  return {
@@ -8933,7 +9455,7 @@ function createBackend2(config) {
8933
9455
  return new GitSyncBackend({
8934
9456
  repo: config.sync.repo,
8935
9457
  token,
8936
- cacheDir: join10(AIUSAGE_DIR, "sync-repo")
9458
+ cacheDir: join11(AIUSAGE_DIR, "sync-repo")
8937
9459
  });
8938
9460
  }
8939
9461
  if (config.sync.backend === "s3") {
@@ -9266,7 +9788,7 @@ var RuntimeSettingsController = class {
9266
9788
 
9267
9789
  // src/commands/serve.ts
9268
9790
  var MAX_PORT_ATTEMPTS = 10;
9269
- var PORT_FILE = join11(AIUSAGE_DIR, ".serve-port");
9791
+ var PORT_FILE = join12(AIUSAGE_DIR, ".serve-port");
9270
9792
  var MIME_TYPES = {
9271
9793
  ".html": "text/html",
9272
9794
  ".js": "application/javascript",
@@ -9283,6 +9805,15 @@ function serve(options) {
9283
9805
  const config = loadConfig();
9284
9806
  const dbWriteQueue = new AsyncTaskQueue();
9285
9807
  const runDbWrite = (task) => dbWriteQueue.run(task);
9808
+ if (config?.priceOverrides && Object.keys(config.priceOverrides).length > 0) {
9809
+ const imported = importConfigPriceOverrides(options.db, config.priceOverrides);
9810
+ delete config.priceOverrides;
9811
+ saveConfig(config);
9812
+ loadPricingRuntime(options.db, config);
9813
+ if (imported.length > 0) {
9814
+ console.log(`[serve] migrated ${imported.length} config price override(s) into the registry: ${imported.join(", ")}`);
9815
+ }
9816
+ }
9286
9817
  if (config == null || config.exchangeRate == null) {
9287
9818
  const cacheAge = config?.exchangeRateCache ? Date.now() - config.exchangeRateCache.fetchedAt : Infinity;
9288
9819
  if (cacheAge >= CACHE_TTL_MS) {
@@ -9316,7 +9847,10 @@ function serve(options) {
9316
9847
  onSyncScheduleChanged: (ts3) => syncRuntime.setNextSyncAt(ts3)
9317
9848
  });
9318
9849
  runtimeSettings.start();
9319
- runDbWrite(() => runParse(options.db)).catch((err) => {
9850
+ console.log("[serve] parsing logs...");
9851
+ runDbWrite(() => runParse(options.db)).then((result) => {
9852
+ console.log(`[serve] parsed ${result.parsedCount} records, ${result.toolCallCount} tool calls.`);
9853
+ }).catch((err) => {
9320
9854
  console.error("[serve] initial parse failed:", err);
9321
9855
  });
9322
9856
  const apiServer = createApiServer(options.db, {
@@ -9329,11 +9863,11 @@ function serve(options) {
9329
9863
  getDbWriteQueueStatus: () => dbWriteQueue.getStatus()
9330
9864
  });
9331
9865
  const webBuildDir = (() => {
9332
- const prodDir = join11(dirname4(fileURLToPath(import.meta.url)), "web");
9333
- if (existsSync10(prodDir)) return prodDir;
9334
- return join11(dirname4(fileURLToPath(import.meta.url)), "..", "..", "..", "web", "build");
9866
+ const prodDir = join12(dirname5(fileURLToPath(import.meta.url)), "web");
9867
+ if (existsSync11(prodDir)) return prodDir;
9868
+ return join12(dirname5(fileURLToPath(import.meta.url)), "..", "..", "..", "web", "build");
9335
9869
  })();
9336
- if (!existsSync10(webBuildDir)) {
9870
+ if (!existsSync11(webBuildDir)) {
9337
9871
  console.error("Web dashboard not found. Reinstall the package: npm install -g @juliantanx/aiusage");
9338
9872
  process.exit(1);
9339
9873
  }
@@ -9343,19 +9877,19 @@ function serve(options) {
9343
9877
  apiServer.emit("request", req, res);
9344
9878
  return;
9345
9879
  }
9346
- if (existsSync10(webBuildDir)) {
9347
- let filePath = join11(webBuildDir, url.pathname);
9880
+ if (existsSync11(webBuildDir)) {
9881
+ let filePath = join12(webBuildDir, url.pathname);
9348
9882
  try {
9349
- if (statSync3(filePath).isDirectory()) {
9350
- filePath = join11(filePath, "index.html");
9883
+ if (statSync4(filePath).isDirectory()) {
9884
+ filePath = join12(filePath, "index.html");
9351
9885
  }
9352
9886
  } catch {
9353
9887
  }
9354
- if (!existsSync10(filePath)) {
9355
- filePath = join11(webBuildDir, "index.html");
9888
+ if (!existsSync11(filePath)) {
9889
+ filePath = join12(webBuildDir, "index.html");
9356
9890
  }
9357
9891
  try {
9358
- const content = readFileSync9(filePath);
9892
+ const content = readFileSync10(filePath);
9359
9893
  const ext = extname2(filePath);
9360
9894
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
9361
9895
  res.writeHead(200, { "Content-Type": contentType });
@@ -9393,7 +9927,7 @@ function serve(options) {
9393
9927
  listenOnPort(options.port);
9394
9928
  const cleanup = () => {
9395
9929
  try {
9396
- unlinkSync3(PORT_FILE);
9930
+ unlinkSync4(PORT_FILE);
9397
9931
  } catch {
9398
9932
  }
9399
9933
  };
@@ -9537,9 +10071,9 @@ Consent recorded for schema v1.`
9537
10071
  }
9538
10072
 
9539
10073
  // src/commands/status.ts
9540
- import { statSync as statSync4, existsSync as existsSync11 } from "fs";
10074
+ import { statSync as statSync5, existsSync as existsSync12 } from "fs";
9541
10075
  import { hostname as hostname3 } from "os";
9542
- import { join as join12 } from "path";
10076
+ import { join as join13 } from "path";
9543
10077
  function formatFileSize(bytes) {
9544
10078
  if (bytes < 1024) return `${bytes} B`;
9545
10079
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -9557,11 +10091,11 @@ function generateStatus(db) {
9557
10091
  const schemaVersion = db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get()?.version ?? 0;
9558
10092
  const tableCount = db.prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").get()?.count ?? 0;
9559
10093
  const viewCount = db.prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type = 'view'").get()?.count ?? 0;
9560
- const sizePath = join12(AIUSAGE_DIR, "cache.db");
10094
+ const sizePath = join13(AIUSAGE_DIR, "cache.db");
9561
10095
  let databaseSize = "0 B";
9562
- if (existsSync11(sizePath)) {
10096
+ if (existsSync12(sizePath)) {
9563
10097
  try {
9564
- const stat2 = statSync4(sizePath);
10098
+ const stat2 = statSync5(sizePath);
9565
10099
  databaseSize = formatFileSize(stat2.size);
9566
10100
  } catch {
9567
10101
  }
@@ -9650,11 +10184,11 @@ function recalcPricing(db) {
9650
10184
  ).all(lastId, BATCH_SIZE);
9651
10185
  if (records.length === 0) break;
9652
10186
  for (const record of records) {
9653
- if (record.cost_source === "log") {
10187
+ const model = record.tool === "qoder" ? normalizeQoderModel(record.model) : record.model;
10188
+ if (record.cost_source === "log" && record.cost > 0 && !hasUserPrice(db, model)) {
9654
10189
  skippedCount++;
9655
10190
  continue;
9656
10191
  }
9657
- const model = record.tool === "qoder" ? normalizeQoderModel(record.model) : record.model;
9658
10192
  const provider = model !== record.model ? inferProvider(model) : record.provider;
9659
10193
  const price = resolvePriceFromRegistry(db, model);
9660
10194
  const newCost = price ? calculateCostForPrice(price, {
@@ -9685,15 +10219,40 @@ function formatBar(ratio, width = 20) {
9685
10219
  var ProgressReporter = class {
9686
10220
  lastWrite = 0;
9687
10221
  lastLine = "";
10222
+ lastInfo = null;
10223
+ currentTool;
10224
+ toolRecords = /* @__PURE__ */ new Map();
10225
+ recordsAtToolStart = 0;
9688
10226
  update(info) {
9689
10227
  if (!isTTY) return;
10228
+ if (info.tool && info.tool !== this.currentTool) {
10229
+ if (this.currentTool && this.lastInfo) {
10230
+ const toolRecordCount = this.lastInfo.records - this.recordsAtToolStart;
10231
+ this.toolRecords.set(this.currentTool, toolRecordCount);
10232
+ if (toolRecordCount > 0) {
10233
+ this.writeLine(this.lastInfo);
10234
+ process.stderr.write("\n");
10235
+ } else {
10236
+ process.stderr.write("\r\x1B[K");
10237
+ }
10238
+ this.lastLine = "";
10239
+ }
10240
+ this.currentTool = info.tool;
10241
+ this.recordsAtToolStart = info.records;
10242
+ }
10243
+ this.lastInfo = info;
9690
10244
  const now = Date.now();
9691
- if (now - this.lastWrite < MIN_INTERVAL_MS) return;
10245
+ const isComplete = info.total > 0 && info.current >= info.total;
10246
+ if (!isComplete && now - this.lastWrite < MIN_INTERVAL_MS) return;
9692
10247
  this.lastWrite = now;
10248
+ this.writeLine(info);
10249
+ }
10250
+ writeLine(info) {
9693
10251
  const pct = info.total > 0 ? info.current / info.total * 100 : 0;
9694
10252
  const bar = formatBar(info.total > 0 ? info.current / info.total : 0);
9695
10253
  const toolLabel = info.tool ? ` [${info.tool}]` : "";
9696
- const line = `\r${info.phase}${toolLabel} ${bar} ${pct.toFixed(1)}% ${info.current}/${info.total} records: ${info.records} calls: ${info.toolCalls} `;
10254
+ const toolRecordCount = info.records - this.recordsAtToolStart;
10255
+ const line = `\r${info.phase}${toolLabel} ${bar} ${pct.toFixed(1)}% ${info.current}/${info.total} records: ${toolRecordCount} `;
9697
10256
  if (line !== this.lastLine) {
9698
10257
  process.stderr.write(line);
9699
10258
  this.lastLine = line;
@@ -9701,7 +10260,16 @@ var ProgressReporter = class {
9701
10260
  }
9702
10261
  done() {
9703
10262
  if (!isTTY) return;
9704
- process.stderr.write("\r\x1B[K");
10263
+ if (this.lastInfo && this.currentTool) {
10264
+ const toolRecordCount = this.lastInfo.records - this.recordsAtToolStart;
10265
+ this.toolRecords.set(this.currentTool, toolRecordCount);
10266
+ if (toolRecordCount > 0) {
10267
+ this.writeLine(this.lastInfo);
10268
+ process.stderr.write("\n");
10269
+ } else {
10270
+ process.stderr.write("\r\x1B[K");
10271
+ }
10272
+ }
9705
10273
  }
9706
10274
  };
9707
10275
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -9748,10 +10316,10 @@ var SyncProgressReporter = class {
9748
10316
  };
9749
10317
 
9750
10318
  // src/commands/widget.ts
9751
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
10319
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
9752
10320
  import { spawn, execSync as execSync2 } from "child_process";
9753
- import { join as join13 } from "path";
9754
- var WIDGET_PID_PATH = join13(AIUSAGE_DIR, "widget.pid");
10321
+ import { join as join14 } from "path";
10322
+ var WIDGET_PID_PATH = join14(AIUSAGE_DIR, "widget.pid");
9755
10323
  async function launchWidget() {
9756
10324
  if (isWidgetRunning()) {
9757
10325
  console.log("aiusage widget is already running in the system tray.");
@@ -9773,10 +10341,10 @@ async function launchWidget() {
9773
10341
  console.log("aiusage widget started.");
9774
10342
  }
9775
10343
  function isWidgetRunning() {
9776
- if (!existsSync12(WIDGET_PID_PATH)) return false;
10344
+ if (!existsSync13(WIDGET_PID_PATH)) return false;
9777
10345
  let pid;
9778
10346
  try {
9779
- pid = parseInt(readFileSync10(WIDGET_PID_PATH, "utf-8").trim(), 10);
10347
+ pid = parseInt(readFileSync11(WIDGET_PID_PATH, "utf-8").trim(), 10);
9780
10348
  } catch {
9781
10349
  return false;
9782
10350
  }
@@ -9820,10 +10388,10 @@ function getDeviceName2() {
9820
10388
  }
9821
10389
  function getCliVersion2() {
9822
10390
  try {
9823
- const { readFileSync: readFileSync12 } = __require("fs");
9824
- const { join: join16 } = __require("path");
9825
- const pkgPath = join16(__dirname, "../../package.json");
9826
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
10391
+ const { readFileSync: readFileSync13 } = __require("fs");
10392
+ const { join: join17 } = __require("path");
10393
+ const pkgPath = join17(__dirname, "../../package.json");
10394
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
9827
10395
  return pkg.version || "0.0.0";
9828
10396
  } catch {
9829
10397
  return "0.0.0";
@@ -10066,17 +10634,17 @@ async function runLeaderboardView(options = {}) {
10066
10634
 
10067
10635
  // src/commands/menu.ts
10068
10636
  import { createInterface } from "readline";
10069
- import { spawn as spawn2, spawnSync, execSync as execSync4 } from "child_process";
10070
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
10071
- import { join as join14 } from "path";
10637
+ import { spawn as spawn2, spawnSync as spawnSync2, execSync as execSync4 } from "child_process";
10638
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
10639
+ import { join as join15 } from "path";
10072
10640
  var DEFAULT_PORT = 3847;
10073
- var PORT_FILE2 = join14(AIUSAGE_DIR, ".serve-port");
10641
+ var PORT_FILE2 = join15(AIUSAGE_DIR, ".serve-port");
10074
10642
  function clearScreen() {
10075
10643
  process.stdout.write("\x1Bc");
10076
10644
  }
10077
10645
  function getPort() {
10078
- if (existsSync13(PORT_FILE2)) {
10079
- const raw = readFileSync11(PORT_FILE2, "utf-8").trim();
10646
+ if (existsSync14(PORT_FILE2)) {
10647
+ const raw = readFileSync12(PORT_FILE2, "utf-8").trim();
10080
10648
  const n = parseInt(raw, 10);
10081
10649
  if (!isNaN(n) && n > 0) return n;
10082
10650
  }
@@ -10123,7 +10691,7 @@ function openBrowser2(url) {
10123
10691
  }
10124
10692
  function runCommand(args) {
10125
10693
  try {
10126
- spawnSync("aiusage", args, { stdio: "inherit" });
10694
+ spawnSync2("aiusage", args, { stdio: "inherit" });
10127
10695
  } catch {
10128
10696
  }
10129
10697
  }
@@ -10565,10 +11133,10 @@ ${dashboardStatusLine()}
10565
11133
  }
10566
11134
 
10567
11135
  // src/cli.ts
10568
- import { join as join15 } from "path";
10569
- var DB_PATH2 = join15(AIUSAGE_DIR, "cache.db");
11136
+ import { join as join16 } from "path";
11137
+ var DB_PATH2 = join16(AIUSAGE_DIR, "cache.db");
10570
11138
  var program = new Command();
10571
- program.name("aiusage").version(true ? "1.5.5" : "dev").description("CLI tool for AI usage statistics");
11139
+ program.name("aiusage").version(true ? "1.5.7" : "dev").description("CLI tool for AI usage statistics");
10572
11140
  program.action(() => {
10573
11141
  const unknownCommand = program.args.find((arg) => typeof arg === "string");
10574
11142
  if (unknownCommand) {
@@ -10855,7 +11423,7 @@ function hasWidget() {
10855
11423
  }
10856
11424
  }
10857
11425
  function buildPm2Config(includeWidget) {
10858
- const wrapperPath = join15(AIUSAGE_DIR, "pm2-server.cjs");
11426
+ const wrapperPath = join16(AIUSAGE_DIR, "pm2-server.cjs");
10859
11427
  writeFileSync7(wrapperPath, [
10860
11428
  `// Auto-generated by aiusage pm2-start \u2014 do not edit`,
10861
11429
  `const { execSync, spawn } = require('child_process')`,
@@ -10902,7 +11470,7 @@ ${apps.join(",\n")}
10902
11470
  program.command("pm2-setup").description("Generate ecosystem.config.cjs for PM2 background services").option("--server-only", "Only include server, skip widget").action((options) => {
10903
11471
  const includeWidget = options.serverOnly ? false : hasWidget();
10904
11472
  const config = buildPm2Config(includeWidget);
10905
- const configPath = join15(AIUSAGE_DIR, "ecosystem.config.cjs");
11473
+ const configPath = join16(AIUSAGE_DIR, "ecosystem.config.cjs");
10906
11474
  writeFileSync7(configPath, config, "utf-8");
10907
11475
  console.log(`Config created: ${configPath}`);
10908
11476
  if (!includeWidget) {
@@ -10912,7 +11480,7 @@ program.command("pm2-setup").description("Generate ecosystem.config.cjs for PM2
10912
11480
  program.command("pm2-start").description("Setup and start PM2 background services (requires pm2 installed globally)").option("--server-only", "Only start server, skip widget").action((options) => {
10913
11481
  const includeWidget = options.serverOnly ? false : hasWidget();
10914
11482
  const config = buildPm2Config(includeWidget);
10915
- const configPath = join15(AIUSAGE_DIR, "ecosystem.config.cjs");
11483
+ const configPath = join16(AIUSAGE_DIR, "ecosystem.config.cjs");
10916
11484
  writeFileSync7(configPath, config, "utf-8");
10917
11485
  console.log(`Config: ${configPath}`);
10918
11486
  try {