@juliantanx/aiusage 1.5.6 → 1.5.8

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 (24) hide show
  1. package/dist/index.js +940 -127
  2. package/dist/web/_app/immutable/chunks/{ToolSelector.0iI1cXVj.js → ToolSelector.CO-QGau6.js} +1 -1
  3. package/dist/web/_app/immutable/chunks/{entry.PMcg7HQL.js → entry.DJZO-wBk.js} +1 -1
  4. package/dist/web/_app/immutable/chunks/{stores.BSI_cVy6.js → stores.C6g63Ng7.js} +1 -1
  5. package/dist/web/_app/immutable/chunks/{stores.DE60qEGb.js → stores.CDuvMHgD.js} +1 -1
  6. package/dist/web/_app/immutable/entry/{app.CSiP_kAD.js → app.WQCJU-_e.js} +2 -2
  7. package/dist/web/_app/immutable/entry/start.COue4Eiw.js +1 -0
  8. package/dist/web/_app/immutable/nodes/{0.dH2tW-7d.js → 0.Fq-nYlaS.js} +1 -1
  9. package/dist/web/_app/immutable/nodes/{1.B8yHhyzy.js → 1.DrSfUVzz.js} +1 -1
  10. package/dist/web/_app/immutable/nodes/{10.CnTuhaua.js → 10.CP1F4_ac.js} +1 -1
  11. package/dist/web/_app/immutable/nodes/{11.5p54X4Ps.js → 11.YqYsFEzd.js} +1 -1
  12. package/dist/web/_app/immutable/nodes/{12.BCT8Dv1G.js → 12.RfrZJ-XB.js} +1 -1
  13. package/dist/web/_app/immutable/nodes/{14.u96Ucsue.js → 14.Cbg9FBSP.js} +1 -1
  14. package/dist/web/_app/immutable/nodes/{15.CMhy_NGl.js → 15.CQoUgLeT.js} +1 -1
  15. package/dist/web/_app/immutable/nodes/{2.CJH1Fs0K.js → 2.DLTFcQ9L.js} +1 -1
  16. package/dist/web/_app/immutable/nodes/{3.Chtgl9hh.js → 3.jLX2a-Ic.js} +1 -1
  17. package/dist/web/_app/immutable/nodes/{5.DtYG5yJy.js → 5.CVQN-3_l.js} +1 -1
  18. package/dist/web/_app/immutable/nodes/{6.B-ud4zCO.js → 6.C-WRTBwg.js} +1 -1
  19. package/dist/web/_app/immutable/nodes/{7.7Fm6yzqA.js → 7.DFkuOTpF.js} +1 -1
  20. package/dist/web/_app/immutable/nodes/{8.Chp3dlzK.js → 8.BNiBE_e2.js} +1 -1
  21. package/dist/web/_app/version.json +1 -1
  22. package/dist/web/index.html +6 -6
  23. package/package.json +3 -3
  24. package/dist/web/_app/immutable/entry/start.DnwUvynt.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 unlinkSync4 } from "fs";
17
- import { join as join11, extname as extname2, dirname as dirname4 } from "path";
16
+ import { readFileSync as readFileSync11, existsSync as existsSync12, statSync as statSync4, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "fs";
17
+ import { join as join13, extname as extname2, dirname as dirname6 } 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 = {
@@ -873,7 +872,39 @@ function sanitizeModel(value, fallback) {
873
872
  const model = lastPathSegment(value);
874
873
  return model || fallback;
875
874
  }
876
- function usageFromAny(parsed) {
875
+ function codeBuddyUsage(parsed) {
876
+ const ru = parsed?.providerData?.rawUsage;
877
+ if (ru && typeof ru === "object") {
878
+ const hit = num(ru.prompt_cache_hit_tokens ?? ru.prompt_tokens_details?.cached_tokens);
879
+ const miss = ru.prompt_cache_miss_tokens;
880
+ const prompt2 = num(ru.prompt_tokens ?? ru.input_tokens);
881
+ return {
882
+ inputTokens: miss != null ? num(miss) : Math.max(0, prompt2 - hit),
883
+ outputTokens: num(ru.completion_tokens ?? ru.output_tokens),
884
+ cacheReadTokens: hit,
885
+ cacheWriteTokens: num(ru.prompt_cache_write_tokens ?? ru.cache_creation_input_tokens),
886
+ thinkingTokens: num(ru.completion_thinking_tokens ?? ru.completion_tokens_details?.reasoning_tokens)
887
+ };
888
+ }
889
+ const mu = parsed?.message?.usage;
890
+ if (mu && typeof mu === "object") {
891
+ const cacheRead = num(mu.cache_read_input_tokens ?? mu.prompt_tokens_details?.cached_tokens);
892
+ const rawInput = num(mu.input_tokens ?? mu.prompt_tokens);
893
+ return {
894
+ inputTokens: Math.max(0, rawInput - cacheRead),
895
+ outputTokens: num(mu.output_tokens ?? mu.completion_tokens),
896
+ cacheReadTokens: cacheRead,
897
+ cacheWriteTokens: num(mu.cache_creation_input_tokens ?? mu.cache_write_input_tokens),
898
+ thinkingTokens: num(mu.thinking_tokens ?? mu.reasoning_tokens)
899
+ };
900
+ }
901
+ return null;
902
+ }
903
+ function usageFromAny(parsed, tool) {
904
+ if (tool === "codebuddy") {
905
+ const cb = codeBuddyUsage(parsed);
906
+ if (cb) return cb;
907
+ }
877
908
  const usage = parsed?.message?.usage ?? parsed?.usage ?? parsed?.event?.usage ?? parsed?.payload?.token_usage ?? parsed?.message?.payload?.token_usage ?? parsed?.providerData?.rawUsage ?? parsed?.data?.usage;
878
909
  if (!usage || typeof usage !== "object") return null;
879
910
  const details = usage.prompt_tokens_details ?? usage.input_tokens_details ?? {};
@@ -943,7 +974,7 @@ var GenericJsonlParser = class {
943
974
  }
944
975
  const normalized = parsed?.type === "context.append_loop_event" && parsed.event ? { ...parsed.event, time: parsed.time } : parsed;
945
976
  if (!shouldAccept(this.tool, normalized)) return null;
946
- const usage = usageFromAny(normalized);
977
+ const usage = usageFromAny(normalized, this.tool);
947
978
  if (!usage) return null;
948
979
  const total = usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.thinkingTokens;
949
980
  if (total === 0) return null;
@@ -1436,7 +1467,12 @@ function defaultCursorDbPath() {
1436
1467
  return join3(xdgConfigHome, "Cursor", "User", "globalStorage", "state.vscdb");
1437
1468
  }
1438
1469
  function defaultKiloDbPath() {
1439
- return join3(xdgDataDir(homedir2(), "kilo"), "kilo.db");
1470
+ const home = homedir2();
1471
+ if (platform() === "win32") {
1472
+ const appData = process.env.APPDATA ?? join3(home, "AppData", "Roaming");
1473
+ return join3(appData, "kilo", "kilo.db");
1474
+ }
1475
+ return join3(xdgDataDir(home, "kilo"), "kilo.db");
1440
1476
  }
1441
1477
  function defaultGooseDbPath() {
1442
1478
  const home = homedir2();
@@ -1599,29 +1635,101 @@ function probeCodeBuddy(ctx) {
1599
1635
  const dir = join3(ctx.env.CODEBUDDY_HOME ?? join3(ctx.home, ".codebuddy"), "projects");
1600
1636
  return existsSync3(dir) ? dir : null;
1601
1637
  }
1638
+ function codeBuddyIdeRoots(ctx) {
1639
+ const roots = [];
1640
+ if (platform() === "darwin") {
1641
+ roots.push(join3(ctx.home, "Library", "Application Support", "CodeBuddyExtension", "Data"));
1642
+ } else if (platform() === "win32") {
1643
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1644
+ roots.push(join3(appData, "CodeBuddyExtension", "Data"));
1645
+ } else {
1646
+ const config = ctx.env.XDG_CONFIG_HOME ?? join3(ctx.home, ".config");
1647
+ roots.push(join3(config, "CodeBuddyExtension", "Data"));
1648
+ }
1649
+ return unique(roots);
1650
+ }
1651
+ function probeCodeBuddyIde(ctx) {
1652
+ const override = envOverride("codebuddy-ide", ctx.env);
1653
+ if (override) return override;
1654
+ const legacy = ctx.legacySources?.["codebuddy-ide"];
1655
+ if (legacy) return legacy;
1656
+ for (const root of codeBuddyIdeRoots(ctx)) {
1657
+ if (existsSync3(root)) return root;
1658
+ }
1659
+ return null;
1660
+ }
1661
+ function countCodeBuddyIdeConversations(dir) {
1662
+ let count = 0;
1663
+ const walk = (root, depth) => {
1664
+ if (depth > 12) return;
1665
+ let entries;
1666
+ try {
1667
+ entries = readdirSync(root, { withFileTypes: true });
1668
+ } catch {
1669
+ return;
1670
+ }
1671
+ for (const entry of entries) {
1672
+ if (!entry.isDirectory()) continue;
1673
+ const full = join3(root, entry.name);
1674
+ if (entry.name === "messages") {
1675
+ try {
1676
+ if (readdirSync(full).some((f) => f.endsWith(".json"))) count++;
1677
+ } catch {
1678
+ }
1679
+ continue;
1680
+ }
1681
+ walk(full, depth + 1);
1682
+ }
1683
+ };
1684
+ walk(dir, 0);
1685
+ return count;
1686
+ }
1602
1687
  function probeKiro(ctx) {
1603
1688
  const override = envOverride("kiro", ctx.env);
1604
1689
  if (override) return override;
1605
1690
  const legacy = ctx.legacySources?.["kiro"];
1606
1691
  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
- );
1692
+ return probeKiroDefaultPath(ctx);
1693
+ }
1694
+ function probeKiroOverridePath(ctx) {
1695
+ const override = envOverride("kiro", ctx.env);
1696
+ if (override) return override;
1697
+ const legacy = ctx.legacySources?.["kiro"];
1698
+ if (legacy) return legacy;
1699
+ return null;
1700
+ }
1701
+ function probeKiroDefaultPath(ctx) {
1702
+ const devDataBase = kiroDevDataDir(ctx);
1703
+ const ideDb = join3(devDataBase, "devdata.sqlite");
1704
+ const tokensJsonl = join3(devDataBase, "tokens_generated.jsonl");
1618
1705
  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;
1706
+ 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");
1707
+ if (existsSync3(ideDb) && statSync(ideDb).size > 0) return ideDb;
1621
1708
  if (existsSync3(appSupport)) return appSupport;
1622
1709
  if (existsSync3(cliSessions)) return cliSessions;
1710
+ if (existsSync3(tokensJsonl)) return devDataBase;
1623
1711
  return null;
1624
1712
  }
1713
+ function kiroDevDataDir(ctx) {
1714
+ if (platform() === "darwin") {
1715
+ return join3(ctx.home, "Library", "Application Support", "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1716
+ }
1717
+ if (platform() === "win32") {
1718
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1719
+ return join3(appData, "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1720
+ }
1721
+ return join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "Kiro", "User", "globalStorage", "kiro.kiroagent", "dev_data");
1722
+ }
1723
+ function kiroWorkspaceSessionsDir(ctx) {
1724
+ if (platform() === "darwin") {
1725
+ return join3(ctx.home, "Library", "Application Support", "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1726
+ }
1727
+ if (platform() === "win32") {
1728
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1729
+ return join3(appData, "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1730
+ }
1731
+ return join3(ctx.env.XDG_DATA_HOME ?? join3(ctx.home, ".local", "share"), "Kiro", "User", "globalStorage", "kiro.kiroagent", "workspace-sessions");
1732
+ }
1625
1733
  function probeGrok(ctx) {
1626
1734
  const override = envOverride("grok", ctx.env);
1627
1735
  if (override) return override;
@@ -1725,6 +1833,27 @@ function probeDroid(ctx) {
1725
1833
  const dir = join3(ctx.home, ".droid", "sessions");
1726
1834
  return existsSync3(dir) ? dir : null;
1727
1835
  }
1836
+ function probeTrae(ctx) {
1837
+ const override = envOverride("trae", ctx.env);
1838
+ if (override) return override;
1839
+ const legacy = ctx.legacySources?.["trae"];
1840
+ if (legacy) return legacy;
1841
+ const appNames = platform() === "darwin" ? ["Trae CN", "TRAE SOLO CN", "Trae"] : platform() === "win32" ? ["Trae CN", "TRAE SOLO CN", "Trae"] : ["Trae CN", "TRAE SOLO CN", "Trae"];
1842
+ for (const appName of appNames) {
1843
+ let dbPath;
1844
+ if (platform() === "darwin") {
1845
+ dbPath = join3(ctx.home, "Library", "Application Support", appName, "User", "globalStorage", "state.vscdb");
1846
+ } else if (platform() === "win32") {
1847
+ const appData = ctx.env.APPDATA ?? join3(ctx.home, "AppData", "Roaming");
1848
+ dbPath = join3(appData, appName, "User", "globalStorage", "state.vscdb");
1849
+ } else {
1850
+ const config = ctx.env.XDG_CONFIG_HOME ?? join3(ctx.home, ".config");
1851
+ dbPath = join3(config, appName, "User", "globalStorage", "state.vscdb");
1852
+ }
1853
+ if (existsSync3(dbPath)) return dbPath;
1854
+ }
1855
+ return null;
1856
+ }
1728
1857
  function defaultQoderSessionDirs(home) {
1729
1858
  const windowsHomes = [
1730
1859
  process.env.USERPROFILE,
@@ -1762,6 +1891,7 @@ var TOOL_REGISTRY = [
1762
1891
  { tool: "gemini", sourceKey: "gemini", label: "Gemini CLI", probe: probeGemini },
1763
1892
  { tool: "kimi", sourceKey: "kimi", label: "Kimi Code", probe: probeKimi },
1764
1893
  { tool: "codebuddy", sourceKey: "codebuddy", label: "CodeBuddy", probe: probeCodeBuddy },
1894
+ { tool: "codebuddy", sourceKey: "codebuddy-ide", label: "CodeBuddy (IDE)", probe: probeCodeBuddyIde },
1765
1895
  { tool: "kiro", sourceKey: "kiro", label: "Kiro", probe: probeKiro },
1766
1896
  { tool: "grok", sourceKey: "grok", label: "Grok Build", probe: probeGrok },
1767
1897
  { tool: "antigravity", sourceKey: "antigravity", label: "Antigravity", probe: probeAntigravity },
@@ -1772,7 +1902,8 @@ var TOOL_REGISTRY = [
1772
1902
  { tool: "omp", sourceKey: "omp", label: "oh-my-pi", probe: probeOmp },
1773
1903
  { tool: "pi", sourceKey: "pi", label: "pi", probe: probePi },
1774
1904
  { tool: "craft", sourceKey: "craft", label: "Craft", probe: probeCraft },
1775
- { tool: "droid", sourceKey: "droid", label: "Droid", probe: probeDroid }
1905
+ { tool: "droid", sourceKey: "droid", label: "Droid", probe: probeDroid },
1906
+ { tool: "trae", sourceKey: "trae", label: "Trae", probe: probeTrae }
1776
1907
  ];
1777
1908
  function discoverTools(env = process.env) {
1778
1909
  const home = homedir2();
@@ -1790,7 +1921,9 @@ function discoverTools(env = process.env) {
1790
1921
  try {
1791
1922
  const stat2 = statSync(detectedPath);
1792
1923
  if (stat2.isDirectory()) {
1793
- if (entry.sourceKey === "roocode" || entry.sourceKey === "kilocode") {
1924
+ if (entry.sourceKey === "codebuddy-ide") {
1925
+ fileCount += countCodeBuddyIdeConversations(detectedPath);
1926
+ } else if (entry.sourceKey === "roocode" || entry.sourceKey === "kilocode") {
1794
1927
  fileCount += findJsonFiles(detectedPath).filter((p) => basename(p) === "ui_messages.json").length;
1795
1928
  } else if (entry.sourceKey === "kelivo") {
1796
1929
  fileCount += findJsonFiles(detectedPath).filter((p) => basename(p) === "chats.json").length + findZipFiles(detectedPath).length;
@@ -1889,7 +2022,9 @@ function discoverLogFiles(env = process.env) {
1889
2022
  { tool: "gemini", path: probeGemini(ctx) },
1890
2023
  { tool: "kimi", path: probeKimi(ctx), filter: (p) => basename(p) === "wire.jsonl" },
1891
2024
  { tool: "codebuddy", path: probeCodeBuddy(ctx) },
1892
- { tool: "kiro", path: probeKiro(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
2025
+ { tool: "kiro", path: kiroDevDataDir(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
2026
+ { tool: "kiro", path: probeKiroOverridePath(ctx), filter: (p) => extname(p) === ".jsonl" || extname(p) === ".json" },
2027
+ { tool: "kiro", path: kiroWorkspaceSessionsDir(ctx), filter: (p) => extname(p) === ".json" && basename(p) !== "sessions.json" },
1893
2028
  { tool: "grok", path: probeGrok(ctx), filter: (p) => extname(p) === ".jsonl" },
1894
2029
  { tool: "antigravity", path: probeAntigravity(ctx) },
1895
2030
  { tool: "omp", path: probeOmp(ctx) },
@@ -1918,6 +2053,49 @@ function discoverLogFiles(env = process.env) {
1918
2053
  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
2054
  if (paths.length > 0) results.push({ tool: "kelivo", paths: unique(paths) });
1920
2055
  }
2056
+ const cursorProjectDirs = cursorProjectRoots(ctx);
2057
+ for (const cursorHome of cursorProjectDirs) {
2058
+ if (!existsSync3(cursorHome)) continue;
2059
+ const transcriptPaths = findCursorTranscriptFiles(cursorHome);
2060
+ if (transcriptPaths.length > 0) {
2061
+ const existing = results.find((r) => r.tool === "cursor");
2062
+ if (existing) {
2063
+ existing.paths = unique([...existing.paths, ...transcriptPaths]);
2064
+ } else {
2065
+ results.push({ tool: "cursor", paths: transcriptPaths });
2066
+ }
2067
+ }
2068
+ }
2069
+ return results;
2070
+ }
2071
+ function cursorProjectRoots(ctx) {
2072
+ const roots = [join3(ctx.home, ".cursor", "projects")];
2073
+ if (platform() === "win32") {
2074
+ const userProfile = ctx.env.USERPROFILE ?? ctx.home;
2075
+ roots.push(join3(userProfile, ".cursor", "projects"));
2076
+ }
2077
+ return unique(roots);
2078
+ }
2079
+ function findCursorTranscriptFiles(dir) {
2080
+ const results = [];
2081
+ try {
2082
+ const entries = readdirSync(dir, { withFileTypes: true });
2083
+ for (const entry of entries) {
2084
+ if (!entry.isDirectory()) continue;
2085
+ const agentDir = join3(dir, entry.name, "agent-transcripts");
2086
+ if (!existsSync3(agentDir)) continue;
2087
+ try {
2088
+ const sessions = readdirSync(agentDir, { withFileTypes: true });
2089
+ for (const session of sessions) {
2090
+ if (!session.isDirectory()) continue;
2091
+ const jsonlPath = join3(agentDir, session.name, `${session.name}.jsonl`);
2092
+ if (existsSync3(jsonlPath)) results.push(jsonlPath);
2093
+ }
2094
+ } catch {
2095
+ }
2096
+ }
2097
+ } catch {
2098
+ }
1921
2099
  return results;
1922
2100
  }
1923
2101
  function findJsonFiles(dir) {
@@ -3082,6 +3260,17 @@ function migrateV11(db) {
3082
3260
  `);
3083
3261
  }
3084
3262
 
3263
+ // src/db/migrations/v12.ts
3264
+ function migrateV12(db) {
3265
+ db.prepare(`
3266
+ UPDATE records
3267
+ SET updated_at = ?
3268
+ WHERE source_file NOT LIKE 'synced/%'
3269
+ AND (cwd != '' OR source_file LIKE '%:session:%')
3270
+ `).run(Date.now());
3271
+ db.prepare("INSERT INTO schema_version (version) VALUES (12)").run();
3272
+ }
3273
+
3085
3274
  // src/db/migrations/index.ts
3086
3275
  var MIGRATIONS = [
3087
3276
  { version: 1, migrate: migrateV1 },
@@ -3094,7 +3283,8 @@ var MIGRATIONS = [
3094
3283
  { version: 8, migrate: migrateV8 },
3095
3284
  { version: 9, migrate: migrateV9 },
3096
3285
  { version: 10, migrate: migrateV10 },
3097
- { version: 11, migrate: migrateV11 }
3286
+ { version: 11, migrate: migrateV11 },
3287
+ { version: 12, migrate: migrateV12 }
3098
3288
  ];
3099
3289
  function runMigrations(db) {
3100
3290
  createSchemaVersionTable(db);
@@ -3201,6 +3391,21 @@ function loadPricingRuntime(db, config) {
3201
3391
  for (const [model, entry] of Object.entries(configOverrides)) overrides[model] = entry;
3202
3392
  setRuntimePriceTable(builtin, overrides);
3203
3393
  }
3394
+ function importConfigPriceOverrides(db, overrides) {
3395
+ const imported = [];
3396
+ for (const [modelKey, entry] of Object.entries(overrides)) {
3397
+ if (!modelKey.trim()) continue;
3398
+ const existingUserPrice = db.prepare(`
3399
+ SELECT 1 FROM model_prices
3400
+ WHERE model_key = ? AND origin = 'user' AND status = 'active'
3401
+ LIMIT 1
3402
+ `).get(modelKey);
3403
+ if (existingUserPrice) continue;
3404
+ setUserPrice(db, modelKey, entry);
3405
+ imported.push(modelKey);
3406
+ }
3407
+ return imported;
3408
+ }
3204
3409
  function setUserPrice(db, modelKey, entry) {
3205
3410
  const now = Date.now();
3206
3411
  const baseline = db.prepare(`
@@ -3455,6 +3660,14 @@ function priceTableFromDb(db) {
3455
3660
  for (const row of rows) table[row.model_key] = rowToPrice(row);
3456
3661
  return table;
3457
3662
  }
3663
+ function hasUserPrice(db, model) {
3664
+ const row = db.prepare(`
3665
+ SELECT 1 FROM model_prices
3666
+ WHERE model_key = ? AND origin = 'user' AND status = 'active'
3667
+ LIMIT 1
3668
+ `).get(model);
3669
+ return Boolean(row);
3670
+ }
3458
3671
  function resolvePriceFromRegistry(db, model) {
3459
3672
  const directUserPrice = db.prepare(`
3460
3673
  SELECT input, output, cache_read, cache_write, currency
@@ -3914,10 +4127,10 @@ function getClientPlatform() {
3914
4127
  }
3915
4128
  function getCliVersion() {
3916
4129
  try {
3917
- const { readFileSync: readFileSync12 } = __require("fs");
3918
- const { join: join16 } = __require("path");
3919
- const pkgPath = join16(__dirname, "../../package.json");
3920
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
4130
+ const { readFileSync: readFileSync14 } = __require("fs");
4131
+ const { join: join18 } = __require("path");
4132
+ const pkgPath = join18(__dirname, "../../package.json");
4133
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
3921
4134
  return pkg.version || "0.0.0";
3922
4135
  } catch {
3923
4136
  return "0.0.0";
@@ -4529,12 +4742,12 @@ async function recalcCosts(db, onProgress) {
4529
4742
  const tx = db.transaction((batch) => {
4530
4743
  for (const r of batch) {
4531
4744
  processed++;
4532
- if (r.cost_source === "log") {
4745
+ const rawModel = r.tool === "qoder" ? normalizeQoderModel(r.model) : r.model;
4746
+ const model = rawModel === "unknown" ? r.tool === "qoder" ? "qoder-auto" : r.model : rawModel;
4747
+ if (r.cost_source === "log" && r.cost > 0 && !hasUserPrice(db, model)) {
4533
4748
  skipped++;
4534
4749
  continue;
4535
4750
  }
4536
- const rawModel = r.tool === "qoder" ? normalizeQoderModel(r.model) : r.model;
4537
- const model = rawModel === "unknown" ? r.tool === "qoder" ? "qoder-auto" : r.model : rawModel;
4538
4751
  const provider = model !== r.model ? inferProvider(model) : r.provider;
4539
4752
  const price = resolveCachedPrice(model);
4540
4753
  const cost = price ? calculateCostForPrice(price, {
@@ -6128,8 +6341,8 @@ function createApiServer(db, options) {
6128
6341
 
6129
6342
  // src/commands/parse.ts
6130
6343
  import Database2 from "better-sqlite3";
6131
- import { readFileSync as readFileSync8, statSync as statSync2, existsSync as existsSync8, openSync, readSync, closeSync } from "fs";
6132
- import { join as join7 } from "path";
6344
+ import { readFileSync as readFileSync10, statSync as statSync3, existsSync as existsSync10, openSync, readSync, closeSync } from "fs";
6345
+ import { join as join9, dirname as dirname5 } from "path";
6133
6346
  import { hostname as hostname2 } from "os";
6134
6347
 
6135
6348
  // src/watermark.ts
@@ -6159,7 +6372,8 @@ function defaultFileData() {
6159
6372
  "pi": {},
6160
6373
  "craft": {},
6161
6374
  "droid": {},
6162
- "zcode": {}
6375
+ "zcode": {},
6376
+ "trae": {}
6163
6377
  };
6164
6378
  }
6165
6379
  var WatermarkManager = class {
@@ -6179,7 +6393,7 @@ var WatermarkManager = class {
6179
6393
  if (parsed && typeof parsed === "object" && !("files" in parsed)) {
6180
6394
  return { files: { ...defaultFileData(), ...parsed } };
6181
6395
  }
6182
- 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 };
6396
+ 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, codebuddyIde: parsed.codebuddyIde ?? null };
6183
6397
  } catch {
6184
6398
  return { files: defaultFileData() };
6185
6399
  }
@@ -6260,6 +6474,18 @@ var WatermarkManager = class {
6260
6474
  setZcodeToolCursor(cursor) {
6261
6475
  this.data.zcodeTools = cursor;
6262
6476
  }
6477
+ getTraeLastImported() {
6478
+ return this.data.trae ?? 0;
6479
+ }
6480
+ setTraeLastImported(ts3) {
6481
+ this.data.trae = ts3;
6482
+ }
6483
+ getCodeBuddyIdeCursor() {
6484
+ return this.data.codebuddyIde ?? 0;
6485
+ }
6486
+ setCodeBuddyIdeCursor(ts3) {
6487
+ this.data.codebuddyIde = ts3;
6488
+ }
6263
6489
  };
6264
6490
 
6265
6491
  // src/commands/parse-opencode.ts
@@ -6554,6 +6780,42 @@ function runParseQoder(db, options) {
6554
6780
  }
6555
6781
 
6556
6782
  // src/commands/parse-cursor.ts
6783
+ import { readFileSync as readFileSync8 } from "fs";
6784
+ function runParseCursorTranscript(options) {
6785
+ const charsPerToken = 3;
6786
+ try {
6787
+ const content = readFileSync8(options.jsonlPath, "utf-8");
6788
+ const lines = content.split("\n");
6789
+ let userChars = 0;
6790
+ let assistChars = 0;
6791
+ for (const line of lines) {
6792
+ if (!line.trim()) continue;
6793
+ let data;
6794
+ try {
6795
+ data = JSON.parse(line);
6796
+ } catch {
6797
+ continue;
6798
+ }
6799
+ const role = data?.role ?? "";
6800
+ const blocks = data?.message?.content ?? [];
6801
+ if (!Array.isArray(blocks)) continue;
6802
+ for (const block of blocks) {
6803
+ if (typeof block !== "object" || block === null || block.type !== "text") continue;
6804
+ const text = block.text ?? "";
6805
+ const clean = text.replace(/\[REDACTED\]/g, "").trim();
6806
+ if (!clean) continue;
6807
+ if (role === "user") userChars += clean.length;
6808
+ else if (role === "assistant") assistChars += clean.length;
6809
+ }
6810
+ }
6811
+ return {
6812
+ inputTextChars: Math.floor(userChars / charsPerToken),
6813
+ outputTextChars: Math.floor(assistChars / charsPerToken)
6814
+ };
6815
+ } catch {
6816
+ return { inputTextChars: 0, outputTextChars: 0 };
6817
+ }
6818
+ }
6557
6819
  function runParseCursor(db, options) {
6558
6820
  const { dbPath, device, deviceInstanceId, platform: platform5, now, cursor } = options;
6559
6821
  const records = [];
@@ -7137,6 +7399,306 @@ function runParseZcode(db, options) {
7137
7399
  };
7138
7400
  }
7139
7401
 
7402
+ // src/commands/parse-trae.ts
7403
+ import { existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
7404
+ import { join as join7, basename as basename3 } from "path";
7405
+ import { spawnSync } from "child_process";
7406
+ function deriveDataDir(dbPath) {
7407
+ const normalized = dbPath.replace(/\\/g, "/");
7408
+ const appDir = normalized.replace(/\/User\/globalStorage\/state\.vscdb$/, "");
7409
+ const dataDir = join7(appDir, "ModularData", "ai-agent", "snapshot");
7410
+ return existsSync8(dataDir) ? dataDir : null;
7411
+ }
7412
+ function parseSessionTags(repoPath) {
7413
+ try {
7414
+ const sessionId = basename3(basename3(repoPath) === "v2" ? join7(repoPath, "..") : repoPath);
7415
+ if (!/^[0-9a-f]{24,}$/.test(sessionId)) return null;
7416
+ const tagResult = spawnSync("git", ["tag", "-l"], { cwd: repoPath, encoding: "utf-8", timeout: 5e3 });
7417
+ if (tagResult.status !== 0 || !tagResult.stdout?.trim()) return null;
7418
+ const tags = tagResult.stdout;
7419
+ const lines = tags.trim().split("\n");
7420
+ let chatTurns = 0;
7421
+ let toolCalls = 0;
7422
+ let startTs = 0;
7423
+ for (const tag of lines) {
7424
+ const t = tag.trim();
7425
+ if (!t) continue;
7426
+ let tagTs = 0;
7427
+ try {
7428
+ const logResult = spawnSync("git", ["log", "-1", "--format=%at", t], {
7429
+ cwd: repoPath,
7430
+ encoding: "utf-8",
7431
+ timeout: 3e3
7432
+ });
7433
+ if (logResult.status !== 0 || !logResult.stdout?.trim()) continue;
7434
+ const tsStr = logResult.stdout.trim();
7435
+ tagTs = parseInt(tsStr, 10) * 1e3;
7436
+ } catch {
7437
+ continue;
7438
+ }
7439
+ if (t.startsWith("chain-start-")) {
7440
+ startTs = tagTs;
7441
+ } else if (t.startsWith("before-chat-turn-")) {
7442
+ chatTurns++;
7443
+ } else if (t.startsWith("toolcall-")) {
7444
+ toolCalls++;
7445
+ }
7446
+ }
7447
+ if (startTs === 0) {
7448
+ let earliest = Infinity;
7449
+ for (const tag of lines) {
7450
+ const t = tag.trim();
7451
+ if (!t) continue;
7452
+ const logResult = spawnSync("git", ["log", "-1", "--format=%at", t], {
7453
+ cwd: repoPath,
7454
+ encoding: "utf-8",
7455
+ timeout: 3e3
7456
+ });
7457
+ if (logResult.status !== 0 || !logResult.stdout?.trim()) continue;
7458
+ const ts3 = parseInt(logResult.stdout.trim(), 10) * 1e3;
7459
+ if (ts3 > 0 && ts3 < earliest) earliest = ts3;
7460
+ }
7461
+ if (earliest < Infinity) startTs = earliest;
7462
+ }
7463
+ if (chatTurns === 0 && toolCalls === 0) return null;
7464
+ if (startTs === 0) return null;
7465
+ return { sessionId, startTs, chatTurns, toolCalls };
7466
+ } catch {
7467
+ return null;
7468
+ }
7469
+ }
7470
+ function runParseTrae(options) {
7471
+ const { dbPath, device, deviceInstanceId, platform: platform5, now, lastImportedAt } = options;
7472
+ const records = [];
7473
+ const toolCalls = [];
7474
+ const errors = [];
7475
+ let latestTs = lastImportedAt ?? 0;
7476
+ const dataDir = deriveDataDir(dbPath);
7477
+ if (!dataDir) {
7478
+ return { records, toolCalls, lastImportedAt: latestTs, errors: ["Trae ModularData not found"] };
7479
+ }
7480
+ let entries;
7481
+ try {
7482
+ entries = readdirSync2(dataDir, { withFileTypes: true });
7483
+ } catch (e) {
7484
+ return { records, toolCalls, lastImportedAt: latestTs, errors: [String(e)] };
7485
+ }
7486
+ for (const entry of entries) {
7487
+ if (!entry.isDirectory()) continue;
7488
+ const repoPath = join7(dataDir, entry.name, "v2");
7489
+ if (!existsSync8(join7(repoPath, ".git"))) continue;
7490
+ const session = parseSessionTags(repoPath);
7491
+ if (!session) continue;
7492
+ if (session.startTs < latestTs) continue;
7493
+ if (session.startTs > latestTs) latestTs = session.startTs;
7494
+ const estInputTokens = session.chatTurns * 200;
7495
+ const estOutputTokens = session.chatTurns * 800;
7496
+ const recordId = generateRecordId(deviceInstanceId, `${dbPath}:${session.sessionId}`, session.startTs);
7497
+ const record = {
7498
+ id: recordId,
7499
+ ts: session.startTs,
7500
+ ingestedAt: now,
7501
+ updatedAt: now,
7502
+ lineOffset: 0,
7503
+ tool: "trae",
7504
+ model: "trae-agent",
7505
+ provider: "trae",
7506
+ inputTokens: estInputTokens,
7507
+ outputTokens: estOutputTokens,
7508
+ cacheReadTokens: 0,
7509
+ cacheWriteTokens: 0,
7510
+ thinkingTokens: 0,
7511
+ cost: 0,
7512
+ costSource: "unknown",
7513
+ sessionId: session.sessionId,
7514
+ sourceFile: dbPath,
7515
+ device,
7516
+ deviceInstanceId,
7517
+ platform: platform5
7518
+ };
7519
+ records.push(record);
7520
+ let callIndex = 0;
7521
+ for (let i = 0; i < session.toolCalls; i++) {
7522
+ toolCalls.push({
7523
+ id: generateRecordId(recordId, "tool_call", session.startTs + callIndex),
7524
+ recordId,
7525
+ name: "trae-tool",
7526
+ ts: session.startTs + callIndex,
7527
+ callIndex
7528
+ });
7529
+ callIndex++;
7530
+ }
7531
+ }
7532
+ return { records, toolCalls, lastImportedAt: latestTs, errors };
7533
+ }
7534
+
7535
+ // src/commands/parse-codebuddy.ts
7536
+ import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
7537
+ import { join as join8, basename as basename4, dirname as dirname4 } from "path";
7538
+ function num3(value) {
7539
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
7540
+ }
7541
+ function toMs(iso) {
7542
+ if (typeof iso !== "string") return 0;
7543
+ const ms = Date.parse(iso);
7544
+ return Number.isFinite(ms) ? ms : 0;
7545
+ }
7546
+ var WORKSPACE_FOLDER_RE = /Workspace Folder:\s*(.+)/;
7547
+ function userMessageText(raw) {
7548
+ try {
7549
+ const inner = JSON.parse(raw);
7550
+ if (Array.isArray(inner.content)) {
7551
+ return inner.content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
7552
+ }
7553
+ } catch {
7554
+ }
7555
+ return raw;
7556
+ }
7557
+ function findMessagesDirs(root, depth = 0) {
7558
+ if (depth > 12) return [];
7559
+ let entries;
7560
+ try {
7561
+ entries = readdirSync3(root, { withFileTypes: true });
7562
+ } catch {
7563
+ return [];
7564
+ }
7565
+ const out = [];
7566
+ for (const entry of entries) {
7567
+ if (!entry.isDirectory()) continue;
7568
+ const full = join8(root, entry.name);
7569
+ if (entry.name === "messages") {
7570
+ out.push(full);
7571
+ continue;
7572
+ }
7573
+ out.push(...findMessagesDirs(full, depth + 1));
7574
+ }
7575
+ return out;
7576
+ }
7577
+ function readConversation(messagesDir) {
7578
+ let files;
7579
+ try {
7580
+ files = readdirSync3(messagesDir).filter((f) => f.endsWith(".json"));
7581
+ } catch {
7582
+ return null;
7583
+ }
7584
+ if (files.length === 0) return null;
7585
+ const parsed = [];
7586
+ for (const file of files) {
7587
+ try {
7588
+ const data = JSON.parse(readFileSync9(join8(messagesDir, file), "utf-8"));
7589
+ if (data && typeof data === "object") parsed.push(data);
7590
+ } catch {
7591
+ }
7592
+ }
7593
+ if (parsed.length === 0) return null;
7594
+ let cwd;
7595
+ for (const msg of parsed) {
7596
+ if (msg.role !== "user" || typeof msg.message !== "string") continue;
7597
+ const match = WORKSPACE_FOLDER_RE.exec(userMessageText(msg.message));
7598
+ if (match) {
7599
+ cwd = match[1].trim();
7600
+ break;
7601
+ }
7602
+ }
7603
+ let latest = null;
7604
+ for (const msg of parsed) {
7605
+ if (msg.role !== "assistant" || typeof msg.extra !== "string") continue;
7606
+ let extra2;
7607
+ try {
7608
+ extra2 = JSON.parse(msg.extra);
7609
+ } catch {
7610
+ continue;
7611
+ }
7612
+ const hasStats = extra2.statsSnapshot != null || extra2.lastStepInputTokens != null;
7613
+ if (!hasStats) continue;
7614
+ const ts3 = toMs(msg.createdAt);
7615
+ if (!latest || ts3 >= latest.ts) latest = { ts: ts3, extra: extra2 };
7616
+ }
7617
+ if (!latest) return null;
7618
+ const extra = latest.extra;
7619
+ const snapshot = extra.statsSnapshot ?? {};
7620
+ const cacheReadTokens = num3(snapshot.cachedInputTokens ?? extra.lastStepCachedInputTokens);
7621
+ const inputTokens = snapshot.cacheMissTokens != null ? num3(snapshot.cacheMissTokens) : Math.max(0, num3(extra.lastStepInputTokens) - cacheReadTokens);
7622
+ const outputTokens = num3(snapshot.lastOutputTokens ?? extra.lastStepOutputTokens);
7623
+ const cacheWriteTokens = num3(snapshot.cacheWriteTokens);
7624
+ const thinkingTokens = num3(snapshot.thinkingTokens);
7625
+ const model = typeof extra.modelId === "string" && extra.modelId.trim() ? extra.modelId.trim() : "unknown";
7626
+ return {
7627
+ ts: latest.ts,
7628
+ model,
7629
+ inputTokens,
7630
+ outputTokens,
7631
+ cacheReadTokens,
7632
+ cacheWriteTokens,
7633
+ thinkingTokens,
7634
+ cwd
7635
+ };
7636
+ }
7637
+ function runParseCodeBuddy(options) {
7638
+ const { dataDir, device, deviceInstanceId, platform: platform5, now, cursor, exchangeRate } = options;
7639
+ const records = [];
7640
+ const errors = [];
7641
+ let nextCursor = cursor ?? 0;
7642
+ if (!existsSync9(dataDir)) {
7643
+ return { records, nextCursor, errors };
7644
+ }
7645
+ let messagesDirs;
7646
+ try {
7647
+ messagesDirs = findMessagesDirs(dataDir);
7648
+ } catch (e) {
7649
+ return { records, nextCursor, errors: [String(e)] };
7650
+ }
7651
+ for (const messagesDir of messagesDirs) {
7652
+ const conversationDir = dirname4(messagesDir);
7653
+ const sessionId = basename4(conversationDir);
7654
+ let usage;
7655
+ try {
7656
+ usage = readConversation(messagesDir);
7657
+ } catch (e) {
7658
+ errors.push(`${messagesDir}: ${e instanceof Error ? e.message : e}`);
7659
+ continue;
7660
+ }
7661
+ if (!usage) continue;
7662
+ if (usage.inputTokens + usage.outputTokens + usage.cacheReadTokens === 0) continue;
7663
+ if (cursor && usage.ts <= cursor) continue;
7664
+ if (usage.ts > nextCursor) nextCursor = usage.ts;
7665
+ const provider = inferProvider(usage.model);
7666
+ const tokenArgs = {
7667
+ inputTokens: usage.inputTokens,
7668
+ outputTokens: usage.outputTokens,
7669
+ cacheReadTokens: usage.cacheReadTokens,
7670
+ cacheWriteTokens: usage.cacheWriteTokens,
7671
+ thinkingTokens: usage.thinkingTokens
7672
+ };
7673
+ const cost = calculateCost(usage.model, tokenArgs, exchangeRate);
7674
+ const recordId = generateRecordId(deviceInstanceId, conversationDir, usage.ts);
7675
+ records.push({
7676
+ id: recordId,
7677
+ ts: usage.ts,
7678
+ ingestedAt: now,
7679
+ updatedAt: now,
7680
+ lineOffset: 0,
7681
+ tool: "codebuddy",
7682
+ model: usage.model,
7683
+ provider,
7684
+ inputTokens: usage.inputTokens,
7685
+ outputTokens: usage.outputTokens,
7686
+ cacheReadTokens: usage.cacheReadTokens,
7687
+ cacheWriteTokens: usage.cacheWriteTokens,
7688
+ thinkingTokens: usage.thinkingTokens,
7689
+ cost,
7690
+ costSource: cost > 0 ? "pricing" : "unknown",
7691
+ sessionId,
7692
+ sourceFile: conversationDir,
7693
+ cwd: usage.cwd,
7694
+ device,
7695
+ deviceInstanceId,
7696
+ platform: platform5
7697
+ });
7698
+ }
7699
+ return { records, nextCursor, errors };
7700
+ }
7701
+
7140
7702
  // src/commands/parse.ts
7141
7703
  function extractCwdFromJson(data) {
7142
7704
  if (typeof data.cwd === "string" && data.cwd) return data.cwd;
@@ -7180,6 +7742,7 @@ function extractSessionId2(filePath, tool) {
7180
7742
  }
7181
7743
  return "unknown";
7182
7744
  }
7745
+ var CHARS_PER_TOKEN2 = 4;
7183
7746
  function toNumber2(value) {
7184
7747
  const n = Number(value);
7185
7748
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
@@ -7190,7 +7753,7 @@ function parseUiMessagesFile(options) {
7190
7753
  const errors = [];
7191
7754
  let messages;
7192
7755
  try {
7193
- const parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
7756
+ const parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
7194
7757
  messages = Array.isArray(parsed) ? parsed : [];
7195
7758
  } catch (e) {
7196
7759
  return { records, errors: [`${filePath}: ${e instanceof Error ? e.message : e}`] };
@@ -7257,7 +7820,7 @@ function parseTimestamp2(value, fallback) {
7257
7820
  }
7258
7821
  function pathIsFile(filePath) {
7259
7822
  try {
7260
- return statSync2(filePath).isFile();
7823
+ return statSync3(filePath).isFile();
7261
7824
  } catch {
7262
7825
  return false;
7263
7826
  }
@@ -7268,10 +7831,13 @@ function parseKiroSessionFile(options) {
7268
7831
  const errors = [];
7269
7832
  let parsed;
7270
7833
  try {
7271
- parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
7834
+ parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
7272
7835
  } catch (e) {
7273
7836
  return { records, errors: [`${filePath}: ${e instanceof Error ? e.message : e}`] };
7274
7837
  }
7838
+ if (Array.isArray(parsed?.history) && !parsed?.session_state) {
7839
+ return parseKiroWorkspaceSession({ filePath, parsed, device, deviceInstanceId, platform: platform5, now, exchangeRate });
7840
+ }
7275
7841
  const turns = parsed?.session_state?.conversation_metadata?.user_turn_metadatas;
7276
7842
  if (!Array.isArray(turns)) return { records, errors };
7277
7843
  const sessionId = typeof parsed?.session_id === "string" && parsed.session_id ? parsed.session_id : extractSessionId2(filePath, "kiro");
@@ -7313,6 +7879,83 @@ function parseKiroSessionFile(options) {
7313
7879
  }
7314
7880
  return { records, errors };
7315
7881
  }
7882
+ function parseKiroWorkspaceSession(options) {
7883
+ const { filePath, parsed, device, deviceInstanceId, platform: platform5, now, exchangeRate } = options;
7884
+ const records = [];
7885
+ const errors = [];
7886
+ const sessionId = parsed?.sessionId ?? extractSessionId2(filePath, "kiro");
7887
+ let sessionTs = now;
7888
+ try {
7889
+ const dir = dirname5(filePath);
7890
+ const sessionsJson = readFileSync10(join9(dir, "sessions.json"), "utf-8");
7891
+ const sessions = JSON.parse(sessionsJson);
7892
+ const match = Array.isArray(sessions) && sessions.find((s) => s.sessionId === sessionId);
7893
+ if (match?.dateCreated) {
7894
+ const dc = Number(match.dateCreated);
7895
+ if (Number.isFinite(dc) && dc > 0) sessionTs = dc < 1e12 ? dc * 1e3 : dc;
7896
+ }
7897
+ } catch {
7898
+ }
7899
+ const allLogs = [];
7900
+ let logIndex = 0;
7901
+ for (const entry of parsed.history ?? []) {
7902
+ for (const pl of entry?.promptLogs ?? []) {
7903
+ allLogs.push({ log: pl, index: logIndex++ });
7904
+ }
7905
+ }
7906
+ const contextLength = parsed?.config?.models?.[0]?.contextLength ?? 4e4;
7907
+ const maxTokens = parsed?.config?.models?.[0]?.completionOptions?.maxTokens ?? 4e3;
7908
+ const contextPct = Number(parsed?.contextUsagePercentageBySession?.[sessionId] ?? parsed?.contextUsagePercentage ?? 0) / 100;
7909
+ const totalInputEstimate = contextPct > 0 ? Math.floor(contextLength * contextPct) : 0;
7910
+ const hasNoTokenData = allLogs.every(({ log }) => {
7911
+ const pl = typeof log.prompt === "string" ? log.prompt.length : 0;
7912
+ const cl = typeof log.completion === "string" ? log.completion.length : 0;
7913
+ return pl < 100 && cl === 0;
7914
+ });
7915
+ for (const { log, index } of allLogs) {
7916
+ const promptLen = typeof log.prompt === "string" ? log.prompt.length : 0;
7917
+ const completionLen = typeof log.completion === "string" ? log.completion.length : 0;
7918
+ let inputTokens;
7919
+ let outputTokens;
7920
+ if (hasNoTokenData) {
7921
+ inputTokens = totalInputEstimate > 0 && allLogs.length > 0 ? Math.max(1, Math.floor(totalInputEstimate / allLogs.length)) : 200;
7922
+ outputTokens = Math.floor(maxTokens * 0.3);
7923
+ } else {
7924
+ inputTokens = totalInputEstimate > 0 && allLogs.length > 0 ? Math.max(1, Math.floor(totalInputEstimate / allLogs.length)) : Math.max(1, Math.floor(promptLen / CHARS_PER_TOKEN2));
7925
+ outputTokens = completionLen > 0 ? Math.floor(completionLen / CHARS_PER_TOKEN2) : Math.floor(maxTokens * 0.3);
7926
+ }
7927
+ if (inputTokens + outputTokens === 0) continue;
7928
+ const model = normalizeKiroModel(log.completionOptions?.model ?? log.modelTitle);
7929
+ const provider = typeof log.provider === "string" && log.provider.trim() ? log.provider.trim().toLowerCase() : inferProvider(model);
7930
+ const recordTs = sessionTs + index;
7931
+ const tokenArgs = { inputTokens, outputTokens, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
7932
+ const cost = calculateCost(model, tokenArgs, exchangeRate);
7933
+ const sourceId = `${sessionId}:${index}`;
7934
+ records.push({
7935
+ id: generateRecordId(deviceInstanceId, `${filePath}:${sourceId}`, recordTs),
7936
+ ts: recordTs,
7937
+ ingestedAt: now,
7938
+ updatedAt: now,
7939
+ lineOffset: index,
7940
+ tool: "kiro",
7941
+ model,
7942
+ provider,
7943
+ inputTokens,
7944
+ outputTokens,
7945
+ cacheReadTokens: 0,
7946
+ cacheWriteTokens: 0,
7947
+ thinkingTokens: 0,
7948
+ cost,
7949
+ costSource: cost > 0 ? "pricing" : "unknown",
7950
+ sessionId,
7951
+ sourceFile: filePath,
7952
+ device,
7953
+ deviceInstanceId,
7954
+ platform: platform5
7955
+ });
7956
+ }
7957
+ return { records, errors };
7958
+ }
7316
7959
  async function runParse(db, filterTool, options) {
7317
7960
  const state = getState(AIUSAGE_DIR);
7318
7961
  const config = loadConfig();
@@ -7320,7 +7963,7 @@ async function runParse(db, filterTool, options) {
7320
7963
  const device = config?.device || hostname2() || state?.deviceInstanceId?.slice(0, 8) || "unknown";
7321
7964
  const deviceInstanceId = state?.deviceInstanceId ?? "unknown";
7322
7965
  const devicePlatform = config?.platform;
7323
- const watermarkPath = join7(AIUSAGE_DIR, "watermark.json");
7966
+ const watermarkPath = join9(AIUSAGE_DIR, "watermark.json");
7324
7967
  const wm = new WatermarkManager(watermarkPath);
7325
7968
  const toolPaths = discoverLogFiles();
7326
7969
  const aggregator = new Aggregator();
@@ -7336,7 +7979,7 @@ async function runParse(db, filterTool, options) {
7336
7979
  for (const filePath of paths) {
7337
7980
  toolIndex++;
7338
7981
  try {
7339
- const stat2 = statSync2(filePath);
7982
+ const stat2 = statSync3(filePath);
7340
7983
  const entry = wm.getEntry(tool, filePath);
7341
7984
  const offset = entry?.offset ?? 0;
7342
7985
  if (offset >= stat2.size) {
@@ -7390,6 +8033,66 @@ async function runParse(db, filterTool, options) {
7390
8033
  onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7391
8034
  continue;
7392
8035
  }
8036
+ if (tool === "kiro" && filePath.endsWith(".jsonl")) {
8037
+ const content2 = readFileSync10(filePath, "utf-8");
8038
+ const lines2 = content2.split("\n");
8039
+ let byteOffset2 = 0;
8040
+ for (const line of lines2) {
8041
+ if (!line.trim()) {
8042
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
8043
+ continue;
8044
+ }
8045
+ try {
8046
+ const data = JSON.parse(line);
8047
+ const inputTokens = toNumber2(data.promptTokens ?? data.inputTokens ?? data.tokensIn);
8048
+ const outputTokens = toNumber2(data.generatedTokens ?? data.outputTokens ?? data.tokensOut);
8049
+ if (inputTokens + outputTokens === 0) {
8050
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
8051
+ continue;
8052
+ }
8053
+ const model = normalizeKiroModel(data.model);
8054
+ const provider = typeof data.provider === "string" && data.provider.trim() ? data.provider.trim().toLowerCase() : inferProvider(model);
8055
+ const recordTs = stat2.mtimeMs;
8056
+ const tokenArgs = { inputTokens, outputTokens, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
8057
+ const cost = calculateCost(model, tokenArgs, exchangeRate);
8058
+ const recordId = generateRecordId(deviceInstanceId, `${filePath}:${byteOffset2}`, recordTs);
8059
+ const record = {
8060
+ id: recordId,
8061
+ ts: recordTs,
8062
+ ingestedAt: Date.now(),
8063
+ updatedAt: Date.now(),
8064
+ lineOffset: byteOffset2,
8065
+ tool: "kiro",
8066
+ model,
8067
+ provider,
8068
+ inputTokens,
8069
+ outputTokens,
8070
+ cacheReadTokens: 0,
8071
+ cacheWriteTokens: 0,
8072
+ thinkingTokens: 0,
8073
+ cost,
8074
+ costSource: cost > 0 ? "pricing" : "unknown",
8075
+ sessionId: filePath,
8076
+ sourceFile: filePath,
8077
+ device,
8078
+ deviceInstanceId,
8079
+ platform: devicePlatform
8080
+ };
8081
+ insertRecord(db, record);
8082
+ parsedCount++;
8083
+ } catch {
8084
+ }
8085
+ byteOffset2 += Buffer.byteLength(line, "utf-8") + 1;
8086
+ }
8087
+ wm.setEntry(tool, filePath, {
8088
+ offset: stat2.size,
8089
+ size: stat2.size,
8090
+ mtime: stat2.mtimeMs
8091
+ });
8092
+ wm.save();
8093
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
8094
+ continue;
8095
+ }
7393
8096
  if (tool === "kelivo" && (filePath.endsWith("chats.json") || filePath.endsWith(".zip"))) {
7394
8097
  const result = await runParseKelivo({
7395
8098
  filePath,
@@ -7413,7 +8116,55 @@ async function runParse(db, filterTool, options) {
7413
8116
  onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
7414
8117
  continue;
7415
8118
  }
7416
- const content = readFileSync8(filePath, "utf-8");
8119
+ if (tool === "cursor" && filePath.endsWith(".jsonl") && filePath.includes("agent-transcripts")) {
8120
+ const est = runParseCursorTranscript({
8121
+ jsonlPath: filePath,
8122
+ device,
8123
+ deviceInstanceId,
8124
+ platform: devicePlatform,
8125
+ now: Date.now()
8126
+ });
8127
+ if (est.inputTextChars + est.outputTextChars > 0) {
8128
+ const sessionId2 = filePath.split("/").slice(-2, -1)[0] || "unknown";
8129
+ const recordTs = stat2.mtimeMs;
8130
+ const tokenArgs = { inputTokens: est.inputTextChars, outputTokens: est.outputTextChars, cacheReadTokens: 0, cacheWriteTokens: 0, thinkingTokens: 0 };
8131
+ const cost = calculateCost("cursor-composer", tokenArgs, exchangeRate);
8132
+ const recordId = generateRecordId(deviceInstanceId, filePath, recordTs);
8133
+ const record = {
8134
+ id: recordId,
8135
+ ts: recordTs,
8136
+ ingestedAt: Date.now(),
8137
+ updatedAt: Date.now(),
8138
+ lineOffset: 0,
8139
+ tool: "cursor",
8140
+ model: "cursor-composer",
8141
+ provider: "cursor",
8142
+ inputTokens: est.inputTextChars,
8143
+ outputTokens: est.outputTextChars,
8144
+ cacheReadTokens: 0,
8145
+ cacheWriteTokens: 0,
8146
+ thinkingTokens: 0,
8147
+ cost,
8148
+ costSource: cost > 0 ? "pricing" : "unknown",
8149
+ sessionId: sessionId2,
8150
+ sourceFile: filePath,
8151
+ device,
8152
+ deviceInstanceId,
8153
+ platform: devicePlatform
8154
+ };
8155
+ insertRecord(db, record);
8156
+ parsedCount++;
8157
+ }
8158
+ wm.setEntry(tool, filePath, {
8159
+ offset: stat2.size,
8160
+ size: stat2.size,
8161
+ mtime: stat2.mtimeMs
8162
+ });
8163
+ wm.save();
8164
+ onProgress({ phase: "Parsing logs", tool, current: toolIndex, total: toolTotal, records: parsedCount, toolCalls: toolCallCount });
8165
+ continue;
8166
+ }
8167
+ const content = readFileSync10(filePath, "utf-8");
7417
8168
  const lines = content.split("\n");
7418
8169
  let byteOffset = 0;
7419
8170
  let fileCwd;
@@ -7477,7 +8228,7 @@ async function runParse(db, filterTool, options) {
7477
8228
  }
7478
8229
  }
7479
8230
  if (fileCwd) {
7480
- db.prepare(`UPDATE records SET cwd = ? WHERE source_file = ? AND cwd = ''`).run(fileCwd, filePath);
8231
+ db.prepare(`UPDATE records SET cwd = ?, updated_at = ? WHERE source_file = ? AND cwd = ''`).run(fileCwd, Date.now(), filePath);
7481
8232
  }
7482
8233
  wm.setEntry(tool, filePath, {
7483
8234
  offset: stat2.size,
@@ -7492,7 +8243,7 @@ async function runParse(db, filterTool, options) {
7492
8243
  }
7493
8244
  }
7494
8245
  const openCodeDbPath = options?.openCodeDbPath ?? getDbPath("opencode") ?? "";
7495
- if ((!filterTool || filterTool === "opencode") && existsSync8(openCodeDbPath)) {
8246
+ if ((!filterTool || filterTool === "opencode") && existsSync10(openCodeDbPath)) {
7496
8247
  try {
7497
8248
  const openCodeDb = new Database2(openCodeDbPath, { readonly: true });
7498
8249
  try {
@@ -7523,7 +8274,7 @@ async function runParse(db, filterTool, options) {
7523
8274
  }
7524
8275
  }
7525
8276
  const hermesDbPath = options?.hermesDbPath ?? getDbPath("hermes") ?? "";
7526
- if ((!filterTool || filterTool === "hermes") && existsSync8(hermesDbPath)) {
8277
+ if ((!filterTool || filterTool === "hermes") && existsSync10(hermesDbPath)) {
7527
8278
  try {
7528
8279
  const hermesDb = new Database2(hermesDbPath, { readonly: true });
7529
8280
  try {
@@ -7554,7 +8305,7 @@ async function runParse(db, filterTool, options) {
7554
8305
  }
7555
8306
  }
7556
8307
  const qoderDbPath = options?.qoderDbPath ?? getDbPath("qoder-db") ?? "";
7557
- if ((!filterTool || filterTool === "qoder") && existsSync8(qoderDbPath)) {
8308
+ if ((!filterTool || filterTool === "qoder") && existsSync10(qoderDbPath)) {
7558
8309
  try {
7559
8310
  const qoderDb = new Database2(qoderDbPath, { readonly: true });
7560
8311
  try {
@@ -7585,7 +8336,7 @@ async function runParse(db, filterTool, options) {
7585
8336
  }
7586
8337
  }
7587
8338
  const cursorDbPath = options?.cursorDbPath ?? getDbPath("cursor") ?? "";
7588
- if ((!filterTool || filterTool === "cursor") && existsSync8(cursorDbPath)) {
8339
+ if ((!filterTool || filterTool === "cursor") && existsSync10(cursorDbPath)) {
7589
8340
  try {
7590
8341
  const cursorDb = new Database2(cursorDbPath, { readonly: true });
7591
8342
  try {
@@ -7615,7 +8366,7 @@ async function runParse(db, filterTool, options) {
7615
8366
  }
7616
8367
  }
7617
8368
  const kiloDbPath = getDbPath("kilocode-db") ?? "";
7618
- if ((!filterTool || filterTool === "kilocode") && existsSync8(kiloDbPath)) {
8369
+ if ((!filterTool || filterTool === "kilocode") && existsSync10(kiloDbPath)) {
7619
8370
  try {
7620
8371
  const kiloDb = new Database2(kiloDbPath, { readonly: true });
7621
8372
  try {
@@ -7655,7 +8406,7 @@ async function runParse(db, filterTool, options) {
7655
8406
  }
7656
8407
  }
7657
8408
  const gooseDbPath = getDbPath("goose") ?? "";
7658
- if ((!filterTool || filterTool === "goose") && existsSync8(gooseDbPath)) {
8409
+ if ((!filterTool || filterTool === "goose") && existsSync10(gooseDbPath)) {
7659
8410
  try {
7660
8411
  const gooseDb = new Database2(gooseDbPath, { readonly: true });
7661
8412
  try {
@@ -7713,7 +8464,7 @@ async function runParse(db, filterTool, options) {
7713
8464
  }
7714
8465
  }
7715
8466
  const zedDbPath = getDbPath("zed") ?? "";
7716
- if ((!filterTool || filterTool === "zed") && existsSync8(zedDbPath)) {
8467
+ if ((!filterTool || filterTool === "zed") && existsSync10(zedDbPath)) {
7717
8468
  try {
7718
8469
  const zedDb = new Database2(zedDbPath, { readonly: true });
7719
8470
  try {
@@ -7743,7 +8494,7 @@ async function runParse(db, filterTool, options) {
7743
8494
  }
7744
8495
  if (!filterTool || filterTool === "zcode") {
7745
8496
  const zcodeDbPath = getDbPath("zcode") ?? "";
7746
- if (existsSync8(zcodeDbPath)) {
8497
+ if (existsSync10(zcodeDbPath)) {
7747
8498
  try {
7748
8499
  const zcodeDb = new Database2(zcodeDbPath, { readonly: true });
7749
8500
  try {
@@ -7780,6 +8531,53 @@ async function runParse(db, filterTool, options) {
7780
8531
  }
7781
8532
  }
7782
8533
  }
8534
+ const traeDbPath = getDbPath("trae") ?? "";
8535
+ if ((!filterTool || filterTool === "trae") && pathIsFile(traeDbPath)) {
8536
+ const traeLastImported = wm.getTraeLastImported?.() ?? 0;
8537
+ const result = runParseTrae({
8538
+ dbPath: traeDbPath,
8539
+ device,
8540
+ deviceInstanceId,
8541
+ platform: devicePlatform,
8542
+ now: Date.now(),
8543
+ lastImportedAt: traeLastImported
8544
+ });
8545
+ for (const record of result.records) insertRecord(db, record);
8546
+ for (const tc of result.toolCalls) insertToolCall(db, tc);
8547
+ parsedCount += result.records.length;
8548
+ toolCallCount += result.toolCalls.length;
8549
+ errors.push(...result.errors);
8550
+ if (result.lastImportedAt > traeLastImported) {
8551
+ wm.setTraeLastImported?.(result.lastImportedAt);
8552
+ wm.save();
8553
+ }
8554
+ onProgress({ phase: "Parsing SQLite", tool: "trae", current: 1, total: 1, records: parsedCount, toolCalls: toolCallCount });
8555
+ }
8556
+ const codeBuddyIdeDir = getDbPath("codebuddy-ide") ?? "";
8557
+ if ((!filterTool || filterTool === "codebuddy") && existsSync10(codeBuddyIdeDir)) {
8558
+ try {
8559
+ const cbCursor = wm.getCodeBuddyIdeCursor();
8560
+ const result = runParseCodeBuddy({
8561
+ dataDir: codeBuddyIdeDir,
8562
+ device,
8563
+ deviceInstanceId,
8564
+ platform: devicePlatform,
8565
+ now: Date.now(),
8566
+ cursor: cbCursor,
8567
+ exchangeRate
8568
+ });
8569
+ for (const record of result.records) insertRecord(db, record);
8570
+ parsedCount += result.records.length;
8571
+ errors.push(...result.errors);
8572
+ if (result.nextCursor > cbCursor) {
8573
+ wm.setCodeBuddyIdeCursor(result.nextCursor);
8574
+ wm.save();
8575
+ }
8576
+ onProgress({ phase: "Parsing logs", tool: "codebuddy", current: 1, total: 1, records: parsedCount, toolCalls: toolCallCount });
8577
+ } catch (e) {
8578
+ errors.push(`${codeBuddyIdeDir}: ${e instanceof Error ? e.message : e}`);
8579
+ }
8580
+ }
7783
8581
  if (deviceInstanceId !== "unknown") {
7784
8582
  db.prepare(
7785
8583
  `UPDATE records SET device_instance_id = ?, device = ? WHERE device_instance_id = 'unknown'`
@@ -7791,9 +8589,19 @@ async function runParse(db, filterTool, options) {
7791
8589
  ).run(devicePlatform);
7792
8590
  }
7793
8591
  backfillHermesSourceFiles(db);
8592
+ backfillCwd(db);
8593
+ backfillSkillNames(db);
8594
+ backfillCodexModels(db);
8595
+ backfillMissingToolCalls(db, exchangeRate);
8596
+ return { parsedCount, toolCallCount, errors };
8597
+ }
8598
+ function backfillCwd(db) {
7794
8599
  const staleFiles = db.prepare(
7795
8600
  `SELECT DISTINCT source_file FROM records WHERE cwd = '' AND source_file NOT LIKE 'synced/%'`
7796
8601
  ).all();
8602
+ const updateStmt = db.prepare(
8603
+ `UPDATE records SET cwd = ?, updated_at = ? WHERE source_file = ? AND cwd = ''`
8604
+ );
7797
8605
  for (const { source_file } of staleFiles) {
7798
8606
  try {
7799
8607
  const fd = openSync(source_file, "r");
@@ -7804,15 +8612,11 @@ async function runParse(db, filterTool, options) {
7804
8612
  const data = JSON.parse(firstLine);
7805
8613
  const cwd = extractCwdFromJson(data);
7806
8614
  if (cwd) {
7807
- db.prepare(`UPDATE records SET cwd = ? WHERE source_file = ? AND cwd = ''`).run(cwd, source_file);
8615
+ updateStmt.run(cwd, Date.now(), source_file);
7808
8616
  }
7809
8617
  } catch {
7810
8618
  }
7811
8619
  }
7812
- backfillSkillNames(db);
7813
- backfillCodexModels(db);
7814
- backfillMissingToolCalls(db, exchangeRate);
7815
- return { parsedCount, toolCallCount, errors };
7816
8620
  }
7817
8621
  function backfillHermesSourceFiles(db) {
7818
8622
  const rows = db.prepare(`
@@ -7826,7 +8630,7 @@ function backfillHermesSourceFiles(db) {
7826
8630
  const dbPath = rows[0].source_file;
7827
8631
  let titleMap = /* @__PURE__ */ new Map();
7828
8632
  try {
7829
- if (existsSync8(dbPath)) {
8633
+ if (existsSync10(dbPath)) {
7830
8634
  const hermesDb = new Database2(dbPath, { readonly: true });
7831
8635
  try {
7832
8636
  const sessions = hermesDb.prepare(
@@ -7839,11 +8643,11 @@ function backfillHermesSourceFiles(db) {
7839
8643
  }
7840
8644
  } catch {
7841
8645
  }
7842
- const updateStmt = db.prepare(`UPDATE records SET source_file = ? WHERE tool = 'hermes' AND session_id = ? AND source_file = ?`);
8646
+ const updateStmt = db.prepare(`UPDATE records SET source_file = ?, updated_at = ? WHERE tool = 'hermes' AND session_id = ? AND source_file = ?`);
7843
8647
  for (const row of rows) {
7844
8648
  const title = (titleMap.get(row.session_id) || "").replace(/[/\\:]/g, "_").slice(0, 80);
7845
8649
  const newSourceFile = title ? `${row.source_file}:session:${row.session_id}:${title}` : `${row.source_file}:session:${row.session_id}`;
7846
- updateStmt.run(newSourceFile, row.session_id, row.source_file);
8650
+ updateStmt.run(newSourceFile, Date.now(), row.session_id, row.source_file);
7847
8651
  }
7848
8652
  }
7849
8653
  function backfillSkillNames(db) {
@@ -7907,7 +8711,7 @@ function backfillCodexModels(db) {
7907
8711
  );
7908
8712
  for (const [sourceFile, fileRows] of byFile) {
7909
8713
  try {
7910
- const content = readFileSync8(sourceFile, "utf-8");
8714
+ const content = readFileSync10(sourceFile, "utf-8");
7911
8715
  const lines = content.split("\n");
7912
8716
  const offsetToModel = /* @__PURE__ */ new Map();
7913
8717
  let currentModel = "";
@@ -7955,7 +8759,7 @@ function backfillMissingToolCalls(db, exchangeRate) {
7955
8759
  }
7956
8760
  for (const [sourceFile, fileRows] of rowsByFile) {
7957
8761
  try {
7958
- const content = readFileSync8(sourceFile, "utf-8");
8762
+ const content = readFileSync10(sourceFile, "utf-8");
7959
8763
  const lines = content.split("\n");
7960
8764
  const aggregator = new Aggregator();
7961
8765
  const recordIdByOffset = /* @__PURE__ */ new Map();
@@ -8017,7 +8821,7 @@ function deriveSessionId(tool, sourceFile) {
8017
8821
  }
8018
8822
 
8019
8823
  // src/commands/sync.ts
8020
- import { join as join9 } from "path";
8824
+ import { join as join11 } from "path";
8021
8825
 
8022
8826
  // src/db/synced-records.ts
8023
8827
  function insertSyncedRecord(db, record) {
@@ -8450,10 +9254,10 @@ async function cloudClear() {
8450
9254
  }
8451
9255
  function getClientVersion() {
8452
9256
  try {
8453
- const { readFileSync: readFileSync12 } = __require("fs");
8454
- const { join: join16 } = __require("path");
8455
- const pkgPath = join16(__dirname, "../../package.json");
8456
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
9257
+ const { readFileSync: readFileSync14 } = __require("fs");
9258
+ const { join: join18 } = __require("path");
9259
+ const pkgPath = join18(__dirname, "../../package.json");
9260
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
8457
9261
  return pkg.version || "0.0.0";
8458
9262
  } catch {
8459
9263
  return "0.0.0";
@@ -8544,7 +9348,7 @@ var CloudSyncOrchestrator = class {
8544
9348
  import { execFile } from "child_process";
8545
9349
  import { promisify } from "util";
8546
9350
  import { readFile, writeFile, mkdir, readdir, stat, unlink, rm } from "fs/promises";
8547
- import { join as join8 } from "path";
9351
+ import { join as join10 } from "path";
8548
9352
  var exec = promisify(execFile);
8549
9353
  var GitSyncBackend = class _GitSyncBackend {
8550
9354
  repo;
@@ -8556,7 +9360,7 @@ var GitSyncBackend = class _GitSyncBackend {
8556
9360
  this.repo = config.repo;
8557
9361
  this.token = config.token;
8558
9362
  this.cacheDir = config.cacheDir;
8559
- this.dataDir = join8(config.cacheDir, "data");
9363
+ this.dataDir = join10(config.cacheDir, "data");
8560
9364
  this.branch = config.branch ?? "main";
8561
9365
  }
8562
9366
  get remoteUrl() {
@@ -8588,7 +9392,7 @@ var GitSyncBackend = class _GitSyncBackend {
8588
9392
  /** Clone or pull the repo to get latest remote state */
8589
9393
  async prepare() {
8590
9394
  try {
8591
- await stat(join8(this.cacheDir, ".git"));
9395
+ await stat(join10(this.cacheDir, ".git"));
8592
9396
  await this.git(["fetch", "origin", this.branch, "--depth=1"]);
8593
9397
  await this.git(["reset", "--hard", `origin/${this.branch}`]);
8594
9398
  } catch {
@@ -8605,15 +9409,15 @@ var GitSyncBackend = class _GitSyncBackend {
8605
9409
  }
8606
9410
  async readFile(path3) {
8607
9411
  try {
8608
- const fullPath = join8(this.dataDir, path3);
9412
+ const fullPath = join10(this.dataDir, path3);
8609
9413
  return await readFile(fullPath, "utf-8");
8610
9414
  } catch {
8611
9415
  return null;
8612
9416
  }
8613
9417
  }
8614
9418
  async writeFile(path3, content) {
8615
- const fullPath = join8(this.dataDir, path3);
8616
- await mkdir(join8(fullPath, ".."), { recursive: true });
9419
+ const fullPath = join10(this.dataDir, path3);
9420
+ await mkdir(join10(fullPath, ".."), { recursive: true });
8617
9421
  await writeFile(fullPath, content, "utf-8");
8618
9422
  }
8619
9423
  async listFiles() {
@@ -8624,7 +9428,7 @@ var GitSyncBackend = class _GitSyncBackend {
8624
9428
  }
8625
9429
  }
8626
9430
  async deleteFile(path3) {
8627
- const fullPath = join8(this.dataDir, path3);
9431
+ const fullPath = join10(this.dataDir, path3);
8628
9432
  try {
8629
9433
  await unlink(fullPath);
8630
9434
  } catch {
@@ -8663,7 +9467,7 @@ var GitSyncBackend = class _GitSyncBackend {
8663
9467
  for (const entry of entries) {
8664
9468
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
8665
9469
  if (entry.isDirectory()) {
8666
- files.push(...await this.walkDir(join8(dir, entry.name), relPath));
9470
+ files.push(...await this.walkDir(join10(dir, entry.name), relPath));
8667
9471
  } else if (entry.name.endsWith(".ndjson")) {
8668
9472
  files.push(relPath);
8669
9473
  }
@@ -8797,7 +9601,7 @@ function createBackend(config) {
8797
9601
  return new GitSyncBackend({
8798
9602
  repo: sync.repo,
8799
9603
  token,
8800
- cacheDir: join9(AIUSAGE_DIR, "sync-repo")
9604
+ cacheDir: join11(AIUSAGE_DIR, "sync-repo")
8801
9605
  });
8802
9606
  }
8803
9607
  if (sync.backend === "s3") {
@@ -8892,8 +9696,8 @@ async function runSync(db, options) {
8892
9696
  }
8893
9697
 
8894
9698
  // src/commands/clean.ts
8895
- import { unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
8896
- import { join as join10 } from "path";
9699
+ import { unlinkSync as unlinkSync3, existsSync as existsSync11 } from "fs";
9700
+ import { join as join12 } from "path";
8897
9701
  function cleanOldData(db, days) {
8898
9702
  const cutoff = Date.now() - days * 864e5;
8899
9703
  const recordsResult = db.prepare("DELETE FROM records WHERE ts < ?").run(cutoff);
@@ -8914,9 +9718,9 @@ function cleanAll(db) {
8914
9718
  const syncedResult = db.prepare("DELETE FROM synced_records").run();
8915
9719
  const syncStateResult = db.prepare("DELETE FROM sync_record_state").run();
8916
9720
  const tombstonesResult = db.prepare("DELETE FROM sync_tombstones").run();
8917
- const watermarkPath = join10(AIUSAGE_DIR, "watermark.json");
9721
+ const watermarkPath = join12(AIUSAGE_DIR, "watermark.json");
8918
9722
  let watermarkRemoved = false;
8919
- if (existsSync9(watermarkPath)) {
9723
+ if (existsSync11(watermarkPath)) {
8920
9724
  unlinkSync3(watermarkPath);
8921
9725
  watermarkRemoved = true;
8922
9726
  }
@@ -8951,7 +9755,7 @@ function createBackend2(config) {
8951
9755
  return new GitSyncBackend({
8952
9756
  repo: config.sync.repo,
8953
9757
  token,
8954
- cacheDir: join10(AIUSAGE_DIR, "sync-repo")
9758
+ cacheDir: join12(AIUSAGE_DIR, "sync-repo")
8955
9759
  });
8956
9760
  }
8957
9761
  if (config.sync.backend === "s3") {
@@ -9284,7 +10088,7 @@ var RuntimeSettingsController = class {
9284
10088
 
9285
10089
  // src/commands/serve.ts
9286
10090
  var MAX_PORT_ATTEMPTS = 10;
9287
- var PORT_FILE = join11(AIUSAGE_DIR, ".serve-port");
10091
+ var PORT_FILE = join13(AIUSAGE_DIR, ".serve-port");
9288
10092
  var MIME_TYPES = {
9289
10093
  ".html": "text/html",
9290
10094
  ".js": "application/javascript",
@@ -9301,6 +10105,15 @@ function serve(options) {
9301
10105
  const config = loadConfig();
9302
10106
  const dbWriteQueue = new AsyncTaskQueue();
9303
10107
  const runDbWrite = (task) => dbWriteQueue.run(task);
10108
+ if (config?.priceOverrides && Object.keys(config.priceOverrides).length > 0) {
10109
+ const imported = importConfigPriceOverrides(options.db, config.priceOverrides);
10110
+ delete config.priceOverrides;
10111
+ saveConfig(config);
10112
+ loadPricingRuntime(options.db, config);
10113
+ if (imported.length > 0) {
10114
+ console.log(`[serve] migrated ${imported.length} config price override(s) into the registry: ${imported.join(", ")}`);
10115
+ }
10116
+ }
9304
10117
  if (config == null || config.exchangeRate == null) {
9305
10118
  const cacheAge = config?.exchangeRateCache ? Date.now() - config.exchangeRateCache.fetchedAt : Infinity;
9306
10119
  if (cacheAge >= CACHE_TTL_MS) {
@@ -9350,11 +10163,11 @@ function serve(options) {
9350
10163
  getDbWriteQueueStatus: () => dbWriteQueue.getStatus()
9351
10164
  });
9352
10165
  const webBuildDir = (() => {
9353
- const prodDir = join11(dirname4(fileURLToPath(import.meta.url)), "web");
9354
- if (existsSync10(prodDir)) return prodDir;
9355
- return join11(dirname4(fileURLToPath(import.meta.url)), "..", "..", "..", "web", "build");
10166
+ const prodDir = join13(dirname6(fileURLToPath(import.meta.url)), "web");
10167
+ if (existsSync12(prodDir)) return prodDir;
10168
+ return join13(dirname6(fileURLToPath(import.meta.url)), "..", "..", "..", "web", "build");
9356
10169
  })();
9357
- if (!existsSync10(webBuildDir)) {
10170
+ if (!existsSync12(webBuildDir)) {
9358
10171
  console.error("Web dashboard not found. Reinstall the package: npm install -g @juliantanx/aiusage");
9359
10172
  process.exit(1);
9360
10173
  }
@@ -9364,19 +10177,19 @@ function serve(options) {
9364
10177
  apiServer.emit("request", req, res);
9365
10178
  return;
9366
10179
  }
9367
- if (existsSync10(webBuildDir)) {
9368
- let filePath = join11(webBuildDir, url.pathname);
10180
+ if (existsSync12(webBuildDir)) {
10181
+ let filePath = join13(webBuildDir, url.pathname);
9369
10182
  try {
9370
- if (statSync3(filePath).isDirectory()) {
9371
- filePath = join11(filePath, "index.html");
10183
+ if (statSync4(filePath).isDirectory()) {
10184
+ filePath = join13(filePath, "index.html");
9372
10185
  }
9373
10186
  } catch {
9374
10187
  }
9375
- if (!existsSync10(filePath)) {
9376
- filePath = join11(webBuildDir, "index.html");
10188
+ if (!existsSync12(filePath)) {
10189
+ filePath = join13(webBuildDir, "index.html");
9377
10190
  }
9378
10191
  try {
9379
- const content = readFileSync9(filePath);
10192
+ const content = readFileSync11(filePath);
9380
10193
  const ext = extname2(filePath);
9381
10194
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
9382
10195
  res.writeHead(200, { "Content-Type": contentType });
@@ -9558,9 +10371,9 @@ Consent recorded for schema v1.`
9558
10371
  }
9559
10372
 
9560
10373
  // src/commands/status.ts
9561
- import { statSync as statSync4, existsSync as existsSync11 } from "fs";
10374
+ import { statSync as statSync5, existsSync as existsSync13 } from "fs";
9562
10375
  import { hostname as hostname3 } from "os";
9563
- import { join as join12 } from "path";
10376
+ import { join as join14 } from "path";
9564
10377
  function formatFileSize(bytes) {
9565
10378
  if (bytes < 1024) return `${bytes} B`;
9566
10379
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -9578,11 +10391,11 @@ function generateStatus(db) {
9578
10391
  const schemaVersion = db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get()?.version ?? 0;
9579
10392
  const tableCount = db.prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").get()?.count ?? 0;
9580
10393
  const viewCount = db.prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type = 'view'").get()?.count ?? 0;
9581
- const sizePath = join12(AIUSAGE_DIR, "cache.db");
10394
+ const sizePath = join14(AIUSAGE_DIR, "cache.db");
9582
10395
  let databaseSize = "0 B";
9583
- if (existsSync11(sizePath)) {
10396
+ if (existsSync13(sizePath)) {
9584
10397
  try {
9585
- const stat2 = statSync4(sizePath);
10398
+ const stat2 = statSync5(sizePath);
9586
10399
  databaseSize = formatFileSize(stat2.size);
9587
10400
  } catch {
9588
10401
  }
@@ -9671,11 +10484,11 @@ function recalcPricing(db) {
9671
10484
  ).all(lastId, BATCH_SIZE);
9672
10485
  if (records.length === 0) break;
9673
10486
  for (const record of records) {
9674
- if (record.cost_source === "log") {
10487
+ const model = record.tool === "qoder" ? normalizeQoderModel(record.model) : record.model;
10488
+ if (record.cost_source === "log" && record.cost > 0 && !hasUserPrice(db, model)) {
9675
10489
  skippedCount++;
9676
10490
  continue;
9677
10491
  }
9678
- const model = record.tool === "qoder" ? normalizeQoderModel(record.model) : record.model;
9679
10492
  const provider = model !== record.model ? inferProvider(model) : record.provider;
9680
10493
  const price = resolvePriceFromRegistry(db, model);
9681
10494
  const newCost = price ? calculateCostForPrice(price, {
@@ -9803,10 +10616,10 @@ var SyncProgressReporter = class {
9803
10616
  };
9804
10617
 
9805
10618
  // src/commands/widget.ts
9806
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
10619
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
9807
10620
  import { spawn, execSync as execSync2 } from "child_process";
9808
- import { join as join13 } from "path";
9809
- var WIDGET_PID_PATH = join13(AIUSAGE_DIR, "widget.pid");
10621
+ import { join as join15 } from "path";
10622
+ var WIDGET_PID_PATH = join15(AIUSAGE_DIR, "widget.pid");
9810
10623
  async function launchWidget() {
9811
10624
  if (isWidgetRunning()) {
9812
10625
  console.log("aiusage widget is already running in the system tray.");
@@ -9828,10 +10641,10 @@ async function launchWidget() {
9828
10641
  console.log("aiusage widget started.");
9829
10642
  }
9830
10643
  function isWidgetRunning() {
9831
- if (!existsSync12(WIDGET_PID_PATH)) return false;
10644
+ if (!existsSync14(WIDGET_PID_PATH)) return false;
9832
10645
  let pid;
9833
10646
  try {
9834
- pid = parseInt(readFileSync10(WIDGET_PID_PATH, "utf-8").trim(), 10);
10647
+ pid = parseInt(readFileSync12(WIDGET_PID_PATH, "utf-8").trim(), 10);
9835
10648
  } catch {
9836
10649
  return false;
9837
10650
  }
@@ -9875,10 +10688,10 @@ function getDeviceName2() {
9875
10688
  }
9876
10689
  function getCliVersion2() {
9877
10690
  try {
9878
- const { readFileSync: readFileSync12 } = __require("fs");
9879
- const { join: join16 } = __require("path");
9880
- const pkgPath = join16(__dirname, "../../package.json");
9881
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
10691
+ const { readFileSync: readFileSync14 } = __require("fs");
10692
+ const { join: join18 } = __require("path");
10693
+ const pkgPath = join18(__dirname, "../../package.json");
10694
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
9882
10695
  return pkg.version || "0.0.0";
9883
10696
  } catch {
9884
10697
  return "0.0.0";
@@ -10047,8 +10860,8 @@ function formatTokens(value) {
10047
10860
  return Number(value).toLocaleString();
10048
10861
  }
10049
10862
  function formatCost(value) {
10050
- const num3 = Number(value);
10051
- return Number.isFinite(num3) ? `$${num3.toFixed(4)}` : "$0.0000";
10863
+ const num4 = Number(value);
10864
+ return Number.isFinite(num4) ? `$${num4.toFixed(4)}` : "$0.0000";
10052
10865
  }
10053
10866
  function formatUpdatedAt(value) {
10054
10867
  const date = new Date(value);
@@ -10121,17 +10934,17 @@ async function runLeaderboardView(options = {}) {
10121
10934
 
10122
10935
  // src/commands/menu.ts
10123
10936
  import { createInterface } from "readline";
10124
- import { spawn as spawn2, spawnSync, execSync as execSync4 } from "child_process";
10125
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
10126
- import { join as join14 } from "path";
10937
+ import { spawn as spawn2, spawnSync as spawnSync2, execSync as execSync4 } from "child_process";
10938
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
10939
+ import { join as join16 } from "path";
10127
10940
  var DEFAULT_PORT = 3847;
10128
- var PORT_FILE2 = join14(AIUSAGE_DIR, ".serve-port");
10941
+ var PORT_FILE2 = join16(AIUSAGE_DIR, ".serve-port");
10129
10942
  function clearScreen() {
10130
10943
  process.stdout.write("\x1Bc");
10131
10944
  }
10132
10945
  function getPort() {
10133
- if (existsSync13(PORT_FILE2)) {
10134
- const raw = readFileSync11(PORT_FILE2, "utf-8").trim();
10946
+ if (existsSync15(PORT_FILE2)) {
10947
+ const raw = readFileSync13(PORT_FILE2, "utf-8").trim();
10135
10948
  const n = parseInt(raw, 10);
10136
10949
  if (!isNaN(n) && n > 0) return n;
10137
10950
  }
@@ -10178,7 +10991,7 @@ function openBrowser2(url) {
10178
10991
  }
10179
10992
  function runCommand(args) {
10180
10993
  try {
10181
- spawnSync("aiusage", args, { stdio: "inherit" });
10994
+ spawnSync2("aiusage", args, { stdio: "inherit" });
10182
10995
  } catch {
10183
10996
  }
10184
10997
  }
@@ -10620,10 +11433,10 @@ ${dashboardStatusLine()}
10620
11433
  }
10621
11434
 
10622
11435
  // src/cli.ts
10623
- import { join as join15 } from "path";
10624
- var DB_PATH2 = join15(AIUSAGE_DIR, "cache.db");
11436
+ import { join as join17 } from "path";
11437
+ var DB_PATH2 = join17(AIUSAGE_DIR, "cache.db");
10625
11438
  var program = new Command();
10626
- program.name("aiusage").version(true ? "1.5.6" : "dev").description("CLI tool for AI usage statistics");
11439
+ program.name("aiusage").version(true ? "1.5.8" : "dev").description("CLI tool for AI usage statistics");
10627
11440
  program.action(() => {
10628
11441
  const unknownCommand = program.args.find((arg) => typeof arg === "string");
10629
11442
  if (unknownCommand) {
@@ -10910,7 +11723,7 @@ function hasWidget() {
10910
11723
  }
10911
11724
  }
10912
11725
  function buildPm2Config(includeWidget) {
10913
- const wrapperPath = join15(AIUSAGE_DIR, "pm2-server.cjs");
11726
+ const wrapperPath = join17(AIUSAGE_DIR, "pm2-server.cjs");
10914
11727
  writeFileSync7(wrapperPath, [
10915
11728
  `// Auto-generated by aiusage pm2-start \u2014 do not edit`,
10916
11729
  `const { execSync, spawn } = require('child_process')`,
@@ -10957,7 +11770,7 @@ ${apps.join(",\n")}
10957
11770
  program.command("pm2-setup").description("Generate ecosystem.config.cjs for PM2 background services").option("--server-only", "Only include server, skip widget").action((options) => {
10958
11771
  const includeWidget = options.serverOnly ? false : hasWidget();
10959
11772
  const config = buildPm2Config(includeWidget);
10960
- const configPath = join15(AIUSAGE_DIR, "ecosystem.config.cjs");
11773
+ const configPath = join17(AIUSAGE_DIR, "ecosystem.config.cjs");
10961
11774
  writeFileSync7(configPath, config, "utf-8");
10962
11775
  console.log(`Config created: ${configPath}`);
10963
11776
  if (!includeWidget) {
@@ -10967,7 +11780,7 @@ program.command("pm2-setup").description("Generate ecosystem.config.cjs for PM2
10967
11780
  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) => {
10968
11781
  const includeWidget = options.serverOnly ? false : hasWidget();
10969
11782
  const config = buildPm2Config(includeWidget);
10970
- const configPath = join15(AIUSAGE_DIR, "ecosystem.config.cjs");
11783
+ const configPath = join17(AIUSAGE_DIR, "ecosystem.config.cjs");
10971
11784
  writeFileSync7(configPath, config, "utf-8");
10972
11785
  console.log(`Config: ${configPath}`);
10973
11786
  try {