spiracha 1.3.3 → 1.5.0

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 (242) hide show
  1. package/AGENTS.md +55 -6
  2. package/README.md +46 -5
  3. package/apps/ui/AGENTS.md +31 -4
  4. package/apps/ui/README.md +59 -5
  5. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +1 -0
  6. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +7 -0
  7. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +1 -0
  8. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +1 -0
  9. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +1 -0
  10. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +1 -0
  11. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +1 -0
  12. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +1 -0
  13. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +1 -0
  14. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +1 -0
  15. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +1 -0
  16. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +1 -0
  17. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +1 -0
  18. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +1 -0
  19. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +1 -0
  20. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +1 -0
  21. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +3 -0
  22. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +1 -0
  23. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +1 -0
  24. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +1 -0
  25. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +1 -0
  26. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +2 -0
  27. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +1 -0
  28. package/apps/ui/dist/client/assets/{data-table-DeQq8xdt.js → data-table-Br78_dk3.js} +2 -2
  29. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +7 -0
  30. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +1 -0
  31. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +5 -0
  32. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +1 -0
  33. package/apps/ui/dist/client/assets/dist-DvMS2965.js +1 -0
  34. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +1 -0
  35. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +1 -0
  36. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +41 -0
  37. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +1 -0
  38. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +1 -0
  39. package/apps/ui/dist/client/assets/index-C-STgIH4.js +166 -0
  40. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +1 -0
  41. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +1 -0
  42. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +1 -0
  43. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +1 -0
  44. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +1 -0
  45. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +1 -0
  46. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +1 -0
  47. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +1 -0
  48. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +1 -0
  49. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +8 -0
  50. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +1 -0
  51. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +1 -0
  52. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +1 -0
  53. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +1 -0
  54. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +1 -0
  55. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +1 -0
  56. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +1 -0
  57. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +1 -0
  58. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +3 -0
  59. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +1 -0
  60. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +1 -0
  61. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +1 -0
  62. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +1 -0
  63. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +1 -0
  64. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +1 -0
  65. package/apps/ui/dist/client/assets/select-CYF6pF37.js +1 -0
  66. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +1 -0
  67. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +1 -0
  68. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +1 -0
  69. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +7 -0
  70. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +2 -0
  71. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +1 -0
  72. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +1 -0
  73. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +1 -0
  74. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +1 -0
  75. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +1 -0
  76. package/apps/ui/dist/client/icon.svg +2 -1
  77. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +387 -0
  78. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +16 -0
  79. package/apps/ui/dist/server/assets/{analytics-Dc_QDZZC.js → analytics-CtpOMVMO.js} +13 -6
  80. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +613 -0
  81. package/apps/ui/dist/server/assets/{antigravity-conversations._conversationId-C9z4AeN-.js → antigravity-conversations._conversationId-eS87Dx7g.js} +2 -2
  82. package/apps/ui/dist/server/assets/{antigravity-db-D9gW1D8G.js → antigravity-db-Bh8_U9uw.js} +11 -7
  83. package/apps/ui/dist/server/assets/{antigravity-keychain-panel-bpPoxhyp.js → antigravity-keychain-panel-CC7FLmAK.js} +1 -1
  84. package/apps/ui/dist/server/assets/{antigravity-queries-DVIKyOXi.js → antigravity-queries-CGrJO9Vr.js} +5 -5
  85. package/apps/ui/dist/server/assets/{antigravity-server-Cp1lc8fp.js → antigravity-server-CHRjVFqN.js} +11 -11
  86. package/apps/ui/dist/server/assets/{antigravity._workspaceKey-Dl8o1ptg.js → antigravity._workspaceKey-B0QeYUOh.js} +2 -2
  87. package/apps/ui/dist/server/assets/{antigravity._workspaceKey-BAoa4izU.js → antigravity._workspaceKey-C3kP-qyN.js} +4 -4
  88. package/apps/ui/dist/server/assets/{antigravity.index-BOlrJasZ.js → antigravity.index-CPCYkqCi.js} +2 -2
  89. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +361 -0
  90. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +37 -0
  91. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +72 -0
  92. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +18 -0
  93. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +473 -0
  94. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +11 -0
  95. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +139 -0
  96. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +10 -0
  97. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +28 -0
  98. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +11 -0
  99. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +191 -0
  100. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +92 -0
  101. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +11 -0
  102. package/apps/ui/dist/server/assets/{codex-queries-ev_mRUzr.js → codex-queries-CtgeZ7VL.js} +15 -13
  103. package/apps/ui/dist/server/assets/{codex-server-DIYFLozB.js → codex-server-CGXU4H8-.js} +421 -146
  104. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +18 -0
  105. package/apps/ui/dist/server/assets/{createServerRpc-Cg-Zr1rq.js → createServerRpc-CF_DEwnm.js} +1 -1
  106. package/apps/ui/dist/server/assets/{createSsrRpc-DVnVQhGx.js → createSsrRpc-C754NPki.js} +1 -1
  107. package/apps/ui/dist/server/assets/{cursor-db-B7agkAvM.js → cursor-db-CZnYy7aT.js} +196 -9
  108. package/apps/ui/dist/server/assets/{cursor-queries-f7DjBKSD.js → cursor-queries-NCIM0Nat.js} +12 -10
  109. package/apps/ui/dist/server/assets/{cursor-recovery-9bJLs7vG.js → cursor-recovery-CXpO8KcD.js} +2 -2
  110. package/apps/ui/dist/server/assets/{cursor-server-nPzojHN3.js → cursor-server-DNu52A3K.js} +71 -42
  111. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +582 -0
  112. package/apps/ui/dist/server/assets/{cursor-threads._composerId-B3qcsh9C.js → cursor-threads._composerId-p9-ARAzc.js} +2 -2
  113. package/apps/ui/dist/server/assets/{cursor-transcript-2iL3KFSK.js → cursor-transcript-B7tGnqtn.js} +1 -1
  114. package/apps/ui/dist/server/assets/{cursor._workspaceKey-BL_CQyHQ.js → cursor._workspaceKey-CZuohIUL.js} +2 -2
  115. package/apps/ui/dist/server/assets/{cursor._workspaceKey-BhQeepXB.js → cursor._workspaceKey-DPp5JSjy.js} +11 -9
  116. package/apps/ui/dist/server/assets/{cursor.index-CsrdckZD.js → cursor.index-DkWT-mxw.js} +2 -2
  117. package/apps/ui/dist/server/assets/{delete-confirm-dialog-PCD7S0_M.js → delete-confirm-dialog-BcBNCxWB.js} +5 -1
  118. package/apps/ui/dist/server/assets/{empty-plugin-adapters-D3qLMPfC.js → empty-plugin-adapters-B3lHh1La.js} +1 -1
  119. package/apps/ui/dist/server/assets/{export-dialog-DaPlOGFT.js → export-dialog-D88ze9Gy.js} +54 -12
  120. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +552 -0
  121. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +37 -0
  122. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +72 -0
  123. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +11 -0
  124. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +449 -0
  125. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +18 -0
  126. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +112 -0
  127. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +27 -0
  128. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +11 -0
  129. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +28 -0
  130. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +193 -0
  131. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +11 -0
  132. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +93 -0
  133. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +397 -0
  134. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +50 -0
  135. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +72 -0
  136. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +518 -0
  137. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +18 -0
  138. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +11 -0
  139. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +87 -0
  140. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +141 -0
  141. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +32 -0
  142. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +11 -0
  143. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +28 -0
  144. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +207 -0
  145. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +11 -0
  146. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +93 -0
  147. package/apps/ui/dist/server/assets/{projects._project-CqqUFr4l.js → projects._project-4io5LO0E.js} +5 -3
  148. package/apps/ui/dist/server/assets/{projects._project-DahGqNYS.js → projects._project-D6ZoGhJ8.js} +42 -12
  149. package/apps/ui/dist/server/assets/{projects.index-CkTeFWNU.js → projects.index-BkLiF2FF.js} +21 -7
  150. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +14 -0
  151. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +38 -0
  152. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +518 -0
  153. package/apps/ui/dist/server/assets/{routes-Xix3ZeSo.js → routes-Bsact1uB.js} +2 -2
  154. package/apps/ui/dist/server/assets/{routes-0x1U0IHq.js → routes-BzkJgq4W.js} +2 -2
  155. package/apps/ui/dist/server/assets/{shared-CPRNYIql.js → shared-DyhChtHf.js} +4 -1
  156. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +38 -0
  157. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +4 -0
  158. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +501 -0
  159. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +45 -0
  160. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +648 -0
  161. package/apps/ui/dist/server/assets/{threads._threadId-BLCkGW--.js → threads._threadId-Ci0S4mKU.js} +2 -2
  162. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +83 -0
  163. package/apps/ui/dist/server/server.js +141 -83
  164. package/package.json +28 -11
  165. package/src/lib/antigravity-db.ts +20 -13
  166. package/src/lib/claude-code-db.ts +655 -0
  167. package/src/lib/claude-code-exporter-types.ts +110 -0
  168. package/src/lib/claude-code-transcript-phase.ts +25 -0
  169. package/src/lib/claude-code-transcript.ts +180 -0
  170. package/src/lib/codex-analytics.ts +118 -18
  171. package/src/lib/codex-browser-db.ts +491 -56
  172. package/src/lib/codex-browser-export.ts +27 -71
  173. package/src/lib/codex-thread-cache.ts +17 -1
  174. package/src/lib/concurrency.ts +25 -0
  175. package/src/lib/cursor-db.ts +318 -7
  176. package/src/lib/cursor-exporter.ts +2 -2
  177. package/src/lib/kiro-db.ts +949 -0
  178. package/src/lib/kiro-exporter-types.ts +100 -0
  179. package/src/lib/kiro-transcript-phase.ts +41 -0
  180. package/src/lib/kiro-transcript.ts +115 -0
  181. package/src/lib/opencode-db.ts +610 -0
  182. package/src/lib/opencode-exporter-types.ts +129 -0
  183. package/src/lib/opencode-think-tags.ts +126 -0
  184. package/src/lib/opencode-transcript-phase.ts +50 -0
  185. package/src/lib/opencode-transcript.ts +194 -0
  186. package/src/lib/shared.ts +25 -0
  187. package/src/lib/ui-cache.ts +14 -1
  188. package/src/lib/ui-export-archive.ts +61 -0
  189. package/apps/ui/dist/client/assets/analytics-C83LcWNb.js +0 -1
  190. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DkY8EEpK.js +0 -1
  191. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-OraHuz8j.js +0 -1
  192. package/apps/ui/dist/client/assets/antigravity-keychain-panel-DnDcq1ge.js +0 -1
  193. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CQaZscA5.js +0 -1
  194. package/apps/ui/dist/client/assets/antigravity._workspaceKey-DTs99obU.js +0 -1
  195. package/apps/ui/dist/client/assets/antigravity.index-DbJa715k.js +0 -1
  196. package/apps/ui/dist/client/assets/antigravity.index-P-rs6w5X.js +0 -1
  197. package/apps/ui/dist/client/assets/badge-wgTVp-aq.js +0 -1
  198. package/apps/ui/dist/client/assets/checkbox-BUx0Gkuf.js +0 -1
  199. package/apps/ui/dist/client/assets/cursor-threads._composerId-D0DFXt8t.js +0 -1
  200. package/apps/ui/dist/client/assets/cursor-threads._composerId-tFa6Cf6A.js +0 -1
  201. package/apps/ui/dist/client/assets/cursor._workspaceKey-Df4PfA2k.js +0 -1
  202. package/apps/ui/dist/client/assets/cursor._workspaceKey-DwrzEtnV.js +0 -1
  203. package/apps/ui/dist/client/assets/cursor.index-CNBf4ZiR.js +0 -1
  204. package/apps/ui/dist/client/assets/cursor.index-DP9Hy8X6.js +0 -2
  205. package/apps/ui/dist/client/assets/delete-confirm-dialog-CPlNsLhM.js +0 -11
  206. package/apps/ui/dist/client/assets/dist-Ds3kS91l.js +0 -1
  207. package/apps/ui/dist/client/assets/download-DLuPnxpV.js +0 -1
  208. package/apps/ui/dist/client/assets/dropdown-menu-lFk6zjoL.js +0 -1
  209. package/apps/ui/dist/client/assets/es2015-D-H_iZF6.js +0 -41
  210. package/apps/ui/dist/client/assets/export-dialog-CyqyvwiP.js +0 -1
  211. package/apps/ui/dist/client/assets/formatters-DbCKP02D.js +0 -1
  212. package/apps/ui/dist/client/assets/index-C-ZPpKJ0.js +0 -22
  213. package/apps/ui/dist/client/assets/json-panel-BsngvucB.js +0 -1
  214. package/apps/ui/dist/client/assets/metadata-section-Dtl8dNag.js +0 -1
  215. package/apps/ui/dist/client/assets/metric-card-DNzR1Rne.js +0 -1
  216. package/apps/ui/dist/client/assets/page-header-dha_O7ZL.js +0 -1
  217. package/apps/ui/dist/client/assets/projects._project-B7HBhbXv.js +0 -1
  218. package/apps/ui/dist/client/assets/projects._project-DHVvuT7A.js +0 -1
  219. package/apps/ui/dist/client/assets/projects.index-BUUtx2AZ.js +0 -1
  220. package/apps/ui/dist/client/assets/projects.index-BojAe1Y4.js +0 -3
  221. package/apps/ui/dist/client/assets/refresh-ccw-B-ASKHtx.js +0 -1
  222. package/apps/ui/dist/client/assets/reload-error-panel-DD8M1BIY.js +0 -1
  223. package/apps/ui/dist/client/assets/routes-q7bzFp35.js +0 -1
  224. package/apps/ui/dist/client/assets/scroll-text-TvHyiJ_N.js +0 -1
  225. package/apps/ui/dist/client/assets/select-BFv1HMiT.js +0 -1
  226. package/apps/ui/dist/client/assets/settings-CvegTo4F.js +0 -1
  227. package/apps/ui/dist/client/assets/styles-Ch0r3kMZ.css +0 -1
  228. package/apps/ui/dist/client/assets/text-document-panel-D7Rws41o.js +0 -1
  229. package/apps/ui/dist/client/assets/text-filter-2q5pEoZh.js +0 -2
  230. package/apps/ui/dist/client/assets/threads._threadId-LpZVUlPq.js +0 -7
  231. package/apps/ui/dist/client/assets/threads._threadId-MSmNIBkq.js +0 -1
  232. package/apps/ui/dist/client/assets/useMutation-BOPJU9e0.js +0 -1
  233. package/apps/ui/dist/client/assets/useQuery-CbxZiVGF.js +0 -1
  234. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-zvGzK-Yx.js +0 -229
  235. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-JEhx-f2R.js +0 -212
  236. package/apps/ui/dist/server/assets/cursor-threads._composerId-BK3iW-3e.js +0 -218
  237. package/apps/ui/dist/server/assets/json-panel-RYsxWFae.js +0 -16
  238. package/apps/ui/dist/server/assets/metadata-section-D6Lbc7D6.js +0 -54
  239. package/apps/ui/dist/server/assets/router-K0m7dK4T.js +0 -394
  240. package/apps/ui/dist/server/assets/start-bX7bXjzw.js +0 -4
  241. package/apps/ui/dist/server/assets/text-document-panel-D8JbQWAn.js +0 -23
  242. package/apps/ui/dist/server/assets/threads._threadId-LjgsGUh-.js +0 -1052
@@ -6,6 +6,7 @@ import { convertSessionFile, writeSessionFileExport } from './codex-exporter-tra
6
6
  import type { CodexCliOptions } from './codex-exporter-types';
7
7
  import { applyPathTransforms, type PathDisplaySettings } from './path-transforms';
8
8
  import { type ExportFormat, getPortablePathBasename } from './shared';
9
+ import { getExportMimeType, sanitizeExportFileName, zipExportDirectory, zipExportFile } from './ui-export-archive';
9
10
  import { buildUiExportDownloadUrl, ensureUiExportDir } from './ui-export-files';
10
11
 
11
12
  type RenderCodexThreadDownloadInput = {
@@ -18,6 +19,7 @@ type RenderCodexThreadDownloadInput = {
18
19
  pathDisplaySettings?: Pick<PathDisplaySettings, 'convertToProjectRoot' | 'redactUsername'>;
19
20
  publicExportDir?: string;
20
21
  threadId: string;
22
+ zipArchive?: boolean;
21
23
  };
22
24
 
23
25
  type RenderCodexThreadsDownloadInput = Omit<RenderCodexThreadDownloadInput, 'threadId'> & {
@@ -40,14 +42,6 @@ export type CodexThreadDownload =
40
42
 
41
43
  const LARGE_BROWSER_EXPORT_THRESHOLD_BYTES = 128 * 1024 * 1024;
42
44
 
43
- const toSanitizedFileName = (value: string) => {
44
- return value
45
- .replace(/[<>:"/\\|?*\u0000-\u001f]/gu, ' ')
46
- .replace(/\.\.+/gu, ' ')
47
- .replace(/\s+/gu, ' ')
48
- .trim();
49
- };
50
-
51
45
  const formatReadableExportDate = (value: number) => {
52
46
  const date = new Date(value);
53
47
  const year = date.getUTCFullYear();
@@ -59,7 +53,7 @@ const formatReadableExportDate = (value: number) => {
59
53
  };
60
54
 
61
55
  const buildExportBaseName = (thread: ReturnType<typeof getThreadBrowseData>['thread']) => {
62
- const projectName = toSanitizedFileName(getPortablePathBasename(thread.cwd) || 'thread') || 'thread';
56
+ const projectName = sanitizeExportFileName(getPortablePathBasename(thread.cwd) || 'thread') || 'thread';
63
57
  const timestamp = thread.updated_at_ms ?? thread.updated_at * 1000;
64
58
  return `${projectName}-${formatReadableExportDate(timestamp)}-${thread.id.slice(0, 8)}`;
65
59
  };
@@ -70,7 +64,7 @@ const buildBatchExportBaseName = (threads: Array<ReturnType<typeof getThreadBrow
70
64
  throw new Error('No threads selected for export');
71
65
  }
72
66
 
73
- const projectName = toSanitizedFileName(getPortablePathBasename(firstThread.cwd) || 'threads') || 'threads';
67
+ const projectName = sanitizeExportFileName(getPortablePathBasename(firstThread.cwd) || 'threads') || 'threads';
74
68
  const latestTimestamp = Math.max(...threads.map((thread) => thread.updated_at_ms ?? thread.updated_at * 1000));
75
69
  return `${projectName}-${formatReadableExportDate(latestTimestamp)}-threads-${threads.length}`;
76
70
  };
@@ -111,10 +105,6 @@ const toDownloadOptions = (input: RenderCodexThreadDownloadInput): CodexCliOptio
111
105
  };
112
106
  };
113
107
 
114
- const getMimeType = (outputFormat: ExportFormat) => {
115
- return outputFormat === 'md' ? 'text/markdown; charset=utf-8' : 'text/plain; charset=utf-8';
116
- };
117
-
118
108
  const resolvePublicExportDir = async (publicExportDir?: string) => {
119
109
  if (publicExportDir) {
120
110
  await ensureDirectory(publicExportDir);
@@ -140,13 +130,28 @@ const getRolloutSnapshot = async (rolloutPath: string): Promise<RolloutSnapshot>
140
130
  };
141
131
  };
142
132
 
133
+ const isMissingFileError = (error: unknown) => {
134
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
135
+ };
136
+
137
+ const getExistingRolloutSnapshot = async (threadId: string, rolloutPath: string): Promise<RolloutSnapshot> => {
138
+ try {
139
+ return await getRolloutSnapshot(rolloutPath);
140
+ } catch (error) {
141
+ if (isMissingFileError(error)) {
142
+ throw new Error(`Thread ${threadId} rollout file is missing: ${rolloutPath}`, { cause: error });
143
+ }
144
+
145
+ throw error;
146
+ }
147
+ };
148
+
143
149
  const logExportEvent = (level: 'error' | 'info' | 'warn', event: string, details: Record<string, unknown>) => {
144
150
  console[level](`[spiracha:export] ${event}`, details);
145
151
  };
146
152
 
147
153
  const logRolloutChangeIfDetected = (
148
154
  threadId: string,
149
- rolloutPath: string,
150
155
  beforeSnapshot: RolloutSnapshot,
151
156
  afterSnapshot: RolloutSnapshot,
152
157
  ) => {
@@ -159,7 +164,6 @@ const logRolloutChangeIfDetected = (
159
164
  afterSizeBytes: afterSnapshot.sizeBytes,
160
165
  beforeMtimeMs: beforeSnapshot.mtimeMs,
161
166
  beforeSizeBytes: beforeSnapshot.sizeBytes,
162
- rolloutPath,
163
167
  threadId,
164
168
  });
165
169
  };
@@ -175,39 +179,6 @@ const cleanupExportWorkspace = async (workspacePath: string) => {
175
179
  }
176
180
  };
177
181
 
178
- const zipExportFile = async (sourcePath: string, zipPath: string) => {
179
- const proc = Bun.spawn(['zip', '-9', '-j', zipPath, sourcePath], {
180
- stderr: 'pipe',
181
- stdout: 'pipe',
182
- });
183
- const [stdoutText, stderrText, exitCode] = await Promise.all([
184
- new Response(proc.stdout).text(),
185
- new Response(proc.stderr).text(),
186
- proc.exited,
187
- ]);
188
-
189
- if (exitCode !== 0) {
190
- throw new Error(`zip failed (${exitCode}): ${(stderrText || stdoutText).trim()}`);
191
- }
192
- };
193
-
194
- const zipExportDirectory = async (sourceDirectory: string, zipPath: string) => {
195
- const proc = Bun.spawn(['zip', '-9', '-r', zipPath, '.'], {
196
- cwd: sourceDirectory,
197
- stderr: 'pipe',
198
- stdout: 'pipe',
199
- });
200
- const [stdoutText, stderrText, exitCode] = await Promise.all([
201
- new Response(proc.stdout).text(),
202
- new Response(proc.stderr).text(),
203
- proc.exited,
204
- ]);
205
-
206
- if (exitCode !== 0) {
207
- throw new Error(`zip failed (${exitCode}): ${(stderrText || stdoutText).trim()}`);
208
- }
209
- };
210
-
211
182
  export const renderCodexThreadDownload = async (
212
183
  input: RenderCodexThreadDownloadInput,
213
184
  ): Promise<CodexThreadDownload> => {
@@ -216,7 +187,7 @@ export const renderCodexThreadDownload = async (
216
187
  const extension = input.outputFormat === 'md' ? 'md' : 'txt';
217
188
  const fileBaseName = buildExportBaseName(browseData.thread);
218
189
  const fileName = `${fileBaseName}.${extension}`;
219
- const mimeType = getMimeType(input.outputFormat);
190
+ const mimeType = getExportMimeType(input.outputFormat);
220
191
  const transform = (text: string) =>
221
192
  input.pathDisplaySettings
222
193
  ? applyPathTransforms(text, {
@@ -224,17 +195,17 @@ export const renderCodexThreadDownload = async (
224
195
  projectPath: browseData.thread.cwd,
225
196
  })
226
197
  : text;
227
- const rolloutSnapshotBefore = await getRolloutSnapshot(browseData.thread.rollout_path);
198
+ const rolloutSnapshotBefore = await getExistingRolloutSnapshot(input.threadId, browseData.thread.rollout_path);
228
199
 
229
200
  logExportEvent('info', 'single_start', {
230
201
  fileName,
231
- rolloutPath: browseData.thread.rollout_path,
232
202
  sizeBytes: rolloutSnapshotBefore.sizeBytes,
233
203
  threadId: input.threadId,
234
204
  });
235
205
 
236
206
  try {
237
207
  if (
208
+ input.zipArchive ||
238
209
  rolloutSnapshotBefore.sizeBytes > (input.largeExportThresholdBytes ?? LARGE_BROWSER_EXPORT_THRESHOLD_BYTES)
239
210
  ) {
240
211
  const exportBaseName = fileBaseName;
@@ -266,12 +237,7 @@ export const renderCodexThreadDownload = async (
266
237
  }
267
238
 
268
239
  const rolloutSnapshotAfter = await getRolloutSnapshot(browseData.thread.rollout_path);
269
- logRolloutChangeIfDetected(
270
- input.threadId,
271
- browseData.thread.rollout_path,
272
- rolloutSnapshotBefore,
273
- rolloutSnapshotAfter,
274
- );
240
+ logRolloutChangeIfDetected(input.threadId, rolloutSnapshotBefore, rolloutSnapshotAfter);
275
241
 
276
242
  const zipStat = await Bun.file(zipPath).stat();
277
243
  logExportEvent('info', 'single_zip_ready', {
@@ -307,12 +273,7 @@ export const renderCodexThreadDownload = async (
307
273
  }
308
274
 
309
275
  const rolloutSnapshotAfter = await getRolloutSnapshot(browseData.thread.rollout_path);
310
- logRolloutChangeIfDetected(
311
- input.threadId,
312
- browseData.thread.rollout_path,
313
- rolloutSnapshotBefore,
314
- rolloutSnapshotAfter,
315
- );
276
+ logRolloutChangeIfDetected(input.threadId, rolloutSnapshotBefore, rolloutSnapshotAfter);
316
277
  logExportEvent('info', 'single_inline_ready', {
317
278
  durationMs: Date.now() - startedAt,
318
279
  fileName,
@@ -362,7 +323,7 @@ export const renderCodexThreadsDownload = async (
362
323
 
363
324
  try {
364
325
  for (const entry of browseEntries) {
365
- const rolloutSnapshotBefore = await getRolloutSnapshot(entry.thread.rollout_path);
326
+ const rolloutSnapshotBefore = await getExistingRolloutSnapshot(entry.thread.id, entry.thread.rollout_path);
366
327
  const singleBaseName = buildExportBaseName(entry.thread);
367
328
  const uniqueBaseName = buildUniqueBatchEntryBaseName(
368
329
  singleBaseName,
@@ -412,12 +373,7 @@ export const renderCodexThreadsDownload = async (
412
373
  }
413
374
 
414
375
  const rolloutSnapshotAfter = await getRolloutSnapshot(entry.thread.rollout_path);
415
- logRolloutChangeIfDetected(
416
- entry.thread.id,
417
- entry.thread.rollout_path,
418
- rolloutSnapshotBefore,
419
- rolloutSnapshotAfter,
420
- );
376
+ logRolloutChangeIfDetected(entry.thread.id, rolloutSnapshotBefore, rolloutSnapshotAfter);
421
377
  }
422
378
 
423
379
  await zipExportDirectory(bundleDirectory, zipPath);
@@ -7,6 +7,10 @@ import { getFileFingerprint, hashCacheKeyParts, withCachedJson } from './ui-cach
7
7
  export const LARGE_THREAD_SIZE_BYTES = 100 * 1024 * 1024;
8
8
  export const LARGE_THREAD_PREVIEW_EVENT_LIMIT = 200;
9
9
 
10
+ const isMissingFileError = (error: unknown) => {
11
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
12
+ };
13
+
10
14
  export const getCachedParsedCodexTranscript = async (sessionFile: string): Promise<ParsedCodexTranscript> => {
11
15
  const fingerprint = await getFileFingerprint(sessionFile);
12
16
  const key = `thread-${hashCacheKeyParts(path.basename(sessionFile), fingerprint)}`;
@@ -23,7 +27,19 @@ export const getThreadRolloutLoadState = async (
23
27
  sessionFile: string,
24
28
  largeTranscriptThresholdBytes = LARGE_THREAD_SIZE_BYTES,
25
29
  ) => {
26
- const metadata = await stat(sessionFile);
30
+ let metadata: Awaited<ReturnType<typeof stat>>;
31
+ try {
32
+ metadata = await stat(sessionFile);
33
+ } catch (error) {
34
+ if (isMissingFileError(error)) {
35
+ return {
36
+ fileSizeBytes: null,
37
+ shouldDeferTranscriptLoad: false,
38
+ };
39
+ }
40
+
41
+ throw error;
42
+ }
27
43
 
28
44
  return {
29
45
  fileSizeBytes: metadata.size,
@@ -0,0 +1,25 @@
1
+ export const mapWithConcurrency = async <T, TResult>(
2
+ values: T[],
3
+ limit: number,
4
+ mapper: (value: T, index: number) => Promise<TResult>,
5
+ ) => {
6
+ if (values.length === 0) {
7
+ return [];
8
+ }
9
+
10
+ const requestedLimit = Number.isFinite(limit) ? Math.floor(limit) : 1;
11
+ const workerLimit = Math.max(1, requestedLimit);
12
+ const results = new Array<TResult>(values.length);
13
+ let nextIndex = 0;
14
+
15
+ const worker = async () => {
16
+ while (nextIndex < values.length) {
17
+ const currentIndex = nextIndex;
18
+ nextIndex += 1;
19
+ results[currentIndex] = await mapper(values[currentIndex]!, currentIndex);
20
+ }
21
+ };
22
+
23
+ await Promise.all(Array.from({ length: Math.min(workerLimit, values.length) }, () => worker()));
24
+ return results;
25
+ };
@@ -1,4 +1,5 @@
1
1
  import { constants, Database } from 'bun:sqlite';
2
+ import { createHash } from 'node:crypto';
2
3
  import { readdir, stat } from 'node:fs/promises';
3
4
  import path from 'node:path';
4
5
  import {
@@ -109,13 +110,29 @@ const normalizeCursorPath = (value: string): string => {
109
110
  return decoded.replace(/\/+$/u, '') || decoded;
110
111
  };
111
112
 
113
+ const warnCursorDataIssue = (event: string, details: Record<string, unknown>) => {
114
+ console.warn(`[spiracha:cursor] ${event}`, details);
115
+ };
116
+
117
+ const stripJsonComments = (value: string): string => {
118
+ return value.replace(/\/\*[\s\S]*?\*\//gu, '').replace(/(^|\s)\/\/.*$/gmu, '$1');
119
+ };
120
+
121
+ const parseCodeWorkspaceJson = (text: string): { folders?: Array<{ path?: string }> } => {
122
+ try {
123
+ return JSON.parse(text) as { folders?: Array<{ path?: string }> };
124
+ } catch {
125
+ return JSON.parse(stripJsonComments(text)) as { folders?: Array<{ path?: string }> };
126
+ }
127
+ };
128
+
112
129
  const parseCodeWorkspaceFolders = async (workspaceFilePath: string): Promise<string[]> => {
113
130
  if (!workspaceFilePath.endsWith('.code-workspace')) {
114
131
  return [];
115
132
  }
116
133
 
117
134
  try {
118
- const data = (await Bun.file(workspaceFilePath).json()) as { folders?: Array<{ path?: string }> };
135
+ const data = parseCodeWorkspaceJson(await Bun.file(workspaceFilePath).text());
119
136
  const folders: string[] = [];
120
137
  for (const entry of data.folders ?? []) {
121
138
  const folderPath = entry.path;
@@ -131,18 +148,30 @@ const parseCodeWorkspaceFolders = async (workspaceFilePath: string): Promise<str
131
148
  }
132
149
 
133
150
  return folders;
134
- } catch {
151
+ } catch (error) {
152
+ warnCursorDataIssue('invalid_code_workspace_json', {
153
+ error: error instanceof Error ? error.message : String(error),
154
+ workspaceFilePath,
155
+ });
135
156
  return [];
136
157
  }
137
158
  };
138
159
 
139
160
  export const loadGlobalComposerHeaders = (globalDbPath: string): ComposerEntry[] => {
140
- const db = openCursorReadonlyDb(globalDbPath);
141
161
  try {
142
- const data = readItemValue<{ allComposers?: ComposerEntry[] }>(db, COMPOSER_HEADERS_KEY);
143
- return data?.allComposers ?? [];
144
- } finally {
145
- db.close();
162
+ const db = openCursorReadonlyDb(globalDbPath);
163
+ try {
164
+ const data = readItemValue<{ allComposers?: ComposerEntry[] }>(db, COMPOSER_HEADERS_KEY);
165
+ return data?.allComposers ?? [];
166
+ } finally {
167
+ db.close();
168
+ }
169
+ } catch (error) {
170
+ warnCursorDataIssue('global_composer_headers_unavailable', {
171
+ error: error instanceof Error ? error.message : String(error),
172
+ globalDbPath,
173
+ });
174
+ return [];
146
175
  }
147
176
  };
148
177
 
@@ -1055,6 +1084,274 @@ const isRenderableBubble = (bubble: CursorBubble): boolean => {
1055
1084
  return Boolean(bubble.text.trim() || bubble.thinking?.trim() || bubble.toolCall);
1056
1085
  };
1057
1086
 
1087
+ const normalizeBubbleText = (value: string | null): string => {
1088
+ return (value ?? '').replace(/\s+/gu, ' ').trim();
1089
+ };
1090
+
1091
+ const hashText = (value: string) => createHash('sha1').update(value).digest('hex').slice(0, 12);
1092
+
1093
+ const stableStringifyJson = (value: JsonValue): string => {
1094
+ if (Array.isArray(value)) {
1095
+ return `[${value.map(stableStringifyJson).join(',')}]`;
1096
+ }
1097
+
1098
+ if (value && typeof value === 'object') {
1099
+ return `{${Object.keys(value)
1100
+ .sort()
1101
+ .map((key) => `${JSON.stringify(key)}:${stableStringifyJson((value as Record<string, JsonValue>)[key]!)}`)
1102
+ .join(',')}}`;
1103
+ }
1104
+
1105
+ return JSON.stringify(value);
1106
+ };
1107
+
1108
+ const normalizeToolArgumentsText = (value: string | null): string => {
1109
+ if (!value?.trim()) {
1110
+ return '';
1111
+ }
1112
+
1113
+ try {
1114
+ return stableStringifyJson(JSON.parse(value) as JsonValue);
1115
+ } catch {
1116
+ return normalizeBubbleText(value);
1117
+ }
1118
+ };
1119
+
1120
+ const hasRenderableTextSuperset = (left: string | null, right: string | null): boolean => {
1121
+ const normalizedLeft = normalizeBubbleText(left);
1122
+ const normalizedRight = normalizeBubbleText(right);
1123
+ if (!normalizedLeft && !normalizedRight) {
1124
+ return true;
1125
+ }
1126
+
1127
+ return (
1128
+ normalizedLeft === normalizedRight ||
1129
+ normalizedLeft.startsWith(normalizedRight) ||
1130
+ normalizedRight.startsWith(normalizedLeft)
1131
+ );
1132
+ };
1133
+
1134
+ const haveSameToolIdentity = (left: CursorToolCall | null, right: CursorToolCall | null): boolean => {
1135
+ if (!left && !right) {
1136
+ return true;
1137
+ }
1138
+
1139
+ if (!left || !right) {
1140
+ return false;
1141
+ }
1142
+
1143
+ return (
1144
+ left.name === right.name &&
1145
+ normalizeToolArgumentsText(left.argumentsText) === normalizeToolArgumentsText(right.argumentsText)
1146
+ );
1147
+ };
1148
+
1149
+ const areEquivalentBubbles = (left: CursorBubble, right: CursorBubble): boolean => {
1150
+ return (
1151
+ left.kind === right.kind &&
1152
+ hasRenderableTextSuperset(left.text, right.text) &&
1153
+ Boolean(normalizeBubbleText(left.thinking)) === Boolean(normalizeBubbleText(right.thinking)) &&
1154
+ haveSameToolIdentity(left.toolCall, right.toolCall)
1155
+ );
1156
+ };
1157
+
1158
+ const findAgentTailStartIndex = (existingBubbles: CursorBubble[], agentBubbles: CursorBubble[]): number => {
1159
+ const maxOverlap = Math.min(existingBubbles.length, agentBubbles.length);
1160
+ // Agent transcript files contain the complete run while SQLite may lag or truncate the tail.
1161
+ // Match the longest SQLite suffix to the agent prefix, then append only the remaining agent tail.
1162
+ for (let overlapLength = maxOverlap; overlapLength > 0; overlapLength -= 1) {
1163
+ const existingStart = existingBubbles.length - overlapLength;
1164
+ const matches = agentBubbles
1165
+ .slice(0, overlapLength)
1166
+ .every((bubble, index) => areEquivalentBubbles(existingBubbles[existingStart + index]!, bubble));
1167
+ if (matches) {
1168
+ return overlapLength;
1169
+ }
1170
+ }
1171
+
1172
+ return 0;
1173
+ };
1174
+
1175
+ const hasEquivalentBubble = (bubbles: CursorBubble[], candidate: CursorBubble): boolean => {
1176
+ return bubbles.some((bubble) => areEquivalentBubbles(bubble, candidate));
1177
+ };
1178
+
1179
+ const getAgentTranscriptContentParts = (entry: Record<string, JsonValue>): Record<string, JsonValue>[] => {
1180
+ const message = asObject(entry.message ?? null);
1181
+ const content = message?.content ?? entry.content ?? null;
1182
+ if (Array.isArray(content)) {
1183
+ return content.map((part) => asObject(part)).filter((part): part is Record<string, JsonValue> => Boolean(part));
1184
+ }
1185
+
1186
+ if (typeof content === 'string') {
1187
+ return [{ text: content, type: 'text' }];
1188
+ }
1189
+
1190
+ return [];
1191
+ };
1192
+
1193
+ const parseAgentTranscriptToolCall = (parts: Record<string, JsonValue>[]): CursorToolCall | null => {
1194
+ const toolUse = parts.find((part) => asString(part.type ?? null) === 'tool_use');
1195
+ if (!toolUse) {
1196
+ return null;
1197
+ }
1198
+
1199
+ const name = asString(toolUse.name ?? null);
1200
+ if (!name) {
1201
+ return null;
1202
+ }
1203
+
1204
+ return {
1205
+ argumentsText: toolUse.input === undefined ? null : JSON.stringify(toolUse.input),
1206
+ callId: asString(toolUse.id ?? null),
1207
+ name,
1208
+ resultText: null,
1209
+ status: null,
1210
+ };
1211
+ };
1212
+
1213
+ const parseAgentTranscriptBubble = (
1214
+ filePath: string,
1215
+ lineNumber: number,
1216
+ raw: Record<string, JsonValue>,
1217
+ ): CursorBubble | null => {
1218
+ const message = asObject(raw.message ?? null);
1219
+ const role = asString(raw.role ?? message?.role ?? null);
1220
+ const kind = role === 'user' || role === 'assistant' ? role : 'unknown';
1221
+ const parts = getAgentTranscriptContentParts(raw);
1222
+ const text = parts
1223
+ .filter((part) => asString(part.type ?? null) === 'text')
1224
+ .map((part) => asString(part.text ?? null))
1225
+ .filter((value): value is string => Boolean(value?.trim()))
1226
+ .join('\n\n');
1227
+ const toolCall = parseAgentTranscriptToolCall(parts);
1228
+ const bubble: CursorBubble = {
1229
+ bubbleId: `agent-transcript:${hashText(path.resolve(filePath))}:${path.basename(filePath)}:${lineNumber}`,
1230
+ createdAtMs: null,
1231
+ kind,
1232
+ text,
1233
+ thinking: null,
1234
+ toolCall,
1235
+ };
1236
+
1237
+ return isRenderableBubble(bubble) ? bubble : null;
1238
+ };
1239
+
1240
+ const readCursorAgentTranscriptFile = async (filePath: string): Promise<CursorBubble[]> => {
1241
+ let text = '';
1242
+ try {
1243
+ text = await Bun.file(filePath).text();
1244
+ } catch (error) {
1245
+ warnCursorDataIssue('agent_transcript_unreadable', {
1246
+ error: error instanceof Error ? error.message : String(error),
1247
+ filePath,
1248
+ });
1249
+ return [];
1250
+ }
1251
+
1252
+ const bubbles: CursorBubble[] = [];
1253
+ for (const [index, line] of text.split(/\n/u).entries()) {
1254
+ if (!line.trim()) {
1255
+ continue;
1256
+ }
1257
+
1258
+ let raw: JsonValue;
1259
+ try {
1260
+ raw = JSON.parse(line) as JsonValue;
1261
+ } catch (error) {
1262
+ warnCursorDataIssue('invalid_agent_transcript_jsonl', {
1263
+ error: error instanceof Error ? error.message : String(error),
1264
+ filePath,
1265
+ lineNumber: index + 1,
1266
+ });
1267
+ continue;
1268
+ }
1269
+
1270
+ const entry = asObject(raw);
1271
+ if (!entry) {
1272
+ continue;
1273
+ }
1274
+
1275
+ const bubble = parseAgentTranscriptBubble(filePath, index + 1, entry);
1276
+ if (bubble) {
1277
+ bubbles.push(bubble);
1278
+ }
1279
+ }
1280
+
1281
+ return bubbles;
1282
+ };
1283
+
1284
+ const listCursorAgentTranscriptFiles = async (transcriptDir: string, composerId: string): Promise<string[]> => {
1285
+ const preferred = path.join(transcriptDir, `${composerId}.jsonl`);
1286
+ const files = new Set<string>();
1287
+ if (await pathExists(preferred)) {
1288
+ files.add(preferred);
1289
+ }
1290
+
1291
+ let entries: string[] = [];
1292
+ try {
1293
+ entries = await readdir(transcriptDir);
1294
+ } catch {
1295
+ return [...files];
1296
+ }
1297
+
1298
+ for (const entry of entries) {
1299
+ if (entry.endsWith('.jsonl')) {
1300
+ files.add(path.join(transcriptDir, entry));
1301
+ }
1302
+ }
1303
+
1304
+ return [...files].sort();
1305
+ };
1306
+
1307
+ const readCursorAgentTranscriptBubbles = async (composerId: string, userDir: string): Promise<CursorBubble[]> => {
1308
+ const transcriptDirs = await findCursorTranscriptDirs(composerId, userDir);
1309
+ const bubbles: CursorBubble[] = [];
1310
+ for (const transcriptDir of transcriptDirs.sort()) {
1311
+ const files = await listCursorAgentTranscriptFiles(transcriptDir, composerId);
1312
+ for (const file of files) {
1313
+ bubbles.push(...(await readCursorAgentTranscriptFile(file)));
1314
+ }
1315
+ }
1316
+
1317
+ return bubbles;
1318
+ };
1319
+
1320
+ const mergeAgentTranscriptTail = (
1321
+ transcript: CursorThreadTranscript,
1322
+ agentBubbles: CursorBubble[],
1323
+ ): CursorThreadTranscript => {
1324
+ const tailStartIndex = findAgentTailStartIndex(transcript.bubbles, agentBubbles);
1325
+ const seen = [...transcript.bubbles];
1326
+ const appended: CursorBubble[] = [];
1327
+ const candidates = agentBubbles.slice(tailStartIndex);
1328
+ for (const bubble of candidates) {
1329
+ if (hasEquivalentBubble(seen, bubble)) {
1330
+ continue;
1331
+ }
1332
+
1333
+ seen.push(bubble);
1334
+ appended.push(bubble);
1335
+ }
1336
+
1337
+ if (appended.length === 0) {
1338
+ return transcript;
1339
+ }
1340
+
1341
+ return {
1342
+ ...transcript,
1343
+ bubbles: [...transcript.bubbles, ...appended],
1344
+ renderableBubbleCount: transcript.renderableBubbleCount + appended.length,
1345
+ };
1346
+ };
1347
+
1348
+ const inferCursorUserDirFromGlobalDbPath = (globalDbPath: string): string => {
1349
+ const globalStorageDir = path.dirname(globalDbPath);
1350
+ return path.basename(globalStorageDir) === 'globalStorage'
1351
+ ? path.dirname(globalStorageDir)
1352
+ : resolveCursorUserDir();
1353
+ };
1354
+
1058
1355
  export const readCursorThreadTranscript = (globalDbPath: string, composerId: string): CursorThreadTranscript | null => {
1059
1356
  const head = readCursorThreadHead(globalDbPath, composerId);
1060
1357
  if (!head) {
@@ -1087,6 +1384,20 @@ export const readCursorThreadTranscript = (globalDbPath: string, composerId: str
1087
1384
  }
1088
1385
  };
1089
1386
 
1387
+ export const readCursorThreadTranscriptWithAgentFiles = async (
1388
+ globalDbPath: string,
1389
+ composerId: string,
1390
+ userDir = inferCursorUserDirFromGlobalDbPath(globalDbPath),
1391
+ ): Promise<CursorThreadTranscript | null> => {
1392
+ const transcript = readCursorThreadTranscript(globalDbPath, composerId);
1393
+ if (!transcript) {
1394
+ return null;
1395
+ }
1396
+
1397
+ const agentBubbles = await readCursorAgentTranscriptBubbles(composerId, userDir);
1398
+ return mergeAgentTranscriptTail(transcript, agentBubbles);
1399
+ };
1400
+
1090
1401
  const readAllBubbleIds = (db: Database, composerId: string): string[] => {
1091
1402
  const prefix = `bubbleId:${composerId}:`;
1092
1403
  const rows = db.query('SELECT key FROM cursorDiskKV WHERE key LIKE ? ORDER BY key ASC').all(`${prefix}%`) as Array<{
@@ -4,7 +4,7 @@ import {
4
4
  listCursorThreadsForGroup,
5
5
  listCursorWorkspaceGroups,
6
6
  readCursorThreadHead,
7
- readCursorThreadTranscript,
7
+ readCursorThreadTranscriptWithAgentFiles,
8
8
  } from './cursor-db';
9
9
  import {
10
10
  type CursorCliOptions,
@@ -115,7 +115,7 @@ const exportSingleThread = async (
115
115
  outputDir: string,
116
116
  options: CursorCliOptions,
117
117
  ): Promise<CursorExportedFile | null> => {
118
- const transcript = readCursorThreadTranscript(globalDbPath, thread.composerId);
118
+ const transcript = await readCursorThreadTranscriptWithAgentFiles(globalDbPath, thread.composerId, options.userDir);
119
119
  if (!transcript) {
120
120
  return null;
121
121
  }