dr-claw 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/LICENSE +1380 -0
  2. package/NOTICE +17 -0
  3. package/README.md +875 -0
  4. package/README.zh-CN.md +857 -0
  5. package/dist/api-docs.html +878 -0
  6. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  7. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  8. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  22. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  25. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  26. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  29. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  31. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  32. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  35. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  37. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  49. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  50. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  52. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  53. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  55. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  56. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  57. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  58. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  60. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  61. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  63. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  64. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  65. package/dist/assets/_basePickBy-C0n1uo4u.js +1 -0
  66. package/dist/assets/_baseUniq-BRNmhFkc.js +1 -0
  67. package/dist/assets/arc-DOMBmC_A.js +1 -0
  68. package/dist/assets/architectureDiagram-VXUJARFQ-CtMWglbD.js +36 -0
  69. package/dist/assets/blockDiagram-VD42YOAC-DK_gTkJy.js +122 -0
  70. package/dist/assets/c4Diagram-YG6GDRKO-DrDAvsn7.js +10 -0
  71. package/dist/assets/channel-BFOE7OyC.js +1 -0
  72. package/dist/assets/chunk-4BX2VUAB-CGbhdZcN.js +1 -0
  73. package/dist/assets/chunk-55IACEB6-sGeFSxen.js +1 -0
  74. package/dist/assets/chunk-B4BG7PRW-RxgHAU_P.js +165 -0
  75. package/dist/assets/chunk-DI55MBZ5-CvHUXZrx.js +220 -0
  76. package/dist/assets/chunk-FMBD7UC4-Sn7cXVNU.js +15 -0
  77. package/dist/assets/chunk-QN33PNHL-bDKOHppN.js +1 -0
  78. package/dist/assets/chunk-QZHKN3VN-B9kN5LGm.js +1 -0
  79. package/dist/assets/chunk-TZMSLE5B-CCBe6len.js +1 -0
  80. package/dist/assets/classDiagram-2ON5EDUG-DQoMNYlY.js +1 -0
  81. package/dist/assets/classDiagram-v2-WZHVMYZB-DQoMNYlY.js +1 -0
  82. package/dist/assets/clone-BulQSblH.js +1 -0
  83. package/dist/assets/cose-bilkent-S5V4N54A-r6sLiHBW.js +1 -0
  84. package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  85. package/dist/assets/dagre-6UL2VRFP-AbsaR1Oa.js +4 -0
  86. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  87. package/dist/assets/diagram-PSM6KHXK-DjCvdUyy.js +24 -0
  88. package/dist/assets/diagram-QEK2KX5R-CqiocXkH.js +43 -0
  89. package/dist/assets/diagram-S2PKOQOG-D5ALeoII.js +24 -0
  90. package/dist/assets/erDiagram-Q2GNP2WA-C1AeOsiz.js +60 -0
  91. package/dist/assets/flowDiagram-NV44I4VS-C98ve_DO.js +162 -0
  92. package/dist/assets/ganttDiagram-JELNMOA3-3zdLvcxD.js +267 -0
  93. package/dist/assets/gitGraphDiagram-V2S2FVAM-nDuDMsHh.js +65 -0
  94. package/dist/assets/graph-Blm-emkR.js +1 -0
  95. package/dist/assets/index-B8Yutsnm.css +1 -0
  96. package/dist/assets/index-Cf6gyhc3.js +654 -0
  97. package/dist/assets/infoDiagram-HS3SLOUP-CmzE3q0R.js +2 -0
  98. package/dist/assets/init-Gi6I4Gst.js +1 -0
  99. package/dist/assets/journeyDiagram-XKPGCS4Q-CYaW7LrP.js +139 -0
  100. package/dist/assets/kanban-definition-3W4ZIXB7-mkmW7p0j.js +89 -0
  101. package/dist/assets/layout-Ct2kFIId.js +1 -0
  102. package/dist/assets/linear-CKG9EwIJ.js +1 -0
  103. package/dist/assets/mermaid.core-CaKcUnnd.js +250 -0
  104. package/dist/assets/mindmap-definition-VGOIOE7T-Bct-pYin.js +68 -0
  105. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  106. package/dist/assets/pieDiagram-ADFJNKIX-C8wFD4Jr.js +30 -0
  107. package/dist/assets/quadrantDiagram-AYHSOK5B-CuiE_f46.js +7 -0
  108. package/dist/assets/requirementDiagram-UZGBJVZJ-C4v1CqU8.js +64 -0
  109. package/dist/assets/sankeyDiagram-TZEHDZUN-keAqqs-Y.js +10 -0
  110. package/dist/assets/sequenceDiagram-WL72ISMW-ClNbvKTB.js +145 -0
  111. package/dist/assets/stateDiagram-FKZM4ZOC-D8We-2vq.js +1 -0
  112. package/dist/assets/stateDiagram-v2-4FDKWEC3-BUURfjrE.js +1 -0
  113. package/dist/assets/timeline-definition-IT6M3QCI-DsvEMT5T.js +61 -0
  114. package/dist/assets/treemap-GDKQZRPO-TJrzN7Bj.js +162 -0
  115. package/dist/assets/vendor-codemirror-ansM-eig.js +33 -0
  116. package/dist/assets/vendor-react-BWF6YQQ3.js +8 -0
  117. package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
  118. package/dist/assets/xychartDiagram-PRI3JC2R-BdATmY5F.js +7 -0
  119. package/dist/clear-cache.html +85 -0
  120. package/dist/convert-icons.md +53 -0
  121. package/dist/dr-claw.png +0 -0
  122. package/dist/favicon.png +0 -0
  123. package/dist/generate-icons.js +49 -0
  124. package/dist/icons/claude-ai-icon.svg +1 -0
  125. package/dist/icons/codex-white.svg +3 -0
  126. package/dist/icons/codex.svg +3 -0
  127. package/dist/icons/cursor-white.svg +12 -0
  128. package/dist/icons/cursor.svg +1 -0
  129. package/dist/icons/file.svg +16997 -0
  130. package/dist/icons/generate-icons.md +19 -0
  131. package/dist/icons/icon-128x128.png +0 -0
  132. package/dist/icons/icon-144x144.png +0 -0
  133. package/dist/icons/icon-152x152.png +0 -0
  134. package/dist/icons/icon-192x192.png +0 -0
  135. package/dist/icons/icon-384x384.png +0 -0
  136. package/dist/icons/icon-512x512.png +0 -0
  137. package/dist/icons/icon-72x72.png +0 -0
  138. package/dist/icons/icon-96x96.png +0 -0
  139. package/dist/icons/news/arxiv.svg +1 -0
  140. package/dist/icons/news/huggingface.svg +8 -0
  141. package/dist/icons/news/x-black.png +0 -0
  142. package/dist/icons/news/x-white.png +0 -0
  143. package/dist/icons/news/xiaohongshu.png +0 -0
  144. package/dist/index.html +51 -0
  145. package/dist/leveraged-cognition.png +0 -0
  146. package/dist/manifest.json +61 -0
  147. package/dist/screenshots/chat.png +0 -0
  148. package/dist/screenshots/new_claw.png +0 -0
  149. package/dist/screenshots/news_dashboard.png +0 -0
  150. package/dist/screenshots/project_dashboard.png +0 -0
  151. package/dist/screenshots/research_lab.png +0 -0
  152. package/dist/screenshots/skill_library.png +0 -0
  153. package/dist/screenshots/task_list.png +0 -0
  154. package/dist/screenshots/terminal_example.png +0 -0
  155. package/dist/screenshots/terminal_example1.png +0 -0
  156. package/dist/sw.js +58 -0
  157. package/dist/wechat-group-qr.jpg +0 -0
  158. package/package.json +133 -0
  159. package/public/api-docs.html +878 -0
  160. package/public/clear-cache.html +85 -0
  161. package/public/convert-icons.md +53 -0
  162. package/public/dr-claw.png +0 -0
  163. package/public/favicon.png +0 -0
  164. package/public/generate-icons.js +49 -0
  165. package/public/icons/claude-ai-icon.svg +1 -0
  166. package/public/icons/codex-white.svg +3 -0
  167. package/public/icons/codex.svg +3 -0
  168. package/public/icons/cursor-white.svg +12 -0
  169. package/public/icons/cursor.svg +1 -0
  170. package/public/icons/file.svg +16997 -0
  171. package/public/icons/generate-icons.md +19 -0
  172. package/public/icons/icon-128x128.png +0 -0
  173. package/public/icons/icon-144x144.png +0 -0
  174. package/public/icons/icon-152x152.png +0 -0
  175. package/public/icons/icon-192x192.png +0 -0
  176. package/public/icons/icon-384x384.png +0 -0
  177. package/public/icons/icon-512x512.png +0 -0
  178. package/public/icons/icon-72x72.png +0 -0
  179. package/public/icons/icon-96x96.png +0 -0
  180. package/public/icons/news/arxiv.svg +1 -0
  181. package/public/icons/news/huggingface.svg +8 -0
  182. package/public/icons/news/x-black.png +0 -0
  183. package/public/icons/news/x-white.png +0 -0
  184. package/public/icons/news/xiaohongshu.png +0 -0
  185. package/public/leveraged-cognition.png +0 -0
  186. package/public/manifest.json +61 -0
  187. package/public/screenshots/chat.png +0 -0
  188. package/public/screenshots/new_claw.png +0 -0
  189. package/public/screenshots/news_dashboard.png +0 -0
  190. package/public/screenshots/project_dashboard.png +0 -0
  191. package/public/screenshots/research_lab.png +0 -0
  192. package/public/screenshots/skill_library.png +0 -0
  193. package/public/screenshots/task_list.png +0 -0
  194. package/public/screenshots/terminal_example.png +0 -0
  195. package/public/screenshots/terminal_example1.png +0 -0
  196. package/public/sw.js +58 -0
  197. package/public/wechat-group-qr.jpg +0 -0
  198. package/scripts/export-skills-catalog-v2.mjs +746 -0
  199. package/scripts/fix-node-pty.js +45 -0
  200. package/scripts/gemini_image_core.py +470 -0
  201. package/scripts/install-python-tools.js +64 -0
  202. package/server/AGENTS.md +73 -0
  203. package/server/__tests__/gemini-legacy-thought.test.mjs +88 -0
  204. package/server/__tests__/runtimePorts.test.mjs +115 -0
  205. package/server/__tests__/session-delete.test.mjs +166 -0
  206. package/server/bin/compute-node +70 -0
  207. package/server/claude-sdk.js +799 -0
  208. package/server/cli-chat.js +488 -0
  209. package/server/cli.js +354 -0
  210. package/server/compute-node.js +439 -0
  211. package/server/constants/config.js +5 -0
  212. package/server/cursor-cli.js +340 -0
  213. package/server/database/auth.db +0 -0
  214. package/server/database/db.js +1852 -0
  215. package/server/database/init.sql +194 -0
  216. package/server/gemini-cli.js +1352 -0
  217. package/server/index.js +3154 -0
  218. package/server/load-env.js +71 -0
  219. package/server/local-gpu.js +860 -0
  220. package/server/middleware/auth.js +100 -0
  221. package/server/openai-codex.js +724 -0
  222. package/server/openrouter.js +890 -0
  223. package/server/project-token-usage.js +407 -0
  224. package/server/projects.js +3973 -0
  225. package/server/routes/agent.js +1272 -0
  226. package/server/routes/auth.js +128 -0
  227. package/server/routes/auto-research.js +558 -0
  228. package/server/routes/cli-auth.js +958 -0
  229. package/server/routes/codex.js +344 -0
  230. package/server/routes/commands.js +601 -0
  231. package/server/routes/compute.js +358 -0
  232. package/server/routes/cursor.js +795 -0
  233. package/server/routes/git.js +1348 -0
  234. package/server/routes/mcp-utils.js +48 -0
  235. package/server/routes/mcp.js +618 -0
  236. package/server/routes/news.js +711 -0
  237. package/server/routes/projects.js +633 -0
  238. package/server/routes/references.js +338 -0
  239. package/server/routes/settings.js +285 -0
  240. package/server/routes/skills.js +914 -0
  241. package/server/routes/taskmaster.js +2566 -0
  242. package/server/routes/telemetry.js +89 -0
  243. package/server/routes/user.js +155 -0
  244. package/server/scripts/fix_session_names.js +57 -0
  245. package/server/scripts/research-news/common_words.py +19 -0
  246. package/server/scripts/research-news/link_keywords.py +298 -0
  247. package/server/scripts/research-news/scan_existing_notes.py +266 -0
  248. package/server/scripts/research-news/scoring_utils.py +227 -0
  249. package/server/scripts/research-news/search_arxiv.py +842 -0
  250. package/server/scripts/research-news/search_huggingface.py +438 -0
  251. package/server/scripts/research-news/search_x.py +490 -0
  252. package/server/scripts/research-news/search_xiaohongshu.py +610 -0
  253. package/server/taskmaster-templates/README.md +150 -0
  254. package/server/taskmaster-templates/README.zh-CN.md +150 -0
  255. package/server/taskmaster-templates/ai-research-dataset.json +230 -0
  256. package/server/taskmaster-templates/ai-research-method-model.json +230 -0
  257. package/server/taskmaster-templates/ai-research-position-paper.json +230 -0
  258. package/server/telemetry.js +121 -0
  259. package/server/templates/AGENTS.md +103 -0
  260. package/server/templates/CLAUDE.md +104 -0
  261. package/server/templates/GEMINI.md +103 -0
  262. package/server/templates/cursor-project.md +108 -0
  263. package/server/templates/index.js +37 -0
  264. package/server/utils/__tests__/sessionTokenUsage.test.js +82 -0
  265. package/server/utils/auto-research-mailer.js +57 -0
  266. package/server/utils/cliResolution.js +170 -0
  267. package/server/utils/commandParser.js +303 -0
  268. package/server/utils/cursorCommand.js +61 -0
  269. package/server/utils/geminiApiKey.js +28 -0
  270. package/server/utils/gitConfig.js +24 -0
  271. package/server/utils/imageAttachmentFiles.js +35 -0
  272. package/server/utils/mcp-detector.js +198 -0
  273. package/server/utils/parsers/bibtex-parser.js +207 -0
  274. package/server/utils/permissions.js +100 -0
  275. package/server/utils/runtimePorts.js +212 -0
  276. package/server/utils/sessionFormatting.js +68 -0
  277. package/server/utils/sessionIndex.js +100 -0
  278. package/server/utils/sessionMode.js +95 -0
  279. package/server/utils/sessionTokenUsage.js +105 -0
  280. package/server/utils/taskmaster-websocket.js +129 -0
  281. package/server/utils/zotero-client.js +136 -0
  282. package/shared/errorClassifier.js +102 -0
  283. package/shared/geminiThinkingSupport.d.ts +22 -0
  284. package/shared/geminiThinkingSupport.js +96 -0
  285. package/shared/geminiThoughtParser.d.ts +8 -0
  286. package/shared/geminiThoughtParser.js +30 -0
  287. package/shared/modelConstants.js +139 -0
@@ -0,0 +1,407 @@
1
+ import { promises as fs } from 'fs';
2
+ import fsSync from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+
7
+ import { encodeProjectPath, getCodexSessions, getGeminiSessions } from './projects.js';
8
+
9
+ const CACHE_TTL_MS = 5_000;
10
+
11
+ let summaryCache = null;
12
+
13
+ function createEmptyUsageTotals() {
14
+ return {
15
+ todayTokens: 0,
16
+ weekTokens: 0,
17
+ };
18
+ }
19
+
20
+ const LEGACY_DEFAULT_WORKSPACES_ROOT = path.join(os.homedir(), 'vibelab');
21
+ const CURRENT_DEFAULT_WORKSPACES_ROOT = path.join(os.homedir(), 'dr-claw');
22
+
23
+ function normalizeProjectRefs(projectRefs = []) {
24
+ return projectRefs
25
+ .filter((project) => project && typeof project.name === 'string' && typeof project.fullPath === 'string')
26
+ .map((project) => ({
27
+ name: project.name,
28
+ fullPath: project.fullPath,
29
+ }))
30
+ .sort((a, b) => a.name.localeCompare(b.name));
31
+ }
32
+
33
+ function getUsageWindowBounds(now = new Date()) {
34
+ const todayStart = new Date(now);
35
+ todayStart.setHours(0, 0, 0, 0);
36
+
37
+ const weekStart = new Date(todayStart);
38
+ const dayOfWeek = weekStart.getDay();
39
+ const daysSinceMonday = (dayOfWeek + 6) % 7;
40
+ weekStart.setDate(weekStart.getDate() - daysSinceMonday);
41
+
42
+ return {
43
+ nowMs: now.getTime(),
44
+ todayStartMs: todayStart.getTime(),
45
+ weekStartMs: weekStart.getTime(),
46
+ cacheKey: `${todayStart.toISOString()}|${weekStart.toISOString()}`,
47
+ };
48
+ }
49
+
50
+ function addUsageForTimestamp(target, timestampMs, tokens, bounds) {
51
+ if (!Number.isFinite(timestampMs) || !Number.isFinite(tokens) || tokens <= 0) {
52
+ return;
53
+ }
54
+
55
+ if (timestampMs >= bounds.weekStartMs && timestampMs <= bounds.nowMs) {
56
+ target.weekTokens += tokens;
57
+ }
58
+
59
+ if (timestampMs >= bounds.todayStartMs && timestampMs <= bounds.nowMs) {
60
+ target.todayTokens += tokens;
61
+ }
62
+ }
63
+
64
+ function remapCurrentProjectPathToLegacy(projectPath) {
65
+ if (!projectPath) {
66
+ return null;
67
+ }
68
+
69
+ const normalizedPath = path.resolve(projectPath);
70
+ if (
71
+ normalizedPath !== CURRENT_DEFAULT_WORKSPACES_ROOT &&
72
+ !normalizedPath.startsWith(CURRENT_DEFAULT_WORKSPACES_ROOT + path.sep)
73
+ ) {
74
+ return null;
75
+ }
76
+
77
+ return path.join(
78
+ LEGACY_DEFAULT_WORKSPACES_ROOT,
79
+ path.relative(CURRENT_DEFAULT_WORKSPACES_ROOT, normalizedPath),
80
+ );
81
+ }
82
+
83
+ function getClaudeProjectDirs(projectRef) {
84
+ const projectDirs = new Set();
85
+
86
+ if (projectRef?.fullPath) {
87
+ projectDirs.add(path.join(os.homedir(), '.claude', 'projects', encodeProjectPath(projectRef.fullPath)));
88
+
89
+ const legacyProjectPath = remapCurrentProjectPathToLegacy(projectRef.fullPath);
90
+ if (legacyProjectPath) {
91
+ projectDirs.add(path.join(os.homedir(), '.claude', 'projects', encodeProjectPath(legacyProjectPath)));
92
+ }
93
+ }
94
+
95
+ return [...projectDirs];
96
+ }
97
+
98
+ async function collectJsonlFiles(dirPath) {
99
+ const files = [];
100
+
101
+ try {
102
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
103
+ for (const entry of entries) {
104
+ const fullPath = path.join(dirPath, entry.name);
105
+ if (entry.isDirectory()) {
106
+ files.push(...await collectJsonlFiles(fullPath));
107
+ } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
108
+ files.push(fullPath);
109
+ }
110
+ }
111
+ } catch (error) {
112
+ if (error?.code !== 'ENOENT') {
113
+ console.warn(`[token-usage] Failed to read directory ${dirPath}:`, error.message);
114
+ }
115
+ }
116
+
117
+ return files;
118
+ }
119
+
120
+ function getClaudeUsageSnapshot(entry) {
121
+ const usage = entry?.message?.usage;
122
+ if (!usage) {
123
+ return null;
124
+ }
125
+
126
+ const model = entry?.message?.model;
127
+ if (model === '<synthetic>') {
128
+ return null;
129
+ }
130
+
131
+ const inputTokens = Number(usage.input_tokens || 0);
132
+ const outputTokens = Number(usage.output_tokens || 0);
133
+ // Keep Claude aligned with Codex/Gemini by counting model input/output only.
134
+ // Cache fields are metadata about prompt reuse and otherwise inflate dashboard totals.
135
+ const totalTokens = inputTokens + outputTokens;
136
+
137
+ return {
138
+ timestampMs: new Date(entry.timestamp || 0).getTime(),
139
+ inputTokens,
140
+ outputTokens,
141
+ totalTokens,
142
+ };
143
+ }
144
+
145
+ async function summarizeClaudeProject(projectRef, bounds) {
146
+ const totals = createEmptyUsageTotals();
147
+ const projectDirs = getClaudeProjectDirs(projectRef);
148
+ const jsonlFiles = (
149
+ await Promise.all(projectDirs.map((projectDir) => collectJsonlFiles(projectDir)))
150
+ ).flat();
151
+ const requestUsageMap = new Map();
152
+ let fallbackIndex = 0;
153
+
154
+ for (const filePath of jsonlFiles) {
155
+ try {
156
+ const fileStream = fsSync.createReadStream(filePath);
157
+ const rl = readline.createInterface({
158
+ input: fileStream,
159
+ crlfDelay: Infinity,
160
+ });
161
+
162
+ for await (const line of rl) {
163
+ if (!line.trim()) {
164
+ continue;
165
+ }
166
+
167
+ try {
168
+ const entry = JSON.parse(line);
169
+ if (entry.type !== 'assistant' || !entry.message?.usage) {
170
+ continue;
171
+ }
172
+
173
+ const snapshot = getClaudeUsageSnapshot(entry);
174
+ if (!snapshot || snapshot.totalTokens <= 0) {
175
+ continue;
176
+ }
177
+
178
+ const rawRequestId = typeof entry.requestId === 'string' ? entry.requestId.trim() : '';
179
+ const requestKey = rawRequestId || `${filePath}:${entry.uuid || entry.timestamp || fallbackIndex++}`;
180
+ const previous = requestUsageMap.get(requestKey);
181
+
182
+ if (!previous) {
183
+ requestUsageMap.set(requestKey, snapshot);
184
+ continue;
185
+ }
186
+
187
+ requestUsageMap.set(requestKey, {
188
+ timestampMs: Math.max(previous.timestampMs, snapshot.timestampMs),
189
+ inputTokens: Math.max(previous.inputTokens, snapshot.inputTokens),
190
+ outputTokens: Math.max(previous.outputTokens, snapshot.outputTokens),
191
+ totalTokens: Math.max(previous.totalTokens, snapshot.totalTokens),
192
+ });
193
+ } catch {
194
+ // Skip malformed JSONL rows.
195
+ }
196
+ }
197
+ } catch (error) {
198
+ console.warn(`[token-usage] Failed to read Claude session file ${filePath}:`, error.message);
199
+ }
200
+ }
201
+
202
+ for (const usage of requestUsageMap.values()) {
203
+ addUsageForTimestamp(totals, usage.timestampMs, usage.totalTokens, bounds);
204
+ }
205
+
206
+ return totals;
207
+ }
208
+
209
+ function getCodexCumulativeTokens(entry) {
210
+ const totalTokens = Number(entry?.payload?.info?.total_token_usage?.total_tokens || 0);
211
+ return Number.isFinite(totalTokens) ? totalTokens : 0;
212
+ }
213
+
214
+ function getCodexLastTokens(entry) {
215
+ const lastTokens = Number(entry?.payload?.info?.last_token_usage?.total_tokens || 0);
216
+ return Number.isFinite(lastTokens) ? lastTokens : 0;
217
+ }
218
+
219
+ async function summarizeCodexProject(projectRef, bounds, codexIndexRef) {
220
+ const totals = createEmptyUsageTotals();
221
+ const sessions = await getCodexSessions(projectRef.fullPath, {
222
+ limit: 0,
223
+ indexRef: codexIndexRef,
224
+ });
225
+
226
+ for (const session of sessions) {
227
+ if (!session?.filePath) {
228
+ continue;
229
+ }
230
+
231
+ let previousCumulativeTokens = 0;
232
+
233
+ try {
234
+ const fileStream = fsSync.createReadStream(session.filePath);
235
+ const rl = readline.createInterface({
236
+ input: fileStream,
237
+ crlfDelay: Infinity,
238
+ });
239
+
240
+ for await (const line of rl) {
241
+ if (!line.trim()) {
242
+ continue;
243
+ }
244
+
245
+ try {
246
+ const entry = JSON.parse(line);
247
+ if (entry.type !== 'event_msg' || entry.payload?.type !== 'token_count' || !entry.payload?.info) {
248
+ continue;
249
+ }
250
+
251
+ const timestampMs = new Date(entry.timestamp || 0).getTime();
252
+ const cumulativeTokens = getCodexCumulativeTokens(entry);
253
+ let deltaTokens = 0;
254
+
255
+ if (cumulativeTokens > previousCumulativeTokens) {
256
+ deltaTokens = cumulativeTokens - previousCumulativeTokens;
257
+ previousCumulativeTokens = cumulativeTokens;
258
+ } else if (cumulativeTokens < previousCumulativeTokens) {
259
+ const lastTokens = getCodexLastTokens(entry);
260
+ if (lastTokens > 0) {
261
+ deltaTokens = lastTokens;
262
+ }
263
+ previousCumulativeTokens = Math.max(cumulativeTokens, 0);
264
+ }
265
+
266
+ addUsageForTimestamp(totals, timestampMs, deltaTokens, bounds);
267
+ } catch {
268
+ // Skip malformed JSONL rows.
269
+ }
270
+ }
271
+ } catch (error) {
272
+ console.warn(`[token-usage] Failed to read Codex session file ${session.filePath}:`, error.message);
273
+ }
274
+ }
275
+
276
+ return totals;
277
+ }
278
+
279
+ function getGeminiUsedTokens(stats) {
280
+ const totalTokens = Number(stats?.total_tokens || 0);
281
+ if (totalTokens > 0) {
282
+ return totalTokens;
283
+ }
284
+
285
+ const inputTokens = Number(stats?.input_tokens || 0);
286
+ const outputTokens = Number(stats?.output_tokens || 0);
287
+ return inputTokens + outputTokens;
288
+ }
289
+
290
+ async function summarizeGeminiProject(projectRef, bounds) {
291
+ const totals = createEmptyUsageTotals();
292
+ const sessions = await getGeminiSessions(projectRef.fullPath, { limit: 0 });
293
+
294
+ for (const session of sessions) {
295
+ if (!session?.filePath) {
296
+ continue;
297
+ }
298
+
299
+ const seenStatusEvents = new Set();
300
+
301
+ try {
302
+ const fileStream = fsSync.createReadStream(session.filePath);
303
+ const rl = readline.createInterface({
304
+ input: fileStream,
305
+ crlfDelay: Infinity,
306
+ });
307
+
308
+ for await (const line of rl) {
309
+ if (!line.trim()) {
310
+ continue;
311
+ }
312
+
313
+ try {
314
+ const entry = JSON.parse(line);
315
+ if (entry.type !== 'status' || !entry.stats) {
316
+ continue;
317
+ }
318
+
319
+ const usedTokens = getGeminiUsedTokens(entry.stats);
320
+ if (usedTokens <= 0) {
321
+ continue;
322
+ }
323
+
324
+ const timestampMs = new Date(entry.timestamp || 0).getTime();
325
+ const dedupeKey = [
326
+ entry.timestamp || '',
327
+ usedTokens,
328
+ Number(entry.stats.input_tokens || 0),
329
+ Number(entry.stats.output_tokens || 0),
330
+ Number(entry.stats.cache_creation_input_tokens || 0),
331
+ Number(entry.stats.cache_read_input_tokens || 0),
332
+ ].join('|');
333
+
334
+ if (seenStatusEvents.has(dedupeKey)) {
335
+ continue;
336
+ }
337
+ seenStatusEvents.add(dedupeKey);
338
+
339
+ addUsageForTimestamp(totals, timestampMs, usedTokens, bounds);
340
+ } catch {
341
+ // Skip malformed JSONL rows.
342
+ }
343
+ }
344
+ } catch (error) {
345
+ console.warn(`[token-usage] Failed to read Gemini session file ${session.filePath}:`, error.message);
346
+ }
347
+ }
348
+
349
+ return totals;
350
+ }
351
+
352
+ function mergeUsageTotals(...totalsList) {
353
+ return totalsList.reduce((merged, totals) => ({
354
+ todayTokens: merged.todayTokens + Number(totals?.todayTokens || 0),
355
+ weekTokens: merged.weekTokens + Number(totals?.weekTokens || 0),
356
+ }), createEmptyUsageTotals());
357
+ }
358
+
359
+ export async function getProjectTokenUsageSummary(projectRefs = []) {
360
+ const normalizedProjectRefs = normalizeProjectRefs(projectRefs);
361
+ const bounds = getUsageWindowBounds();
362
+ const cacheKey = `${bounds.cacheKey}|${JSON.stringify(normalizedProjectRefs)}`;
363
+
364
+ if (summaryCache && summaryCache.key === cacheKey && summaryCache.expiresAt > Date.now()) {
365
+ return summaryCache.data;
366
+ }
367
+
368
+ const codexIndexRef = { sessionsByProject: null };
369
+ const projectUsageEntries = await Promise.all(
370
+ normalizedProjectRefs.map(async (projectRef) => {
371
+ const [claudeTotals, codexTotals, geminiTotals] = await Promise.all([
372
+ summarizeClaudeProject(projectRef, bounds),
373
+ summarizeCodexProject(projectRef, bounds, codexIndexRef),
374
+ summarizeGeminiProject(projectRef, bounds),
375
+ ]);
376
+
377
+ return [
378
+ projectRef.name,
379
+ mergeUsageTotals(claudeTotals, codexTotals, geminiTotals),
380
+ ];
381
+ }),
382
+ );
383
+
384
+ const projects = Object.fromEntries(projectUsageEntries);
385
+ const workspace = Object.values(projects).reduce(
386
+ (accumulator, totals) => mergeUsageTotals(accumulator, totals),
387
+ createEmptyUsageTotals(),
388
+ );
389
+
390
+ const data = {
391
+ generatedAt: new Date().toISOString(),
392
+ workspace,
393
+ projects,
394
+ };
395
+
396
+ summaryCache = {
397
+ key: cacheKey,
398
+ expiresAt: Date.now() + CACHE_TTL_MS,
399
+ data,
400
+ };
401
+
402
+ return data;
403
+ }
404
+
405
+ export function clearProjectTokenUsageSummaryCache() {
406
+ summaryCache = null;
407
+ }