farming-code 2.2.6

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 (453) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/README.zh_cn.md +329 -0
  4. package/SECURITY.md +29 -0
  5. package/THIRD_PARTY_NOTICES.md +106 -0
  6. package/backend/acp/client-services.js +312 -0
  7. package/backend/acp/patch-decisions.js +137 -0
  8. package/backend/acp/permission-security.js +108 -0
  9. package/backend/acp-runtime.js +1144 -0
  10. package/backend/acp-session-state.js +448 -0
  11. package/backend/acp-transcript.js +169 -0
  12. package/backend/agent-activity.js +186 -0
  13. package/backend/agent-env.js +250 -0
  14. package/backend/agent-json-stream.js +268 -0
  15. package/backend/agent-manager.js +4259 -0
  16. package/backend/agent-memory-report.js +583 -0
  17. package/backend/agent-order.js +162 -0
  18. package/backend/agent-provider-session.js +287 -0
  19. package/backend/agent-session-history.js +888 -0
  20. package/backend/app-server-api.js +917 -0
  21. package/backend/async-cache.js +94 -0
  22. package/backend/auth.js +178 -0
  23. package/backend/claude-settings.js +121 -0
  24. package/backend/cli-agents.js +484 -0
  25. package/backend/codex-app-server-home.js +39 -0
  26. package/backend/codex-app-server-runtime.js +901 -0
  27. package/backend/codex-context-window.js +189 -0
  28. package/backend/codex-models.js +229 -0
  29. package/backend/codex-rollout-follower.js +243 -0
  30. package/backend/codex-session-history.js +553 -0
  31. package/backend/codex-transcript-sanitizer.js +103 -0
  32. package/backend/codex-transcript.js +2341 -0
  33. package/backend/command-runner-child.js +67 -0
  34. package/backend/config-manager.js +815 -0
  35. package/backend/control-api.js +151 -0
  36. package/backend/data/chinese-poetic-words.json +8223 -0
  37. package/backend/data/poetic-word-sources/china.json +49209 -0
  38. package/backend/data/poetic-word-sources/india.json +9763 -0
  39. package/backend/data/poetic-word-sources/japan.json +8455 -0
  40. package/backend/executable-discovery.js +207 -0
  41. package/backend/farming-app-cli.js +790 -0
  42. package/backend/farming-cli.js +400 -0
  43. package/backend/farming-session-store.js +373 -0
  44. package/backend/haiku-token.js +772 -0
  45. package/backend/index-html.js +49 -0
  46. package/backend/input-parts.js +54 -0
  47. package/backend/input-routing.js +24 -0
  48. package/backend/json-cli-runtime.js +161 -0
  49. package/backend/local-session-engine.js +620 -0
  50. package/backend/main-agent-skills.js +292 -0
  51. package/backend/main-page-session.js +119 -0
  52. package/backend/native-pty-host-client.js +516 -0
  53. package/backend/native-pty-host-path.js +22 -0
  54. package/backend/native-pty-host.js +816 -0
  55. package/backend/native-session-engine.js +196 -0
  56. package/backend/network.js +23 -0
  57. package/backend/npm-update-helper.js +196 -0
  58. package/backend/packaged-node-pty.js +79 -0
  59. package/backend/provider-session-id.js +47 -0
  60. package/backend/qr-share-tickets.js +100 -0
  61. package/backend/review-diff-router.js +180 -0
  62. package/backend/review-diff-service.js +1270 -0
  63. package/backend/review-session-router.js +74 -0
  64. package/backend/review-session-service.js +432 -0
  65. package/backend/review-session-store.js +144 -0
  66. package/backend/review-state-router.js +116 -0
  67. package/backend/review-state-store.js +287 -0
  68. package/backend/run-history-store.js +70 -0
  69. package/backend/server.js +2446 -0
  70. package/backend/session-engine-bridge.js +110 -0
  71. package/backend/session-engine-router.js +66 -0
  72. package/backend/session-engine.js +39 -0
  73. package/backend/shell-busy-integration.js +472 -0
  74. package/backend/slash-command-discovery.js +296 -0
  75. package/backend/storage-layout.js +101 -0
  76. package/backend/system-monitor.js +77 -0
  77. package/backend/terminal-runtime-cleanup.js +80 -0
  78. package/backend/terminal-screen-state.js +240 -0
  79. package/backend/terminal-screen-worker-pool.js +165 -0
  80. package/backend/terminal-screen-worker-thread.js +161 -0
  81. package/backend/terminal-screen-worker.js +255 -0
  82. package/backend/terminal-status.js +356 -0
  83. package/backend/theme-manager.js +145 -0
  84. package/backend/update-service.js +1183 -0
  85. package/backend/usage-forecast.js +86 -0
  86. package/backend/usage-monitor.js +1357 -0
  87. package/backend/workspace-discovery.js +311 -0
  88. package/backend/workspace-file-router.js +431 -0
  89. package/backend/workspace-file-service.js +2765 -0
  90. package/bin/farming +5 -0
  91. package/config/farming.deploy.env.example +13 -0
  92. package/config/farming.install.env.example +17 -0
  93. package/dist/assets/App-BrP-ENHg.css +1 -0
  94. package/dist/assets/App-Dza5yEDe.js +124 -0
  95. package/dist/assets/FileEditorMarkdownPreview-DRDNQ8mX.css +1 -0
  96. package/dist/assets/FileEditorMarkdownPreview-elKWc8Im.js +404 -0
  97. package/dist/assets/FileEditorPane-BQLocXOp.js +5 -0
  98. package/dist/assets/IconGlyphs-RkMAdBXF.js +1 -0
  99. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  100. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  101. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  102. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  103. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  104. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  105. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  106. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  107. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  108. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  109. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  110. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  111. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  112. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  113. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  114. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  115. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  116. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  117. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  118. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  119. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  120. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  121. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  122. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  123. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  124. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  125. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  126. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  127. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  128. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  129. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  130. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  131. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  132. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  133. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  134. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  135. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  136. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  137. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  138. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  139. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  140. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  141. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  142. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  143. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  144. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  145. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  146. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  147. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  148. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  149. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  150. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  151. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  152. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  153. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  154. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  155. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  156. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  157. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  158. package/dist/assets/ProjectFilesSection-lS1azbgj.js +12 -0
  159. package/dist/assets/ReviewPage-DgnmgU2T.js +3 -0
  160. package/dist/assets/abap-Cc5D5h5i.js +1 -0
  161. package/dist/assets/abnfDiagram-VRR7QNED-BgawzDQT.js +1 -0
  162. package/dist/assets/apex-BKopwLmx.js +1 -0
  163. package/dist/assets/api-D1lyBYIQ.js +1 -0
  164. package/dist/assets/arc-CWoVlxJw.js +1 -0
  165. package/dist/assets/architecture-TIHT7OUA-CbJBFh3w.js +1 -0
  166. package/dist/assets/architectureDiagram-ZJ3FMSHR-DUShwPg0.js +36 -0
  167. package/dist/assets/array-HeX70jSN.js +1 -0
  168. package/dist/assets/azcli-DI-C34WA.js +1 -0
  169. package/dist/assets/bat-Cbp6u42z.js +1 -0
  170. package/dist/assets/bicep-4QA1jTCM.js +2 -0
  171. package/dist/assets/blockDiagram-677ZJIJ3-CeGZviXR.js +132 -0
  172. package/dist/assets/c4Diagram-LMCZKHZV-CLhYht6S.js +10 -0
  173. package/dist/assets/cameligo-Dda_adPs.js +1 -0
  174. package/dist/assets/channel-Uw90AO0b.js +1 -0
  175. package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +1 -0
  176. package/dist/assets/chunk-32BRIVSS-LFYpT-Cu.js +1 -0
  177. package/dist/assets/chunk-52WLFC77-BH6jDw9J.js +10 -0
  178. package/dist/assets/chunk-5FCAYU7R-C7AmIhYv.js +1 -0
  179. package/dist/assets/chunk-5HE753X5-C8cXaouP.js +1 -0
  180. package/dist/assets/chunk-5JV3BV7I-C1PtimcA.js +1 -0
  181. package/dist/assets/chunk-5TONJI2A-_81zUME8.js +2 -0
  182. package/dist/assets/chunk-5VM5RSS4-Dlw7AXWp.js +15 -0
  183. package/dist/assets/chunk-7BUUIJ7U-ZBRhjLOh.js +1 -0
  184. package/dist/assets/chunk-BIQX33UG-CWmzxeAw.js +1 -0
  185. package/dist/assets/chunk-C7G6YPKG-B_mIUlb7.js +1 -0
  186. package/dist/assets/chunk-CQNSW5MT-CMayolwI.js +1 -0
  187. package/dist/assets/chunk-CYSBUYHQ-lSPbrL3Z.js +1 -0
  188. package/dist/assets/chunk-DECur_0Z.js +1 -0
  189. package/dist/assets/chunk-EMLP6XTP-D9-jtCnM.js +1 -0
  190. package/dist/assets/chunk-EX3LRPZG-C63hVuAJ.js +231 -0
  191. package/dist/assets/chunk-FWX5IMBZ-CboX-WF_.js +2 -0
  192. package/dist/assets/chunk-HOUHSVGY-BOgSd6Un.js +1 -0
  193. package/dist/assets/chunk-ICXQ74PX-C0K-pgZt.js +2 -0
  194. package/dist/assets/chunk-JG7HCLWE-Cib606u_.js +2 -0
  195. package/dist/assets/chunk-JWPE2WC7-DSyBRaRG.js +1 -0
  196. package/dist/assets/chunk-KEIR6QF5-CNOHik0T.js +161 -0
  197. package/dist/assets/chunk-MOJQB5TN-CVMfwN_7.js +88 -0
  198. package/dist/assets/chunk-MOZMSUNE-D6JeZqmE.js +1 -0
  199. package/dist/assets/chunk-OGEWGWER-DLT3pxyw.js +1 -0
  200. package/dist/assets/chunk-OSBZ3O6U-C0W8KSeg.js +1 -0
  201. package/dist/assets/chunk-PUDLZKDR-Cd079yIQ.js +156 -0
  202. package/dist/assets/chunk-Q4XR5HBZ-D0jHa6SO.js +70 -0
  203. package/dist/assets/chunk-QBLGF6JB-p4omp7uO.js +1 -0
  204. package/dist/assets/chunk-R7FJI6CG-CZ_p9hSr.js +1 -0
  205. package/dist/assets/chunk-RYQCIY6F-CZA9pvFX.js +1 -0
  206. package/dist/assets/chunk-U6XO7XAA-CcXKsWxp.js +2 -0
  207. package/dist/assets/chunk-V7JOEXUC-CVDtR75H.js +206 -0
  208. package/dist/assets/chunk-VAUOI2AC-B-W2_9Ot.js +1 -0
  209. package/dist/assets/chunk-VR4S4FIN-FD3kWh32.js +1 -0
  210. package/dist/assets/chunk-WYO6CB5R-C2bGaS5r.js +127 -0
  211. package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +1 -0
  212. package/dist/assets/chunk-Y2CYZVJY-Di0sYzi1.js +1 -0
  213. package/dist/assets/chunk-YOTPTUD7-BXHmEk89.js +1 -0
  214. package/dist/assets/chunk-ZGVPDNZ5-ymgts0zo.js +62 -0
  215. package/dist/assets/chunk-ZIRB5QZD-DncwlQQc.js +32 -0
  216. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +1 -0
  217. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +1 -0
  218. package/dist/assets/clojure-CvY8A83L.js +1 -0
  219. package/dist/assets/code-dark-Cjb1QGhu.css +1 -0
  220. package/dist/assets/code-mobile-3C2Rr-CN.css +1 -0
  221. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  222. package/dist/assets/coffee-4LX2Sv8A.js +1 -0
  223. package/dist/assets/core-ZlAPicox.js +3 -0
  224. package/dist/assets/cose-bilkent-JH36ORCC-BNIAz0nN.js +1 -0
  225. package/dist/assets/cpp-D4AzSV28.js +1 -0
  226. package/dist/assets/csharp-C4nWN4Yd.js +1 -0
  227. package/dist/assets/csp-CiEaaS5Z.js +1 -0
  228. package/dist/assets/css-DXxBRsbk.js +3 -0
  229. package/dist/assets/css.worker-DyDu5ynT.js +89 -0
  230. package/dist/assets/cssMode-DDWxTPm_.js +1 -0
  231. package/dist/assets/cynefin-VYW2F7L2-DgXGHlRt.js +1 -0
  232. package/dist/assets/cynefinDiagram-TSTJHNR4-CwpobXIl.js +62 -0
  233. package/dist/assets/cypher-DOMzqyFA.js +1 -0
  234. package/dist/assets/cytoscape.esm-Cl8iC3D7.js +321 -0
  235. package/dist/assets/dagre-DXlcHgr3.js +1 -0
  236. package/dist/assets/dagre-VKFMJZFB-Dkhlfjna.js +4 -0
  237. package/dist/assets/dart-BWS7MXt_.js +1 -0
  238. package/dist/assets/defaultLocale-GJwWH1Jr.js +1 -0
  239. package/dist/assets/diagram-FQU43EPY-hA8dicp6.js +3 -0
  240. package/dist/assets/diagram-G47NLZAW-BTY6JTzO.js +24 -0
  241. package/dist/assets/diagram-NH7WQ7WH-xkaHHX9M.js +24 -0
  242. package/dist/assets/diagram-OA4YK3LP-gk_fg9UU.js +30 -0
  243. package/dist/assets/diagram-WEI45ONY-BvC8xd6T.js +41 -0
  244. package/dist/assets/dist-ClAL-Tv5.js +1 -0
  245. package/dist/assets/dockerfile-Baf4U3xT.js +1 -0
  246. package/dist/assets/ebnfDiagram-CCIWWBDH-CR7RaNat.js +1 -0
  247. package/dist/assets/ecl-CT7udlkQ.js +1 -0
  248. package/dist/assets/editor-CtbFZlk0.css +1 -0
  249. package/dist/assets/editor.api2-B9h6iFPK.js +872 -0
  250. package/dist/assets/editor.worker-Dpt_xPrs.js +26 -0
  251. package/dist/assets/elixir-CIbAZw5l.js +1 -0
  252. package/dist/assets/erDiagram-Q63AITRT-_M-JFPbj.js +85 -0
  253. package/dist/assets/eventmodeling-45OFAUF4-DXYTQ2nt.js +1 -0
  254. package/dist/assets/file-icons-EFUGSSwf.js +1 -0
  255. package/dist/assets/flow9-Dqiw45WV.js +1 -0
  256. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +1 -0
  257. package/dist/assets/freemarker2-QBsTERH-.js +3 -0
  258. package/dist/assets/fsharp-E8E2L9jU.js +1 -0
  259. package/dist/assets/ganttDiagram-NO4QXBWP-Bl8P1yf_.js +292 -0
  260. package/dist/assets/gitGraph-TEB2WS4Q-CzTBjEvV.js +1 -0
  261. package/dist/assets/gitGraphDiagram-IHSO6WYX-DoHa5ysz.js +106 -0
  262. package/dist/assets/go-CouVSN4D.js +1 -0
  263. package/dist/assets/graphlib-CbSvZcZU.js +1 -0
  264. package/dist/assets/graphql-a6h6h5-t.js +1 -0
  265. package/dist/assets/handlebars-CuoxPo5A.js +1 -0
  266. package/dist/assets/hcl-vso3e6N-.js +1 -0
  267. package/dist/assets/html-gX5PEu0Q.js +1 -0
  268. package/dist/assets/html.worker-IWNXhnIC.js +502 -0
  269. package/dist/assets/htmlMode-DIWUbUxd.js +1 -0
  270. package/dist/assets/index-BpHPeJf0.js +10 -0
  271. package/dist/assets/info-DKCQHKI2-DznP7j3s.js +1 -0
  272. package/dist/assets/infoDiagram-FWYZ7A6U-DSFcSuUs.js +2 -0
  273. package/dist/assets/ini-SwZZkotR.js +1 -0
  274. package/dist/assets/init-BPLPMQ3Y.js +1 -0
  275. package/dist/assets/ishikawaDiagram-FXEZZL3T-DdkqullI.js +70 -0
  276. package/dist/assets/java-CtRLC4_3.js +1 -0
  277. package/dist/assets/javascript-BYKGIUGs.js +1 -0
  278. package/dist/assets/journeyDiagram-5HDEW3XC-B4hRnQJr.js +139 -0
  279. package/dist/assets/json.worker-c4h5s80L.js +58 -0
  280. package/dist/assets/jsonMode-AFmQQP6u.js +7 -0
  281. package/dist/assets/jsx-runtime-BthL93pI.js +1 -0
  282. package/dist/assets/julia-CuJWFKQH.js +1 -0
  283. package/dist/assets/kanban-definition-HUTT4EX6-tm2FZB2E.js +89 -0
  284. package/dist/assets/katex-C8DPyI6s.js +257 -0
  285. package/dist/assets/kotlin-B_kBhsre.js +1 -0
  286. package/dist/assets/less-CtrHIfz3.js +2 -0
  287. package/dist/assets/lexon-yVt1pJLu.js +1 -0
  288. package/dist/assets/line-CmsjK4kn.js +1 -0
  289. package/dist/assets/linear-Dld3_ERu.js +1 -0
  290. package/dist/assets/liquid-DBIl-f0z.js +1 -0
  291. package/dist/assets/lspLanguageFeatures-DK6tqicV.js +4 -0
  292. package/dist/assets/lua-prOwgJ4U.js +1 -0
  293. package/dist/assets/m3-DhrdTQ84.js +1 -0
  294. package/dist/assets/main-DTJwH51M.css +1 -0
  295. package/dist/assets/map-LZWWoeWa.js +1 -0
  296. package/dist/assets/markdown-DoI5HwSM.js +1 -0
  297. package/dist/assets/mdx-DS-bVtZN.js +1 -0
  298. package/dist/assets/mermaid-parser.core-BQtepoLd.js +4 -0
  299. package/dist/assets/mermaid.core-C7UT_EEL.js +11 -0
  300. package/dist/assets/mindmap-definition-LN4V7U3C-DplfjGms.js +96 -0
  301. package/dist/assets/mips-Dye5jsBU.js +1 -0
  302. package/dist/assets/monaco.contribution-CyLosfZI.js +2 -0
  303. package/dist/assets/msdax-TkNOyIL8.js +1 -0
  304. package/dist/assets/mysql-CEc-RKZF.js +1 -0
  305. package/dist/assets/objective-c-CqqCsQz0.js +1 -0
  306. package/dist/assets/ordinal-BLrOss5K.js +1 -0
  307. package/dist/assets/packet-7NZHBO7P-BDEPOPha.js +1 -0
  308. package/dist/assets/pascal-DO_pj8yg.js +1 -0
  309. package/dist/assets/pascaligo-1cbdZdF3.js +1 -0
  310. package/dist/assets/path-YdFzr2W6.js +1 -0
  311. package/dist/assets/pegDiagram-2B236MQR-lu3SdkQs.js +1 -0
  312. package/dist/assets/perl-B_N3PYfC.js +1 -0
  313. package/dist/assets/pgsql-9iYk1FQ7.js +1 -0
  314. package/dist/assets/php-rO2sgoUM.js +1 -0
  315. package/dist/assets/pie-RZYD4A2V-DWIrwPJQ.js +1 -0
  316. package/dist/assets/pieDiagram-ENE6RG2P-3fHbmDf9.js +39 -0
  317. package/dist/assets/pla-Cbgv60kq.js +1 -0
  318. package/dist/assets/postiats-y6xrhy_M.js +1 -0
  319. package/dist/assets/powerquery-BaD8gWNE.js +1 -0
  320. package/dist/assets/powershell-BMhG4JVe.js +1 -0
  321. package/dist/assets/preload-helper-mJowh_hw.js +1 -0
  322. package/dist/assets/protobuf-CdOP5kN3.js +2 -0
  323. package/dist/assets/pug-Ck60hNF9.js +1 -0
  324. package/dist/assets/python-C93q3xXz.js +1 -0
  325. package/dist/assets/qoder-C9LmmOSf.svg +1 -0
  326. package/dist/assets/qoder-Cf9gl0Y5.svg +1 -0
  327. package/dist/assets/qoder-gHCinseV.svg +1 -0
  328. package/dist/assets/qrcode-DV-_rjU8.js +3 -0
  329. package/dist/assets/qsharp-BcaKFfP-.js +1 -0
  330. package/dist/assets/quadrantDiagram-ABIIQ3AL-a5qoWz-X.js +7 -0
  331. package/dist/assets/r-rFNes0iK.js +1 -0
  332. package/dist/assets/radar-I7S5WNFK-B7OaD8vp.js +1 -0
  333. package/dist/assets/railroad-3IZDKUUU-BXW2nuCA.js +1 -0
  334. package/dist/assets/railroad-abnf-AHOZXSZD-Buw9QG2r.js +1 -0
  335. package/dist/assets/railroad-ebnf-EBAXGLYW-BjSi4sWJ.js +1 -0
  336. package/dist/assets/railroad-peg-LSFZ7HO6-DN1wIVhS.js +1 -0
  337. package/dist/assets/railroadDiagram-RFXS5EU6-D2TOuXW9.js +1 -0
  338. package/dist/assets/razor-CBifNGXO.js +1 -0
  339. package/dist/assets/react-dom-gtU6X-Hj.js +1 -0
  340. package/dist/assets/redis-DkpQ3K6G.js +1 -0
  341. package/dist/assets/redshift-Di9N5TI3.js +1 -0
  342. package/dist/assets/requirementDiagram-TGXJPOKE-LQBWpUvd.js +84 -0
  343. package/dist/assets/restructuredtext-By5ZzLt6.js +1 -0
  344. package/dist/assets/review-BcnwQtJv.css +1 -0
  345. package/dist/assets/rough.esm-c4PR5shF.js +1 -0
  346. package/dist/assets/ruby-fF5zGiLp.js +1 -0
  347. package/dist/assets/rust-C5b3AYY6.js +1 -0
  348. package/dist/assets/sankeyDiagram-HTMAVEWB-B1ohYoyK.js +40 -0
  349. package/dist/assets/sb-CD07UNRs.js +1 -0
  350. package/dist/assets/scala-jtn1ip9G.js +1 -0
  351. package/dist/assets/scheme-C4vqMkn_.js +1 -0
  352. package/dist/assets/scss-I4Glxjbz.js +3 -0
  353. package/dist/assets/sequenceDiagram-DBY2YBRQ-C3vM-hMq.js +162 -0
  354. package/dist/assets/shell-DubUcuns.js +1 -0
  355. package/dist/assets/sizeCapture-X5ZJPWSS-daTB6OcF.js +1 -0
  356. package/dist/assets/solidity-CNy2KSl2.js +1 -0
  357. package/dist/assets/sophia-D9_URt84.js +1 -0
  358. package/dist/assets/sparql-DviXpm4C.js +1 -0
  359. package/dist/assets/sql-vDRbAZgK.js +1 -0
  360. package/dist/assets/src-WBW1QxJt.js +1 -0
  361. package/dist/assets/st-CnpnRqA7.js +1 -0
  362. package/dist/assets/stateDiagram-2N3HPSRC-BvFhOryI.js +1 -0
  363. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +1 -0
  364. package/dist/assets/swift-D_g8Ganz.js +1 -0
  365. package/dist/assets/swimlanes-5IMT3BWC-DKF_b-2s.js +2 -0
  366. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +8 -0
  367. package/dist/assets/systemverilog-OuZa5h1M.js +1 -0
  368. package/dist/assets/tcl-DBm9ucsV.js +1 -0
  369. package/dist/assets/timeline-definition-FHXFAJF6-vAs11ux_.js +120 -0
  370. package/dist/assets/tokens-BwenNn5Q.css +1 -0
  371. package/dist/assets/treeView-QDETBFTQ-BVfICaZQ.js +1 -0
  372. package/dist/assets/treemap-6X3UGDF4-oWHPqo17.js +1 -0
  373. package/dist/assets/ts.worker-BwM5Ha3L.js +67719 -0
  374. package/dist/assets/tsMode-DGZTlTj8.js +11 -0
  375. package/dist/assets/twig-BRzEqX5Y.js +1 -0
  376. package/dist/assets/typescript-KJKfYO7K.js +1 -0
  377. package/dist/assets/typespec-D4ZgGeX8.js +1 -0
  378. package/dist/assets/useWorkspaceMenuKeyboard-CneKAZUJ.js +1 -0
  379. package/dist/assets/vb-DcP4s6WS.js +1 -0
  380. package/dist/assets/vennDiagram-L72KCM5P-BybTX_9B.js +34 -0
  381. package/dist/assets/wardley-OPB4EBWU-Dg8t5y3s.js +1 -0
  382. package/dist/assets/wardleyDiagram-EHGQE667-CicUmMBR.js +78 -0
  383. package/dist/assets/wgsl-CnRJ_QUf.js +298 -0
  384. package/dist/assets/workers-BSQRIkqa.js +1 -0
  385. package/dist/assets/workspace-view-state-CTyDzk2D.js +1 -0
  386. package/dist/assets/xml-C8FAcN6j.js +1 -0
  387. package/dist/assets/xychartDiagram-FW5EYKEG-BhWDyCX-.js +7 -0
  388. package/dist/assets/yaml-BQ4NrRhG.js +1 -0
  389. package/dist/assets/zsh-CLpveKlF.svg +1 -0
  390. package/dist/assets/zsh-FxSpMPbz.svg +1 -0
  391. package/dist/farming-2/app-icon-v2-180.png +0 -0
  392. package/dist/farming-2/app-icon-v2-192.png +0 -0
  393. package/dist/farming-2/app-icon-v2-512.png +0 -0
  394. package/dist/farming-2/app-icon-v2-maskable-512.png +0 -0
  395. package/dist/farming-2/app-icon-v2-master.png +0 -0
  396. package/dist/farming-2/favicon-v2-16.png +0 -0
  397. package/dist/farming-2/favicon-v2-32.png +0 -0
  398. package/dist/farming-2/favicon-v2-48.png +0 -0
  399. package/dist/farming-2/favicon-v2-64.png +0 -0
  400. package/dist/farming-2/favicon-v2.ico +0 -0
  401. package/dist/farming-2/favicon.ico +0 -0
  402. package/dist/farming-2/github-social-preview.png +0 -0
  403. package/dist/farming-2/images/avatar-watercolor-v1-bee-garden.png +0 -0
  404. package/dist/farming-2/images/avatar-watercolor-v2.png +0 -0
  405. package/dist/farming-2/images/avatar-watercolor-v3.png +0 -0
  406. package/dist/farming-2/legacy-icons-v1/favicon.ico +1 -0
  407. package/dist/farming-2/legacy-icons-v1/logo-16.png +0 -0
  408. package/dist/farming-2/legacy-icons-v1/logo-180.png +0 -0
  409. package/dist/farming-2/legacy-icons-v1/logo-192.png +0 -0
  410. package/dist/farming-2/legacy-icons-v1/logo-32.png +0 -0
  411. package/dist/farming-2/legacy-icons-v1/logo-48.png +0 -0
  412. package/dist/farming-2/legacy-icons-v1/logo-512.png +0 -0
  413. package/dist/farming-2/legacy-icons-v1/logo-64.png +0 -0
  414. package/dist/farming-2/logo-16.png +0 -0
  415. package/dist/farming-2/logo-180.png +0 -0
  416. package/dist/farming-2/logo-192.png +0 -0
  417. package/dist/farming-2/logo-32.png +0 -0
  418. package/dist/farming-2/logo-48.png +0 -0
  419. package/dist/farming-2/logo-512.png +0 -0
  420. package/dist/farming-2/logo-64.png +0 -0
  421. package/dist/farming-2/site.webmanifest +27 -0
  422. package/dist/farming-2/style_capsules/watercolor-pastoral.json +94 -0
  423. package/dist/index.html +27 -0
  424. package/frontend/ghostty-loader.js +24 -0
  425. package/frontend/runtime-paths.js +28 -0
  426. package/frontend/session-bridge.js +82 -0
  427. package/frontend/session-modal-bridge.js +331 -0
  428. package/frontend/skin-bridge.js +25 -0
  429. package/frontend/skins/crt/app.js +6940 -0
  430. package/frontend/skins/crt/assets/branding/farming-crt-icon.svg +11 -0
  431. package/frontend/skins/crt/assets/branding/farming-crt-logo-v1.png +0 -0
  432. package/frontend/skins/crt/assets/effects/phosphor-noise.svg +11 -0
  433. package/frontend/skins/crt/assets/fonts/departure-mono/DepartureMonoNerdFontMono-Regular.otf +0 -0
  434. package/frontend/skins/crt/assets/fonts/departure-mono/LICENSE +93 -0
  435. package/frontend/skins/crt/effects.js +8 -0
  436. package/frontend/skins/crt/index.html +1712 -0
  437. package/frontend/skins/crt/styles/billing.css +1003 -0
  438. package/frontend/skins/crt/styles/effects.css +171 -0
  439. package/frontend/skins/crt/styles/history.css +215 -0
  440. package/frontend/skins/crt/styles/monochrome-green.css +509 -0
  441. package/frontend/skins/crt/styles/search.css +271 -0
  442. package/frontend/terminal-bridge.js +192 -0
  443. package/frontend/theme-loader.js +84 -0
  444. package/frontend/themes/terminal/theme.json +24 -0
  445. package/frontend/vendor/ghostty-web/LICENSE +21 -0
  446. package/frontend/vendor/ghostty-web/__vite-browser-external-2447137e.js +4 -0
  447. package/frontend/vendor/ghostty-web/ghostty-vt.wasm +0 -0
  448. package/frontend/vendor/ghostty-web/ghostty-web.js +2963 -0
  449. package/frontend/vendor/ghostty-web/ghostty-web.umd.cjs +13 -0
  450. package/frontend/vendor/ghostty-web/index.d.ts +1842 -0
  451. package/index.html +23 -0
  452. package/package.json +144 -0
  453. package/scripts/sync-ghostty-vendor.js +66 -0
@@ -0,0 +1,1357 @@
1
+ const fsp = require('fs/promises');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const readline = require('readline');
5
+ const { execFile, spawn } = require('child_process');
6
+ const { promisify } = require('util');
7
+ const { attachQuotaForecasts } = require('./usage-forecast');
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ const USAGE_WINDOW_MS = 5 * 60 * 1000;
12
+ const USAGE_TIMELINE_WINDOW_MS = 60 * 60 * 1000;
13
+ const USAGE_TIMELINE_BUCKET_COUNT = 30;
14
+ const USAGE_DAILY_DAYS = 52 * 7;
15
+ const USAGE_DAILY_CACHE_MS = 5 * 60 * 1000;
16
+ const JSONL_FILE_LIMIT = 60;
17
+ const JSONL_SCAN_LIMIT = 2000;
18
+ const DAILY_JSONL_FILE_LIMIT = 5000;
19
+ const DAILY_JSONL_SCAN_LIMIT = 20_000;
20
+ const JSONL_TAIL_BYTES = 2 * 1024 * 1024;
21
+ const COMMAND_TIMEOUT_MS = 2500;
22
+ const OPENCODE_COMMAND_TIMEOUT_MS = 20_000;
23
+ const OPENCODE_EXPORT_CONCURRENCY = 4;
24
+ const OPENCODE_SESSION_LIMIT = 5000;
25
+ const dailyFileEventCache = new Map();
26
+ const openCodeSessionEventCache = new Map();
27
+ let nativeRipgrepPathPromise = null;
28
+
29
+ function numberOrNull(value) {
30
+ const numberValue = Number(value);
31
+ return Number.isFinite(numberValue) ? numberValue : null;
32
+ }
33
+
34
+ function roundRate(value) {
35
+ if (!Number.isFinite(value)) return 0;
36
+ return Math.round(value * 10) / 10;
37
+ }
38
+
39
+ function normalizeEpochMs(value) {
40
+ const numberValue = numberOrNull(value);
41
+ if (numberValue === null || numberValue <= 0) return null;
42
+ return numberValue < 10_000_000_000 ? numberValue * 1000 : numberValue;
43
+ }
44
+
45
+ function parseTimestampMs(value) {
46
+ if (!value) return null;
47
+ if (typeof value === 'number') return normalizeEpochMs(value);
48
+ const parsed = Date.parse(value);
49
+ return Number.isFinite(parsed) ? parsed : null;
50
+ }
51
+
52
+ function tokenTotalFromUsage(usage, fields) {
53
+ if (!usage || typeof usage !== 'object') return 0;
54
+
55
+ const explicitTotal = numberOrNull(usage.total_tokens);
56
+ if (explicitTotal !== null) return Math.max(0, explicitTotal);
57
+
58
+ return fields.reduce((sum, field) => {
59
+ const value = numberOrNull(usage[field]);
60
+ return sum + Math.max(0, value ?? 0);
61
+ }, 0);
62
+ }
63
+
64
+ function tokenUsageSummary({ totalTokens, eventCount, source, windowMs, sampledAt }) {
65
+ const windowMinutes = Math.max(1, windowMs / 60_000);
66
+ return {
67
+ available: true,
68
+ windowMs,
69
+ source,
70
+ totalTokens,
71
+ tokensPerMinute: roundRate(totalTokens / windowMinutes),
72
+ eventCount,
73
+ sampledAt,
74
+ };
75
+ }
76
+
77
+ function codexTokenTotalFromInfo(info, field) {
78
+ return tokenTotalFromUsage(info?.[field], ['input_tokens', 'output_tokens']);
79
+ }
80
+
81
+ function emptyTokenBreakdown() {
82
+ return {
83
+ totalTokens: 0,
84
+ inputTokens: 0,
85
+ outputTokens: 0,
86
+ cacheReadTokens: 0,
87
+ cacheWriteTokens: 0,
88
+ unattributedTokens: 0,
89
+ };
90
+ }
91
+
92
+ function tokenBreakdownFromUsage(usage, provider) {
93
+ if (!usage || typeof usage !== 'object') return emptyTokenBreakdown();
94
+ const rawInput = Math.max(0, numberOrNull(usage.input_tokens) ?? 0);
95
+ const outputTokens = Math.max(0, numberOrNull(usage.output_tokens) ?? 0);
96
+ const cacheReadTokens = Math.max(0, numberOrNull(
97
+ usage.cached_input_tokens ?? usage.cache_read_input_tokens,
98
+ ) ?? 0);
99
+ const cacheWriteTokens = Math.max(0, numberOrNull(usage.cache_creation_input_tokens) ?? 0);
100
+ const inputTokens = provider === 'codex'
101
+ ? Math.max(0, rawInput - cacheReadTokens)
102
+ : rawInput;
103
+ const componentTotal = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
104
+ const explicitTotal = numberOrNull(usage.total_tokens);
105
+ const totalTokens = Math.max(0, explicitTotal ?? componentTotal);
106
+ return {
107
+ totalTokens,
108
+ inputTokens,
109
+ outputTokens,
110
+ cacheReadTokens,
111
+ cacheWriteTokens,
112
+ unattributedTokens: Math.max(0, totalTokens - componentTotal),
113
+ };
114
+ }
115
+
116
+ function tokenBreakdownFromOpenCode(tokens) {
117
+ if (!tokens || typeof tokens !== 'object') return emptyTokenBreakdown();
118
+ const inputTokens = Math.max(0, numberOrNull(tokens.input) ?? 0);
119
+ const outputTokens = Math.max(0, numberOrNull(tokens.output) ?? 0)
120
+ + Math.max(0, numberOrNull(tokens.reasoning) ?? 0);
121
+ const cacheReadTokens = Math.max(0, numberOrNull(tokens.cache?.read) ?? 0);
122
+ const cacheWriteTokens = Math.max(0, numberOrNull(tokens.cache?.write) ?? 0);
123
+ const componentTotal = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
124
+ const explicitTotal = numberOrNull(tokens.total);
125
+ const totalTokens = Math.max(0, explicitTotal ?? componentTotal);
126
+ return {
127
+ totalTokens,
128
+ inputTokens,
129
+ outputTokens,
130
+ cacheReadTokens,
131
+ cacheWriteTokens,
132
+ unattributedTokens: Math.max(0, totalTokens - componentTotal),
133
+ };
134
+ }
135
+
136
+ function providerHomePaths(providerHomes, provider, fallbackPath) {
137
+ const configured = providerHomes && Array.isArray(providerHomes[provider])
138
+ ? providerHomes[provider]
139
+ : [];
140
+ const candidates = configured.length > 0 ? configured : [fallbackPath];
141
+ return Array.from(new Set(candidates.map((home) => {
142
+ if (typeof home === 'string') return home;
143
+ return home && typeof home.path === 'string' ? home.path : '';
144
+ }).filter(Boolean)));
145
+ }
146
+
147
+ function addTokenBreakdown(target, source) {
148
+ for (const field of Object.keys(emptyTokenBreakdown())) {
149
+ target[field] += Math.max(0, numberOrNull(source?.[field]) ?? 0);
150
+ }
151
+ return target;
152
+ }
153
+
154
+ function subtractTokenBreakdown(current, previous) {
155
+ const result = emptyTokenBreakdown();
156
+ for (const field of Object.keys(result)) {
157
+ result[field] = Math.max(0, current[field] - previous[field]);
158
+ }
159
+ return result;
160
+ }
161
+
162
+ function normalizeDeltaBreakdown(current, previous, last) {
163
+ const deltaTotal = Math.max(0, current.totalTokens - previous.totalTokens);
164
+ const delta = subtractTokenBreakdown(current, previous);
165
+ const componentTotal = delta.inputTokens + delta.outputTokens + delta.cacheReadTokens
166
+ + delta.cacheWriteTokens + delta.unattributedTokens;
167
+ if (componentTotal === deltaTotal) return delta;
168
+ if (last.totalTokens === deltaTotal) return last;
169
+ return { ...emptyTokenBreakdown(), totalTokens: deltaTotal, unattributedTokens: deltaTotal };
170
+ }
171
+
172
+ function usageObjectSignature(usage) {
173
+ if (!usage || typeof usage !== 'object') return '';
174
+ return JSON.stringify(Object.keys(usage).sort().map(key => [key, usage[key]]));
175
+ }
176
+
177
+ function createCodexDeltaState() {
178
+ return {
179
+ previousTotal: null,
180
+ previousBreakdown: emptyTokenBreakdown(),
181
+ seenFallbackUsages: new Set(),
182
+ };
183
+ }
184
+
185
+ function codexTokenEventFromRecord(record, state) {
186
+ if (record?.type !== 'event_msg' || record.payload?.type !== 'token_count') return null;
187
+ const timestamp = parseTimestampMs(record.timestamp);
188
+ if (!timestamp) return null;
189
+ const info = record.payload?.info;
190
+ const cumulativeTotal = codexTokenTotalFromInfo(info, 'total_token_usage');
191
+ const lastTokenTotal = codexTokenTotalFromInfo(info, 'last_token_usage');
192
+ const cumulativeBreakdown = tokenBreakdownFromUsage(info?.total_token_usage, 'codex');
193
+ const lastBreakdown = tokenBreakdownFromUsage(info?.last_token_usage, 'codex');
194
+
195
+ if (cumulativeTotal > 0) {
196
+ let delta = 0;
197
+ let deltaBreakdown = emptyTokenBreakdown();
198
+ if (state.previousTotal !== null && cumulativeTotal >= state.previousTotal) {
199
+ delta = cumulativeTotal - state.previousTotal;
200
+ deltaBreakdown = normalizeDeltaBreakdown(
201
+ cumulativeBreakdown,
202
+ state.previousBreakdown,
203
+ lastBreakdown,
204
+ );
205
+ } else if (state.previousTotal === null) {
206
+ delta = lastTokenTotal > 0 ? lastTokenTotal : cumulativeTotal;
207
+ deltaBreakdown = lastTokenTotal > 0 ? lastBreakdown : cumulativeBreakdown;
208
+ } else if (lastTokenTotal > 0) {
209
+ delta = lastTokenTotal;
210
+ deltaBreakdown = lastBreakdown;
211
+ }
212
+ state.previousTotal = cumulativeTotal;
213
+ state.previousBreakdown = cumulativeBreakdown;
214
+ return delta > 0 ? { timestamp, ...deltaBreakdown, totalTokens: delta } : null;
215
+ }
216
+
217
+ if (lastTokenTotal <= 0) return null;
218
+ const signature = usageObjectSignature(info?.last_token_usage);
219
+ if (signature && state.seenFallbackUsages.has(signature)) return null;
220
+ if (signature) state.seenFallbackUsages.add(signature);
221
+ return { timestamp, ...lastBreakdown, totalTokens: lastTokenTotal };
222
+ }
223
+
224
+ function collectCodexTokenDeltas(records, {
225
+ now,
226
+ windowMs,
227
+ historyWindowMs = windowMs,
228
+ includeAllEvents = false,
229
+ }) {
230
+ const windowStart = now - windowMs;
231
+ const historyWindowStart = now - historyWindowMs;
232
+ const sortedRecords = records
233
+ .map((record, index) => ({
234
+ record,
235
+ index,
236
+ timestamp: parseTimestampMs(record.timestamp),
237
+ }))
238
+ .filter(entry => entry.record.type === 'event_msg' && entry.record.payload?.type === 'token_count')
239
+ .sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0) || a.index - b.index);
240
+
241
+ let totalTokens = 0;
242
+ let eventCount = 0;
243
+ const tokenEvents = [];
244
+ const deltaState = createCodexDeltaState();
245
+
246
+ for (const entry of sortedRecords) {
247
+ const { record } = entry;
248
+ const event = codexTokenEventFromRecord(record, deltaState);
249
+ if (!event) continue;
250
+ const { timestamp } = event;
251
+ const inWindow = Boolean(timestamp && timestamp >= windowStart && timestamp <= now + 60_000);
252
+ const inHistoryWindow = Boolean(timestamp && (
253
+ includeAllEvents || (timestamp >= historyWindowStart && timestamp <= now + 60_000)
254
+ ));
255
+ if (inWindow) {
256
+ totalTokens += event.totalTokens;
257
+ eventCount += 1;
258
+ }
259
+ if (inHistoryWindow) tokenEvents.push(event);
260
+ }
261
+
262
+ return { totalTokens, eventCount, tokenEvents };
263
+ }
264
+
265
+ function buildUsageTimeline(providerEvents, options = {}) {
266
+ const now = options.now ?? Date.now();
267
+ const windowMs = Math.max(60_000, options.windowMs ?? USAGE_TIMELINE_WINDOW_MS);
268
+ const bucketCount = Math.max(1, Math.floor(options.bucketCount ?? USAGE_TIMELINE_BUCKET_COUNT));
269
+ const bucketMs = windowMs / bucketCount;
270
+ const endAt = now;
271
+ const startAt = endAt - windowMs;
272
+ const providerNames = Object.keys(providerEvents || {});
273
+ const points = Array.from({ length: bucketCount }, (_, index) => ({
274
+ startedAt: Math.round(startAt + index * bucketMs),
275
+ endedAt: Math.round(startAt + (index + 1) * bucketMs),
276
+ totalTokens: 0,
277
+ tokensPerMinute: 0,
278
+ providers: Object.fromEntries(providerNames.map(provider => [provider, 0])),
279
+ }));
280
+
281
+ for (const provider of providerNames) {
282
+ for (const event of providerEvents[provider] || []) {
283
+ const timestamp = parseTimestampMs(event?.timestamp);
284
+ const totalTokens = Math.max(0, numberOrNull(event?.totalTokens) ?? 0);
285
+ if (!timestamp || totalTokens <= 0 || timestamp < startAt || timestamp > endAt + 60_000) continue;
286
+ const index = Math.min(bucketCount - 1, Math.max(0, Math.floor((timestamp - startAt) / bucketMs)));
287
+ points[index].providers[provider] += totalTokens;
288
+ points[index].totalTokens += totalTokens;
289
+ }
290
+ }
291
+
292
+ const bucketMinutes = bucketMs / 60_000;
293
+ let totalTokens = 0;
294
+ let peakTokensPerMinute = 0;
295
+ let activeBucketCount = 0;
296
+ for (const point of points) {
297
+ point.tokensPerMinute = roundRate(point.totalTokens / bucketMinutes);
298
+ totalTokens += point.totalTokens;
299
+ peakTokensPerMinute = Math.max(peakTokensPerMinute, point.tokensPerMinute);
300
+ if (point.totalTokens > 0) activeBucketCount += 1;
301
+ }
302
+
303
+ return {
304
+ source: 'local provider token events',
305
+ sampledAt: now,
306
+ startAt,
307
+ endAt,
308
+ windowMs,
309
+ bucketMs,
310
+ bucketCount,
311
+ totalTokens,
312
+ averageTokensPerMinute: roundRate(totalTokens / (windowMs / 60_000)),
313
+ peakTokensPerMinute: roundRate(peakTokensPerMinute),
314
+ activeBucketCount,
315
+ points,
316
+ };
317
+ }
318
+
319
+ function providerUsageFromEvents(events, options = {}) {
320
+ const now = options.now ?? Date.now();
321
+ const windowMs = options.windowMs ?? USAGE_WINDOW_MS;
322
+ const historyWindowMs = options.historyWindowMs ?? USAGE_TIMELINE_WINDOW_MS;
323
+ let totalTokens = 0;
324
+ let eventCount = 0;
325
+ const tokenEvents = [];
326
+ for (const event of Array.isArray(events) ? events : []) {
327
+ const timestamp = parseTimestampMs(event?.timestamp);
328
+ const eventTokens = Math.max(0, numberOrNull(event?.totalTokens) ?? 0);
329
+ if (!timestamp || eventTokens <= 0 || timestamp > now + 60_000) continue;
330
+ if (timestamp >= now - historyWindowMs) tokenEvents.push(event);
331
+ if (timestamp >= now - windowMs) {
332
+ totalTokens += eventTokens;
333
+ eventCount += 1;
334
+ }
335
+ }
336
+ return {
337
+ tokenUsage: tokenUsageSummary({
338
+ totalTokens,
339
+ eventCount,
340
+ source: options.source || 'local provider token events',
341
+ windowMs,
342
+ sampledAt: now,
343
+ }),
344
+ tokenEvents,
345
+ };
346
+ }
347
+
348
+ async function defaultCommandRunner(command, args, options = {}) {
349
+ const result = await execFileAsync(command, args, {
350
+ timeout: options.timeoutMs ?? COMMAND_TIMEOUT_MS,
351
+ windowsHide: true,
352
+ });
353
+
354
+ return {
355
+ stdout: String(result.stdout || ''),
356
+ stderr: String(result.stderr || ''),
357
+ };
358
+ }
359
+
360
+ function commandUnavailable(error) {
361
+ if (!error) return 'Unavailable';
362
+ if (error.code === 'ENOENT') return 'Command not found';
363
+ const stderr = String(error.stderr || '').trim();
364
+ const stdout = String(error.stdout || '').trim();
365
+ return stderr || stdout || error.message || 'Unavailable';
366
+ }
367
+
368
+ async function readCodexAuthStatus(commandRunner = defaultCommandRunner) {
369
+ try {
370
+ const result = await commandRunner('codex', ['login', 'status'], { timeoutMs: COMMAND_TIMEOUT_MS });
371
+ const status = `${result.stdout || ''}${result.stderr || ''}`.trim().split(/\r?\n/).filter(Boolean).join(' ');
372
+ return {
373
+ available: true,
374
+ status: status || 'Available',
375
+ source: 'codex login status',
376
+ };
377
+ } catch (error) {
378
+ return {
379
+ available: false,
380
+ status: commandUnavailable(error),
381
+ source: 'codex login status',
382
+ };
383
+ }
384
+ }
385
+
386
+ async function readClaudeAuthStatus(commandRunner = defaultCommandRunner) {
387
+ try {
388
+ const result = await commandRunner('claude', ['auth', 'status', '--json'], { timeoutMs: COMMAND_TIMEOUT_MS });
389
+ const parsed = JSON.parse((result.stdout || result.stderr || '{}').trim() || '{}');
390
+ const loggedIn = parsed.loggedIn === true;
391
+ const statusParts = [
392
+ loggedIn ? 'logged in' : 'logged out',
393
+ parsed.authMethod,
394
+ parsed.apiProvider,
395
+ ].filter(Boolean);
396
+
397
+ return {
398
+ available: true,
399
+ status: statusParts.join(' / ') || 'Available',
400
+ loggedIn,
401
+ authMethod: parsed.authMethod || '',
402
+ apiProvider: parsed.apiProvider || '',
403
+ source: 'claude auth status --json',
404
+ };
405
+ } catch (error) {
406
+ return {
407
+ available: false,
408
+ status: commandUnavailable(error),
409
+ loggedIn: false,
410
+ authMethod: '',
411
+ apiProvider: '',
412
+ source: 'claude auth status --json',
413
+ };
414
+ }
415
+ }
416
+
417
+ async function readJsonlTail(filePath, maxBytes = JSONL_TAIL_BYTES) {
418
+ const stat = await fsp.stat(filePath);
419
+ const size = stat.size;
420
+ if (size <= 0) return '';
421
+
422
+ const bytesToRead = Math.min(size, maxBytes);
423
+ const start = size - bytesToRead;
424
+ const buffer = Buffer.alloc(bytesToRead);
425
+ const handle = await fsp.open(filePath, 'r');
426
+ try {
427
+ await handle.read(buffer, 0, bytesToRead, start);
428
+ } finally {
429
+ await handle.close();
430
+ }
431
+
432
+ let text = buffer.toString('utf8');
433
+ if (start > 0) {
434
+ const newlineIndex = text.indexOf('\n');
435
+ text = newlineIndex === -1 ? '' : text.slice(newlineIndex + 1);
436
+ }
437
+ return text;
438
+ }
439
+
440
+ async function findRecentJsonlFiles(roots, options = {}) {
441
+ const limit = options.limit ?? JSONL_FILE_LIMIT;
442
+ const maxDepth = options.maxDepth ?? 6;
443
+ const scanLimit = options.scanLimit ?? JSONL_SCAN_LIMIT;
444
+ const files = [];
445
+ let scanned = 0;
446
+
447
+ async function walk(dir, depth) {
448
+ if (depth > maxDepth || scanned >= scanLimit) return;
449
+
450
+ let entries;
451
+ try {
452
+ entries = await fsp.readdir(dir, { withFileTypes: true });
453
+ } catch {
454
+ return;
455
+ }
456
+
457
+ for (const entry of entries) {
458
+ if (scanned >= scanLimit) return;
459
+ const entryPath = path.join(dir, entry.name);
460
+ if (entry.isDirectory()) {
461
+ await walk(entryPath, depth + 1);
462
+ continue;
463
+ }
464
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
465
+
466
+ scanned += 1;
467
+ try {
468
+ const stat = await fsp.stat(entryPath);
469
+ files.push({ filePath: entryPath, mtimeMs: stat.mtimeMs });
470
+ } catch {
471
+ // Ignore files that disappear while we scan.
472
+ }
473
+ }
474
+ }
475
+
476
+ for (const root of roots) {
477
+ await walk(root, 0);
478
+ }
479
+
480
+ return files
481
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)
482
+ .slice(0, limit)
483
+ .map(file => file.filePath);
484
+ }
485
+
486
+ async function readJsonlRecords(filePath) {
487
+ const text = await readJsonlTail(filePath);
488
+ return text
489
+ .split(/\r?\n/)
490
+ .filter(Boolean)
491
+ .map(line => {
492
+ try {
493
+ return JSON.parse(line);
494
+ } catch {
495
+ return null;
496
+ }
497
+ })
498
+ .filter(Boolean);
499
+ }
500
+
501
+ async function isNativeExecutable(filePath) {
502
+ try {
503
+ const resolved = await fsp.realpath(filePath);
504
+ const stat = await fsp.stat(resolved);
505
+ if (!stat.isFile()) return false;
506
+ const handle = await fsp.open(resolved, 'r');
507
+ try {
508
+ const header = Buffer.alloc(2);
509
+ const { bytesRead } = await handle.read(header, 0, header.length, 0);
510
+ return bytesRead === header.length && header.toString('utf8') !== '#!';
511
+ } finally {
512
+ await handle.close();
513
+ }
514
+ } catch {
515
+ return false;
516
+ }
517
+ }
518
+
519
+ async function codexBundledRipgrepCandidates() {
520
+ let codexPackagePath;
521
+ try {
522
+ codexPackagePath = require.resolve('@openai/codex/package.json');
523
+ } catch {
524
+ return [];
525
+ }
526
+ const scopeDir = path.dirname(path.dirname(codexPackagePath));
527
+ let packages;
528
+ try {
529
+ packages = await fsp.readdir(scopeDir, { withFileTypes: true });
530
+ } catch {
531
+ return [];
532
+ }
533
+ const executableName = process.platform === 'win32' ? 'rg.exe' : 'rg';
534
+ const candidates = [];
535
+ for (const entry of packages) {
536
+ if (!entry.isDirectory() || !entry.name.startsWith('codex-')) continue;
537
+ const vendorDir = path.join(scopeDir, entry.name, 'vendor');
538
+ let triples;
539
+ try {
540
+ triples = await fsp.readdir(vendorDir, { withFileTypes: true });
541
+ } catch {
542
+ continue;
543
+ }
544
+ for (const triple of triples) {
545
+ if (!triple.isDirectory()) continue;
546
+ candidates.push(path.join(vendorDir, triple.name, 'codex-path', executableName));
547
+ }
548
+ }
549
+ return candidates;
550
+ }
551
+
552
+ async function resolveNativeRipgrepPath() {
553
+ if (process.env.FARMING_RG_BIN) return process.env.FARMING_RG_BIN;
554
+ if (nativeRipgrepPathPromise) return nativeRipgrepPathPromise;
555
+ nativeRipgrepPathPromise = (async () => {
556
+ const executableName = process.platform === 'win32' ? 'rg.exe' : 'rg';
557
+ const pathCandidates = String(process.env.PATH || '')
558
+ .split(path.delimiter)
559
+ .filter(Boolean)
560
+ .map(dir => path.join(dir, executableName));
561
+ const candidates = [
562
+ ...pathCandidates,
563
+ ...await codexBundledRipgrepCandidates(),
564
+ ];
565
+ for (const candidate of Array.from(new Set(candidates))) {
566
+ if (await isNativeExecutable(candidate)) return candidate;
567
+ }
568
+ return null;
569
+ })();
570
+ return nativeRipgrepPathPromise;
571
+ }
572
+
573
+ function localDateKey(timestamp) {
574
+ const date = new Date(timestamp);
575
+ if (!Number.isFinite(date.getTime())) return '';
576
+ const year = date.getFullYear();
577
+ const month = String(date.getMonth() + 1).padStart(2, '0');
578
+ const day = String(date.getDate()).padStart(2, '0');
579
+ return `${year}-${month}-${day}`;
580
+ }
581
+
582
+ function buildDailyUsage(providerEvents, options = {}) {
583
+ const now = options.now ?? Date.now();
584
+ const days = Math.max(1, Math.floor(options.days ?? USAGE_DAILY_DAYS));
585
+ const cursor = new Date(now);
586
+ cursor.setHours(0, 0, 0, 0);
587
+ cursor.setDate(cursor.getDate() - days + 1);
588
+ const points = [];
589
+ const byDate = new Map();
590
+ const providerNames = Object.keys(providerEvents || {});
591
+
592
+ for (let index = 0; index < days; index += 1) {
593
+ const date = localDateKey(cursor.getTime());
594
+ const point = {
595
+ date,
596
+ ...emptyTokenBreakdown(),
597
+ providers: Object.fromEntries(providerNames.map(provider => [provider, emptyTokenBreakdown()])),
598
+ };
599
+ points.push(point);
600
+ byDate.set(date, point);
601
+ cursor.setDate(cursor.getDate() + 1);
602
+ }
603
+
604
+ for (const provider of providerNames) {
605
+ for (const event of providerEvents[provider] || []) {
606
+ const point = byDate.get(localDateKey(parseTimestampMs(event?.timestamp)));
607
+ if (!point) continue;
608
+ addTokenBreakdown(point, event);
609
+ addTokenBreakdown(point.providers[provider], event);
610
+ }
611
+ }
612
+
613
+ const sumRange = count => points
614
+ .slice(Math.max(0, points.length - count))
615
+ .reduce((total, point) => total + point.totalTokens, 0);
616
+ const peak = points.reduce((best, point) => (
617
+ !best || point.totalTokens > best.totalTokens ? point : best
618
+ ), null);
619
+
620
+ return {
621
+ source: 'local provider token events',
622
+ sampledAt: now,
623
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'local',
624
+ days,
625
+ startDate: points[0]?.date || '',
626
+ endDate: points[points.length - 1]?.date || '',
627
+ summary: {
628
+ todayTokens: sumRange(1),
629
+ sevenDayTokens: sumRange(7),
630
+ thirtyDayTokens: sumRange(30),
631
+ periodTokens: sumRange(days),
632
+ peakDate: peak?.date || '',
633
+ peakTokens: peak?.totalTokens || 0,
634
+ },
635
+ points,
636
+ };
637
+ }
638
+
639
+ function buildUsageDayDetail(providerEvents, options = {}) {
640
+ const date = String(options.date || '').trim();
641
+ const parts = date.split('-').map(Number);
642
+ const dateProbe = parts.length === 3
643
+ ? new Date(parts[0], parts[1] - 1, parts[2], 12, 0, 0, 0)
644
+ : null;
645
+ if (!dateProbe || localDateKey(dateProbe.getTime()) !== date) {
646
+ throw new RangeError('Usage day must be a valid local date in YYYY-MM-DD format.');
647
+ }
648
+
649
+ const providerNames = Object.keys(providerEvents || {});
650
+ const hours = Array.from({ length: 24 }, (_, hour) => ({
651
+ hour,
652
+ label: String(hour).padStart(2, '0'),
653
+ ...emptyTokenBreakdown(),
654
+ }));
655
+ const providers = Object.fromEntries(
656
+ providerNames.map(provider => [provider, emptyTokenBreakdown()]),
657
+ );
658
+ const total = emptyTokenBreakdown();
659
+
660
+ for (const provider of providerNames) {
661
+ for (const event of providerEvents[provider] || []) {
662
+ const timestamp = parseTimestampMs(event?.timestamp);
663
+ if (localDateKey(timestamp) !== date) continue;
664
+ const hour = new Date(timestamp).getHours();
665
+ if (!Number.isInteger(hour) || hour < 0 || hour > 23) continue;
666
+ addTokenBreakdown(hours[hour], event);
667
+ addTokenBreakdown(providers[provider], event);
668
+ addTokenBreakdown(total, event);
669
+ }
670
+ }
671
+
672
+ return {
673
+ source: 'local provider token events',
674
+ date,
675
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'local',
676
+ total,
677
+ hours,
678
+ providers,
679
+ };
680
+ }
681
+
682
+ async function readDailyFileEvents(filePath, provider, minimumMtimeMs = 0) {
683
+ const stat = await fsp.stat(filePath);
684
+ if (stat.mtimeMs < minimumMtimeMs) return { events: [], mtimeMs: stat.mtimeMs };
685
+ const cacheKey = `${provider}:${filePath}`;
686
+ const signature = `${stat.size}:${stat.mtimeMs}`;
687
+ const cached = dailyFileEventCache.get(cacheKey);
688
+ if (cached?.signature === signature) {
689
+ return { events: cached.events, mtimeMs: stat.mtimeMs, truncated: cached.truncated };
690
+ }
691
+
692
+ const records = await readJsonlRecords(filePath);
693
+ const events = [];
694
+ if (provider === 'codex') {
695
+ events.push(...collectCodexTokenDeltas(records, {
696
+ now: Date.now(),
697
+ windowMs: 1,
698
+ includeAllEvents: true,
699
+ }).tokenEvents);
700
+ } else {
701
+ for (const record of records) {
702
+ const timestamp = parseTimestampMs(record.timestamp);
703
+ if (!timestamp) continue;
704
+ for (const usage of claudeUsageObjectsFromRecord(record)) {
705
+ const breakdown = tokenBreakdownFromUsage(usage, 'claude');
706
+ if (breakdown.totalTokens > 0) events.push({ timestamp, ...breakdown });
707
+ }
708
+ }
709
+ }
710
+
711
+ const truncated = stat.size > JSONL_TAIL_BYTES;
712
+ dailyFileEventCache.set(cacheKey, { signature, events, truncated });
713
+ return { events, mtimeMs: stat.mtimeMs, truncated };
714
+ }
715
+
716
+ async function collectCodexDailyEventsWithRipgrep(roots, options = {}) {
717
+ const existingRoots = [];
718
+ for (const root of roots) {
719
+ try {
720
+ await fsp.access(root);
721
+ existingRoots.push(root);
722
+ } catch {
723
+ // Ignore provider history roots that do not exist yet.
724
+ }
725
+ }
726
+ if (existingRoots.length === 0) return { events: [], partial: false };
727
+
728
+ const now = options.now ?? Date.now();
729
+ const cutoffMs = options.cutoffMs ?? 0;
730
+ const ripgrepPath = options.ripgrepPath || await resolveNativeRipgrepPath();
731
+ if (!ripgrepPath) return null;
732
+ return new Promise((resolve) => {
733
+ const child = spawn(ripgrepPath, [
734
+ '-F',
735
+ '"type":"token_count"',
736
+ '--with-filename',
737
+ '--no-heading',
738
+ '--no-line-number',
739
+ '-g',
740
+ '*.jsonl',
741
+ ...existingRoots,
742
+ ], { stdio: ['ignore', 'pipe', 'pipe'] });
743
+ const events = [];
744
+ const states = new Map();
745
+ let unavailable = false;
746
+ child.once('error', () => {
747
+ unavailable = true;
748
+ });
749
+ child.stderr.resume();
750
+ const lines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
751
+ lines.on('line', line => {
752
+ const separator = line.indexOf(':{');
753
+ if (separator <= 0) return;
754
+ const filePath = line.slice(0, separator);
755
+ let record;
756
+ try {
757
+ record = JSON.parse(line.slice(separator + 1));
758
+ } catch {
759
+ return;
760
+ }
761
+ let state = states.get(filePath);
762
+ if (!state) {
763
+ state = createCodexDeltaState();
764
+ states.set(filePath, state);
765
+ }
766
+ const event = codexTokenEventFromRecord(record, state);
767
+ if (event && event.timestamp >= cutoffMs && event.timestamp <= now + 60_000) events.push(event);
768
+ });
769
+ child.once('close', code => {
770
+ if (unavailable || (code !== 0 && code !== 1)) {
771
+ resolve(null);
772
+ return;
773
+ }
774
+ resolve({ events, partial: false, source: 'ripgrep token_count scan' });
775
+ });
776
+ });
777
+ }
778
+
779
+ async function collectProviderDailyEvents(provider, roots, options = {}) {
780
+ const now = options.now ?? Date.now();
781
+ const days = Math.max(1, Math.floor(options.days ?? USAGE_DAILY_DAYS));
782
+ const cutoff = new Date(now);
783
+ cutoff.setHours(0, 0, 0, 0);
784
+ cutoff.setDate(cutoff.getDate() - days);
785
+ if (provider === 'codex') {
786
+ const ripgrepResult = await collectCodexDailyEventsWithRipgrep(roots, {
787
+ now,
788
+ cutoffMs: cutoff.getTime(),
789
+ });
790
+ if (ripgrepResult) return ripgrepResult;
791
+ }
792
+ const fileLimit = options.fileLimit ?? DAILY_JSONL_FILE_LIMIT;
793
+ const files = await findRecentJsonlFiles(roots, {
794
+ limit: fileLimit,
795
+ maxDepth: options.maxDepth ?? 6,
796
+ scanLimit: options.scanLimit ?? DAILY_JSONL_SCAN_LIMIT,
797
+ });
798
+ const events = [];
799
+ let truncated = false;
800
+ for (const filePath of files) {
801
+ const file = await readDailyFileEvents(filePath, provider, cutoff.getTime()).catch(() => null);
802
+ if (!file) continue;
803
+ events.push(...file.events);
804
+ truncated = truncated || file.truncated === true;
805
+ }
806
+ return {
807
+ events,
808
+ partial: truncated || files.length >= fileLimit,
809
+ };
810
+ }
811
+
812
+ async function defaultOpenCodeCommandRunner(args, options = {}) {
813
+ const env = { ...process.env };
814
+ if (options.openCodeHome) env.OPENCODE_CONFIG_DIR = options.openCodeHome;
815
+ const result = await execFileAsync(
816
+ options.openCodeBin || process.env.FARMING_OPENCODE_BIN || 'opencode',
817
+ args,
818
+ {
819
+ env,
820
+ timeout: options.timeoutMs ?? OPENCODE_COMMAND_TIMEOUT_MS,
821
+ maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
822
+ windowsHide: true,
823
+ },
824
+ );
825
+ return { stdout: String(result.stdout || ''), stderr: String(result.stderr || '') };
826
+ }
827
+
828
+ function openCodeTokenEventsFromExport(sessionExport, options = {}) {
829
+ const cutoffMs = options.cutoffMs ?? 0;
830
+ const now = options.now ?? Date.now();
831
+ const events = [];
832
+ for (const message of Array.isArray(sessionExport?.messages) ? sessionExport.messages : []) {
833
+ const info = message?.info;
834
+ if (info?.role !== 'assistant' || !info.tokens) continue;
835
+ const timestamp = parseTimestampMs(info.time?.completed ?? info.time?.created);
836
+ if (!timestamp || timestamp < cutoffMs || timestamp > now + 60_000) continue;
837
+ const breakdown = tokenBreakdownFromOpenCode(info.tokens);
838
+ if (breakdown.totalTokens > 0) events.push({ timestamp, ...breakdown });
839
+ }
840
+ return events;
841
+ }
842
+
843
+ async function collectOpenCodeDailyEvents(homePaths, options = {}) {
844
+ const now = options.now ?? Date.now();
845
+ const cutoffMs = options.cutoffMs ?? 0;
846
+ const commandRunner = options.openCodeCommandRunner || defaultOpenCodeCommandRunner;
847
+ const sessions = [];
848
+ const seenSessionIds = new Set();
849
+ let successfulHomes = 0;
850
+ let partial = false;
851
+ let reason = '';
852
+
853
+ for (const openCodeHome of homePaths) {
854
+ try {
855
+ const result = await commandRunner([
856
+ 'session', 'list', '--format', 'json', '--max-count', String(OPENCODE_SESSION_LIMIT),
857
+ ], { openCodeHome, timeoutMs: OPENCODE_COMMAND_TIMEOUT_MS });
858
+ const listed = JSON.parse(String(result?.stdout || '[]'));
859
+ if (!Array.isArray(listed)) throw new Error('OpenCode session list was not an array');
860
+ successfulHomes += 1;
861
+ for (const session of listed) {
862
+ const id = typeof session?.id === 'string' ? session.id.trim() : '';
863
+ const updatedAt = parseTimestampMs(session?.updated ?? session?.created);
864
+ if (!id || seenSessionIds.has(id) || (updatedAt && updatedAt < cutoffMs)) continue;
865
+ seenSessionIds.add(id);
866
+ sessions.push({ id, openCodeHome, updatedAt: updatedAt || 0 });
867
+ }
868
+ } catch (error) {
869
+ partial = true;
870
+ reason = reason || commandUnavailable(error);
871
+ }
872
+ }
873
+
874
+ const events = [];
875
+ let nextSessionIndex = 0;
876
+ const worker = async () => {
877
+ while (nextSessionIndex < sessions.length) {
878
+ const session = sessions[nextSessionIndex];
879
+ nextSessionIndex += 1;
880
+ const cacheKey = `${session.openCodeHome}:${session.id}`;
881
+ const cached = openCodeSessionEventCache.get(cacheKey);
882
+ if (cached?.updatedAt === session.updatedAt) {
883
+ events.push(...cached.events);
884
+ continue;
885
+ }
886
+ try {
887
+ const result = await commandRunner(
888
+ ['export', session.id, '--pure', '--sanitize'],
889
+ { openCodeHome: session.openCodeHome, timeoutMs: OPENCODE_COMMAND_TIMEOUT_MS },
890
+ );
891
+ const exported = JSON.parse(String(result?.stdout || '{}'));
892
+ const sessionEvents = openCodeTokenEventsFromExport(exported, { cutoffMs, now });
893
+ openCodeSessionEventCache.set(cacheKey, { updatedAt: session.updatedAt, events: sessionEvents });
894
+ events.push(...sessionEvents);
895
+ } catch (error) {
896
+ partial = true;
897
+ reason = reason || commandUnavailable(error);
898
+ }
899
+ }
900
+ };
901
+ await Promise.all(Array.from(
902
+ { length: Math.min(OPENCODE_EXPORT_CONCURRENCY, sessions.length) },
903
+ () => worker(),
904
+ ));
905
+
906
+ return {
907
+ events,
908
+ partial,
909
+ available: successfulHomes > 0,
910
+ reason,
911
+ sessionCount: sessions.length,
912
+ };
913
+ }
914
+
915
+ async function collectUsageHistory(options = {}) {
916
+ const now = options.now ?? Date.now();
917
+ const days = options.days ?? USAGE_DAILY_DAYS;
918
+ const cutoff = new Date(now);
919
+ cutoff.setHours(0, 0, 0, 0);
920
+ cutoff.setDate(cutoff.getDate() - days);
921
+ const codexHomes = providerHomePaths(
922
+ options.providerHomes,
923
+ 'codex',
924
+ options.codexHome || path.join(os.homedir(), '.codex'),
925
+ );
926
+ const claudeHomes = providerHomePaths(
927
+ options.providerHomes,
928
+ 'claude',
929
+ options.claudeHome || path.join(os.homedir(), '.claude'),
930
+ );
931
+ const openCodeHomes = providerHomePaths(
932
+ options.providerHomes,
933
+ 'opencode',
934
+ options.openCodeHome || path.join(os.homedir(), '.opencode'),
935
+ );
936
+ const qoderHomes = providerHomePaths(
937
+ options.providerHomes,
938
+ 'qoder',
939
+ options.qoderHome || path.join(os.homedir(), '.qoder'),
940
+ );
941
+ const [codex, claude, openCode] = await Promise.all([
942
+ collectProviderDailyEvents('codex', codexHomes.flatMap(home => [
943
+ path.join(home, 'sessions'),
944
+ path.join(home, 'archived_sessions'),
945
+ ]), { now, days, maxDepth: 6 }),
946
+ collectProviderDailyEvents('claude', claudeHomes.map(home => path.join(home, 'projects')), {
947
+ now,
948
+ days,
949
+ maxDepth: 4,
950
+ }),
951
+ collectOpenCodeDailyEvents(openCodeHomes, {
952
+ now,
953
+ cutoffMs: cutoff.getTime(),
954
+ openCodeCommandRunner: options.openCodeCommandRunner,
955
+ }),
956
+ ]);
957
+ const providerEvents = {
958
+ codex: codex.events,
959
+ claude: claude.events,
960
+ opencode: openCode.events,
961
+ };
962
+ const coverage = [
963
+ {
964
+ provider: 'codex',
965
+ providerName: 'Codex',
966
+ available: true,
967
+ homeCount: codexHomes.length,
968
+ source: codex.source || 'local Codex token_count events',
969
+ },
970
+ {
971
+ provider: 'claude',
972
+ providerName: 'Claude',
973
+ available: true,
974
+ homeCount: claudeHomes.length,
975
+ source: 'local Claude usage fields',
976
+ },
977
+ {
978
+ provider: 'opencode',
979
+ providerName: 'OpenCode',
980
+ available: openCode.available,
981
+ homeCount: openCodeHomes.length,
982
+ sessionCount: openCode.sessionCount,
983
+ source: 'opencode session export',
984
+ ...(openCode.reason ? { reason: openCode.reason } : {}),
985
+ },
986
+ {
987
+ provider: 'qoder',
988
+ providerName: 'Qoder',
989
+ available: false,
990
+ homeCount: qoderHomes.length,
991
+ source: 'local Qoder sessions',
992
+ reason: 'Qoder session files do not expose model token usage.',
993
+ },
994
+ ];
995
+ return {
996
+ daily: {
997
+ ...buildDailyUsage(providerEvents, { now, days }),
998
+ partial: codex.partial || claude.partial || openCode.partial,
999
+ coverage,
1000
+ },
1001
+ providerEvents,
1002
+ coverage,
1003
+ };
1004
+ }
1005
+
1006
+ async function collectDailyUsage(options = {}) {
1007
+ return (await collectUsageHistory(options)).daily;
1008
+ }
1009
+
1010
+ function parseCodexLimit(limit) {
1011
+ if (!limit || typeof limit !== 'object') return null;
1012
+ const usedPercent = numberOrNull(limit.used_percent ?? limit.usedPercent);
1013
+ const windowMinutes = numberOrNull(limit.window_minutes ?? limit.windowMinutes ?? limit.windowDurationMins);
1014
+ const resetsAt = normalizeEpochMs(limit.resets_at ?? limit.resetsAt);
1015
+ const totalTokens = numberOrNull(limit.total_tokens ?? limit.totalTokens ?? limit.limit_tokens ?? limit.limitTokens);
1016
+
1017
+ if (usedPercent === null && windowMinutes === null && resetsAt === null && totalTokens === null) return null;
1018
+ return {
1019
+ usedPercent,
1020
+ windowMinutes,
1021
+ resetsAt,
1022
+ totalTokens,
1023
+ };
1024
+ }
1025
+
1026
+ function parseCodexRateLimits(rateLimits) {
1027
+ if (!rateLimits || typeof rateLimits !== 'object') return null;
1028
+ const primary = parseCodexLimit(rateLimits.primary);
1029
+ const secondary = parseCodexLimit(rateLimits.secondary);
1030
+
1031
+ if (!primary && !secondary) return null;
1032
+ const resetCredits = rateLimits.rate_limit_reset_credits ?? rateLimits.rateLimitResetCredits;
1033
+ const resetCreditsAvailable = numberOrNull(resetCredits?.available_count ?? resetCredits?.availableCount);
1034
+ return {
1035
+ available: true,
1036
+ source: 'codex token_count events',
1037
+ limitId: rateLimits.limit_id || rateLimits.limitId || '',
1038
+ limitName: rateLimits.limit_name ?? rateLimits.limitName ?? null,
1039
+ planType: rateLimits.plan_type || rateLimits.planType || '',
1040
+ resetCreditsAvailable,
1041
+ primary,
1042
+ secondary,
1043
+ };
1044
+ }
1045
+
1046
+ function isCodexOverallRateLimit(quota) {
1047
+ const limitId = String(quota?.limitId || '').trim().toLowerCase();
1048
+ return !limitId || limitId === 'codex';
1049
+ }
1050
+
1051
+ async function collectCodexUsage(options = {}) {
1052
+ const now = options.now ?? Date.now();
1053
+ const windowMs = options.windowMs ?? USAGE_WINDOW_MS;
1054
+ const historyWindowMs = options.historyWindowMs ?? USAGE_TIMELINE_WINDOW_MS;
1055
+ const codexHomes = providerHomePaths(
1056
+ options.providerHomes,
1057
+ 'codex',
1058
+ options.codexHome || path.join(os.homedir(), '.codex'),
1059
+ );
1060
+ const roots = codexHomes.flatMap(home => [
1061
+ path.join(home, 'sessions'),
1062
+ path.join(home, 'archived_sessions'),
1063
+ ]);
1064
+ const files = await findRecentJsonlFiles(roots, {
1065
+ limit: options.fileLimit ?? JSONL_FILE_LIMIT,
1066
+ maxDepth: 6,
1067
+ scanLimit: options.scanLimit ?? JSONL_SCAN_LIMIT,
1068
+ });
1069
+ let totalTokens = 0;
1070
+ let eventCount = 0;
1071
+ const tokenEvents = [];
1072
+ let latestQuota = null;
1073
+ let latestQuotaAt = 0;
1074
+ let latestOverallQuota = null;
1075
+ let latestOverallQuotaAt = 0;
1076
+
1077
+ for (const filePath of files) {
1078
+ const records = await readJsonlRecords(filePath).catch(() => []);
1079
+ for (const record of records) {
1080
+ if (record.type !== 'event_msg' || record.payload?.type !== 'token_count') continue;
1081
+ const timestamp = parseTimestampMs(record.timestamp);
1082
+ const rateLimits = parseCodexRateLimits(record.payload.rate_limits);
1083
+ if (rateLimits && (!timestamp || timestamp >= latestQuotaAt)) {
1084
+ latestQuota = rateLimits;
1085
+ latestQuotaAt = timestamp ?? latestQuotaAt;
1086
+ }
1087
+ if (rateLimits && isCodexOverallRateLimit(rateLimits) && (!timestamp || timestamp >= latestOverallQuotaAt)) {
1088
+ latestOverallQuota = rateLimits;
1089
+ latestOverallQuotaAt = timestamp ?? latestOverallQuotaAt;
1090
+ }
1091
+
1092
+ }
1093
+ const fileUsage = collectCodexTokenDeltas(records, { now, windowMs, historyWindowMs });
1094
+ totalTokens += fileUsage.totalTokens;
1095
+ eventCount += fileUsage.eventCount;
1096
+ tokenEvents.push(...fileUsage.tokenEvents);
1097
+ }
1098
+ const quota = latestOverallQuota || latestQuota || {
1099
+ available: false,
1100
+ source: 'codex token_count events',
1101
+ reason: 'No recent Codex token_count event with rate limits was found.',
1102
+ };
1103
+
1104
+ return {
1105
+ quota: attachQuotaForecasts(quota, { now }),
1106
+ tokenUsage: tokenUsageSummary({
1107
+ totalTokens,
1108
+ eventCount,
1109
+ source: 'codex cumulative token_count deltas',
1110
+ windowMs,
1111
+ sampledAt: now,
1112
+ }),
1113
+ tokenEvents,
1114
+ };
1115
+ }
1116
+
1117
+ function claudeUsageObjectsFromRecord(record) {
1118
+ const objects = [];
1119
+ if (record?.message?.usage) objects.push(record.message.usage);
1120
+ if (record?.toolUseResult?.usage) objects.push(record.toolUseResult.usage);
1121
+ return objects;
1122
+ }
1123
+
1124
+ async function collectClaudeUsage(options = {}) {
1125
+ const now = options.now ?? Date.now();
1126
+ const windowMs = options.windowMs ?? USAGE_WINDOW_MS;
1127
+ const historyWindowMs = options.historyWindowMs ?? USAGE_TIMELINE_WINDOW_MS;
1128
+ const claudeHomes = providerHomePaths(
1129
+ options.providerHomes,
1130
+ 'claude',
1131
+ options.claudeHome || path.join(os.homedir(), '.claude'),
1132
+ );
1133
+ const files = await findRecentJsonlFiles(claudeHomes.map(home => path.join(home, 'projects')), {
1134
+ limit: options.fileLimit ?? JSONL_FILE_LIMIT,
1135
+ maxDepth: 4,
1136
+ scanLimit: options.scanLimit ?? JSONL_SCAN_LIMIT,
1137
+ });
1138
+ let totalTokens = 0;
1139
+ let eventCount = 0;
1140
+ const tokenEvents = [];
1141
+
1142
+ for (const filePath of files) {
1143
+ const records = await readJsonlRecords(filePath).catch(() => []);
1144
+ for (const record of records) {
1145
+ const timestamp = parseTimestampMs(record.timestamp);
1146
+ if (!timestamp || timestamp < now - historyWindowMs || timestamp > now + 60_000) continue;
1147
+
1148
+ for (const usage of claudeUsageObjectsFromRecord(record)) {
1149
+ const tokenTotal = tokenTotalFromUsage(usage, [
1150
+ 'input_tokens',
1151
+ 'cache_creation_input_tokens',
1152
+ 'cache_read_input_tokens',
1153
+ 'output_tokens',
1154
+ ]);
1155
+ if (tokenTotal > 0) {
1156
+ tokenEvents.push({ timestamp, totalTokens: tokenTotal });
1157
+ if (timestamp >= now - windowMs) {
1158
+ totalTokens += tokenTotal;
1159
+ eventCount += 1;
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+ return {
1167
+ quota: {
1168
+ available: false,
1169
+ source: 'claude auth status',
1170
+ reason: 'Claude Code auth/status output does not expose usage remaining.',
1171
+ },
1172
+ tokenUsage: tokenUsageSummary({
1173
+ totalTokens,
1174
+ eventCount,
1175
+ source: 'claude local usage fields',
1176
+ windowMs,
1177
+ sampledAt: now,
1178
+ }),
1179
+ tokenEvents,
1180
+ };
1181
+ }
1182
+
1183
+ class UsageMonitor {
1184
+ constructor(options = {}) {
1185
+ this.agentManager = options.agentManager || null;
1186
+ this.systemMonitor = options.systemMonitor || this.agentManager?.systemMonitor || null;
1187
+ this.commandRunner = options.commandRunner || defaultCommandRunner;
1188
+ this.codexHome = options.codexHome;
1189
+ this.claudeHome = options.claudeHome;
1190
+ this.openCodeHome = options.openCodeHome;
1191
+ this.qoderHome = options.qoderHome;
1192
+ this.getProviderHomes = options.getProviderHomes || null;
1193
+ this.openCodeCommandRunner = options.openCodeCommandRunner;
1194
+ this.windowMs = options.windowMs ?? USAGE_WINDOW_MS;
1195
+ this.dailyDays = options.dailyDays ?? USAGE_DAILY_DAYS;
1196
+ this.dailyCacheMs = options.dailyCacheMs ?? USAGE_DAILY_CACHE_MS;
1197
+ this.dailyCache = { value: null, fetchedAt: 0, pending: null };
1198
+ }
1199
+
1200
+ invalidateDailyCache() {
1201
+ this.dailyCache.fetchedAt = 0;
1202
+ }
1203
+
1204
+ getDailyUsage(options = {}) {
1205
+ const now = options.now ?? Date.now();
1206
+ if (
1207
+ !options.force
1208
+ && this.dailyCache.value
1209
+ && now - this.dailyCache.fetchedAt <= this.dailyCacheMs
1210
+ ) {
1211
+ return Promise.resolve(this.dailyCache.value);
1212
+ }
1213
+ if (this.dailyCache.pending) return this.dailyCache.pending;
1214
+ const providerHomes = this.getProviderHomes ? this.getProviderHomes() : undefined;
1215
+ this.dailyCache.pending = collectUsageHistory({
1216
+ codexHome: this.codexHome,
1217
+ claudeHome: this.claudeHome,
1218
+ openCodeHome: this.openCodeHome,
1219
+ qoderHome: this.qoderHome,
1220
+ providerHomes,
1221
+ openCodeCommandRunner: this.openCodeCommandRunner,
1222
+ now,
1223
+ days: this.dailyDays,
1224
+ }).then(value => {
1225
+ this.dailyCache.value = value;
1226
+ this.dailyCache.fetchedAt = now;
1227
+ return value;
1228
+ }).finally(() => {
1229
+ this.dailyCache.pending = null;
1230
+ });
1231
+ return this.dailyCache.pending;
1232
+ }
1233
+
1234
+ async getUsageDay(date, options = {}) {
1235
+ const history = await this.getDailyUsage({
1236
+ now: options.now,
1237
+ force: options.fresh === true,
1238
+ });
1239
+ return buildUsageDayDetail(history.providerEvents, { date });
1240
+ }
1241
+
1242
+ async getUsageSummary(options = {}) {
1243
+ const now = options.now ?? Date.now();
1244
+ const windowMs = options.windowMs ?? this.windowMs;
1245
+ const historyWindowMs = options.historyWindowMs ?? USAGE_TIMELINE_WINDOW_MS;
1246
+
1247
+ const providerHomes = this.getProviderHomes ? this.getProviderHomes() : undefined;
1248
+ const [
1249
+ codexAuth,
1250
+ claudeAuth,
1251
+ codexUsage,
1252
+ claudeUsage,
1253
+ history,
1254
+ systemStats,
1255
+ ] = await Promise.all([
1256
+ readCodexAuthStatus(this.commandRunner),
1257
+ readClaudeAuthStatus(this.commandRunner),
1258
+ collectCodexUsage({ codexHome: this.codexHome, providerHomes, now, windowMs, historyWindowMs }),
1259
+ collectClaudeUsage({ claudeHome: this.claudeHome, providerHomes, now, windowMs, historyWindowMs }),
1260
+ this.getDailyUsage({ now, force: options.fresh === true }),
1261
+ this.systemMonitor?.getSystemStats ? this.systemMonitor.getSystemStats().catch(() => null) : Promise.resolve(null),
1262
+ ]);
1263
+
1264
+ const openCodeUsage = providerUsageFromEvents(history.providerEvents.opencode, {
1265
+ now,
1266
+ windowMs,
1267
+ historyWindowMs,
1268
+ source: 'opencode session export',
1269
+ });
1270
+ const timeline = buildUsageTimeline({
1271
+ codex: codexUsage.tokenEvents,
1272
+ claude: claudeUsage.tokenEvents,
1273
+ opencode: openCodeUsage.tokenEvents,
1274
+ }, { now, windowMs: historyWindowMs });
1275
+ const qoderCoverage = history.coverage.find(entry => entry.provider === 'qoder');
1276
+
1277
+ return {
1278
+ sampledAt: now,
1279
+ windowMs,
1280
+ timeline,
1281
+ daily: history.daily,
1282
+ providers: [
1283
+ {
1284
+ provider: 'codex',
1285
+ providerName: 'Codex',
1286
+ auth: codexAuth,
1287
+ quota: codexUsage.quota,
1288
+ tokenUsage: codexUsage.tokenUsage,
1289
+ },
1290
+ {
1291
+ provider: 'claude',
1292
+ providerName: 'Claude',
1293
+ auth: claudeAuth,
1294
+ quota: claudeUsage.quota,
1295
+ tokenUsage: claudeUsage.tokenUsage,
1296
+ },
1297
+ {
1298
+ provider: 'opencode',
1299
+ providerName: 'OpenCode',
1300
+ auth: { available: true, status: 'Local session export', source: 'opencode session export' },
1301
+ quota: {
1302
+ available: false,
1303
+ source: 'opencode session export',
1304
+ reason: 'OpenCode session exports do not expose quota remaining.',
1305
+ },
1306
+ tokenUsage: openCodeUsage.tokenUsage,
1307
+ },
1308
+ {
1309
+ provider: 'qoder',
1310
+ providerName: 'Qoder',
1311
+ auth: { available: true, status: 'Local sessions', source: 'Qoder session files' },
1312
+ quota: {
1313
+ available: false,
1314
+ source: 'Qoder session files',
1315
+ reason: qoderCoverage?.reason || 'Qoder quota telemetry is unavailable.',
1316
+ },
1317
+ tokenUsage: {
1318
+ available: false,
1319
+ windowMs,
1320
+ source: 'Qoder session files',
1321
+ totalTokens: null,
1322
+ tokensPerMinute: null,
1323
+ eventCount: 0,
1324
+ sampledAt: now,
1325
+ reason: qoderCoverage?.reason || 'Qoder session files do not expose model token usage.',
1326
+ },
1327
+ },
1328
+ ],
1329
+ agentUsage: this.agentManager?.getAgentUsageSnapshots
1330
+ ? this.agentManager.getAgentUsageSnapshots({ now, windowMs })
1331
+ : null,
1332
+ systemStats,
1333
+ };
1334
+ }
1335
+ }
1336
+
1337
+ module.exports = {
1338
+ USAGE_WINDOW_MS,
1339
+ USAGE_TIMELINE_WINDOW_MS,
1340
+ USAGE_TIMELINE_BUCKET_COUNT,
1341
+ USAGE_DAILY_DAYS,
1342
+ USAGE_DAILY_CACHE_MS,
1343
+ UsageMonitor,
1344
+ buildUsageTimeline,
1345
+ buildDailyUsage,
1346
+ buildUsageDayDetail,
1347
+ collectDailyUsage,
1348
+ collectClaudeUsage,
1349
+ collectCodexUsage,
1350
+ collectOpenCodeDailyEvents,
1351
+ findRecentJsonlFiles,
1352
+ openCodeTokenEventsFromExport,
1353
+ readClaudeAuthStatus,
1354
+ readCodexAuthStatus,
1355
+ collectCodexTokenDeltas,
1356
+ tokenTotalFromUsage,
1357
+ };