spiracha 1.5.0 → 2.0.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 (365) hide show
  1. package/AGENTS.md +114 -205
  2. package/README.md +89 -269
  3. package/apps/ui/AGENTS.md +17 -7
  4. package/apps/ui/README.md +34 -8
  5. package/apps/ui/components.json +21 -0
  6. package/apps/ui/package.json +65 -0
  7. package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
  8. package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
  9. package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
  10. package/apps/ui/src/components/app-shell.tsx +124 -0
  11. package/apps/ui/src/components/breadcrumbs.tsx +53 -0
  12. package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
  13. package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
  14. package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
  15. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  16. package/apps/ui/src/components/data-table.tsx +241 -0
  17. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  18. package/apps/ui/src/components/export-dialog.tsx +184 -0
  19. package/apps/ui/src/components/json-panel.tsx +17 -0
  20. package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
  21. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  22. package/apps/ui/src/components/list-search-input.tsx +18 -0
  23. package/apps/ui/src/components/loading-panel.tsx +23 -0
  24. package/apps/ui/src/components/metadata-section.tsx +31 -0
  25. package/apps/ui/src/components/metric-card.tsx +19 -0
  26. package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
  27. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  28. package/apps/ui/src/components/page-header.tsx +33 -0
  29. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  30. package/apps/ui/src/components/projects-table.tsx +108 -0
  31. package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
  32. package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
  33. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  34. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  35. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  36. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  37. package/apps/ui/src/components/threads-table.tsx +202 -0
  38. package/apps/ui/src/components/transcript-view.tsx +552 -0
  39. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  40. package/apps/ui/src/components/ui/badge.tsx +45 -0
  41. package/apps/ui/src/components/ui/button.tsx +62 -0
  42. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  43. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  44. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  45. package/apps/ui/src/components/ui/input.tsx +21 -0
  46. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  47. package/apps/ui/src/components/ui/select.tsx +163 -0
  48. package/apps/ui/src/components/ui/separator.tsx +28 -0
  49. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  50. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  51. package/apps/ui/src/components/ui/table.tsx +76 -0
  52. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  53. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  54. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  55. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  56. package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
  57. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  58. package/apps/ui/src/lib/antigravity-server.ts +128 -0
  59. package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
  60. package/apps/ui/src/lib/claude-code-queries.ts +26 -0
  61. package/apps/ui/src/lib/claude-code-server.ts +75 -0
  62. package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
  63. package/apps/ui/src/lib/codex-queries.ts +66 -0
  64. package/apps/ui/src/lib/codex-server.ts +209 -0
  65. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  66. package/apps/ui/src/lib/cursor-server.ts +272 -0
  67. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  68. package/apps/ui/src/lib/download.ts +166 -0
  69. package/apps/ui/src/lib/formatters.ts +149 -0
  70. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  71. package/apps/ui/src/lib/kiro-server.ts +73 -0
  72. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  73. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  74. package/apps/ui/src/lib/opencode-server.ts +73 -0
  75. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  76. package/apps/ui/src/lib/package-metadata.ts +16 -0
  77. package/apps/ui/src/lib/path-utils.ts +13 -0
  78. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  79. package/apps/ui/src/lib/qoder-server.ts +79 -0
  80. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  81. package/apps/ui/src/lib/route-search.ts +68 -0
  82. package/apps/ui/src/lib/settings-store.tsx +59 -0
  83. package/apps/ui/src/lib/source-session-export-server.ts +56 -0
  84. package/apps/ui/src/lib/text-filter.ts +22 -0
  85. package/apps/ui/src/lib/thread-id.ts +3 -0
  86. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  87. package/apps/ui/src/lib/utils.ts +7 -0
  88. package/apps/ui/src/routeTree.gen.ts +730 -0
  89. package/apps/ui/src/router.tsx +20 -0
  90. package/apps/ui/src/routes/$threadId.tsx +22 -0
  91. package/apps/ui/src/routes/__root.tsx +127 -0
  92. package/apps/ui/src/routes/analytics.tsx +143 -0
  93. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
  94. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
  95. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  96. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  97. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  98. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
  99. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  100. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  101. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  102. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
  103. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
  104. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  105. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  106. package/apps/ui/src/routes/codex.index.tsx +116 -0
  107. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  108. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
  109. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  110. package/apps/ui/src/routes/index.tsx +124 -0
  111. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
  112. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
  113. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  114. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
  115. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
  116. package/apps/ui/src/routes/opencode.index.tsx +48 -0
  117. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
  118. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
  119. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  120. package/apps/ui/src/routes/settings.tsx +91 -0
  121. package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
  122. package/apps/ui/src/styles.css +122 -0
  123. package/apps/ui/tsconfig.json +29 -0
  124. package/apps/ui/vite.config.ts +84 -0
  125. package/bin/spiracha.ts +32 -0
  126. package/package.json +46 -73
  127. package/src/client.ts +290 -0
  128. package/src/lib/antigravity-db.ts +213 -19
  129. package/src/lib/antigravity-exporter-types.ts +1 -0
  130. package/src/lib/codex-analytics.ts +1 -1
  131. package/src/lib/codex-browser-db.ts +410 -74
  132. package/src/lib/codex-browser-export.ts +6 -14
  133. package/src/lib/codex-browser-types.ts +1 -1
  134. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  135. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
  136. package/src/lib/conversation-api.ts +792 -0
  137. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  138. package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
  139. package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
  140. package/src/lib/conversation-data/codex-adapter.ts +245 -0
  141. package/src/lib/conversation-data/cursor-adapter.ts +171 -0
  142. package/src/lib/conversation-data/index.ts +311 -0
  143. package/src/lib/conversation-data/kiro-adapter.ts +140 -0
  144. package/src/lib/conversation-data/message-selector.ts +37 -0
  145. package/src/lib/conversation-data/opencode-adapter.ts +180 -0
  146. package/src/lib/conversation-data/path-match.ts +79 -0
  147. package/src/lib/conversation-data/qoder-adapter.ts +230 -0
  148. package/src/lib/conversation-data/types.ts +116 -0
  149. package/src/lib/cursor-db.ts +67 -21
  150. package/src/lib/cursor-exporter-types.ts +0 -19
  151. package/src/lib/qoder-acp-client.ts +268 -0
  152. package/src/lib/qoder-db.ts +1601 -0
  153. package/src/lib/qoder-exporter-types.ts +114 -0
  154. package/src/lib/qoder-transcript-phase.ts +41 -0
  155. package/src/lib/qoder-transcript.ts +112 -0
  156. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  157. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +0 -1
  158. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +0 -7
  159. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  160. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +0 -1
  161. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +0 -1
  162. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  163. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +0 -1
  164. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  165. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +0 -1
  166. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  167. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +0 -1
  168. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +0 -1
  169. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +0 -1
  170. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +0 -1
  171. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +0 -1
  172. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +0 -1
  173. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +0 -3
  174. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  175. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +0 -1
  176. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +0 -1
  177. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  178. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +0 -2
  179. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  180. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  181. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +0 -7
  182. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  183. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +0 -5
  184. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +0 -1
  185. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  186. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  187. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +0 -1
  188. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +0 -41
  189. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +0 -1
  190. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +0 -1
  191. package/apps/ui/dist/client/assets/index-C-STgIH4.js +0 -166
  192. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  193. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +0 -1
  194. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +0 -1
  195. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +0 -1
  196. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +0 -1
  197. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +0 -1
  198. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +0 -1
  199. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  200. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +0 -1
  201. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +0 -8
  202. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +0 -1
  203. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +0 -1
  204. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +0 -1
  205. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +0 -1
  206. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  207. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +0 -1
  208. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +0 -1
  209. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  210. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +0 -3
  211. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  212. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  213. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  214. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  215. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +0 -1
  216. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  217. package/apps/ui/dist/client/assets/select-CYF6pF37.js +0 -1
  218. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +0 -1
  219. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  220. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +0 -1
  221. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +0 -7
  222. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  223. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +0 -1
  224. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  225. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +0 -1
  226. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +0 -1
  227. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +0 -1
  228. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +0 -387
  229. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  230. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  231. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  232. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  233. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +0 -613
  234. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  235. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-eS87Dx7g.js +0 -20
  236. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  237. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  238. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  239. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  240. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  241. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  242. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  243. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  244. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  245. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  246. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  247. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  248. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  249. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +0 -361
  250. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +0 -37
  251. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +0 -72
  252. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +0 -18
  253. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +0 -473
  254. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +0 -11
  255. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +0 -139
  256. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +0 -10
  257. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +0 -28
  258. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +0 -11
  259. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +0 -191
  260. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +0 -92
  261. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +0 -11
  262. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  263. package/apps/ui/dist/server/assets/codex-server-CGXU4H8-.js +0 -2294
  264. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  265. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  266. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  267. package/apps/ui/dist/server/assets/cursor-db-CZnYy7aT.js +0 -830
  268. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  269. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  270. package/apps/ui/dist/server/assets/cursor-recovery-CXpO8KcD.js +0 -361
  271. package/apps/ui/dist/server/assets/cursor-server-DNu52A3K.js +0 -213
  272. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  273. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +0 -582
  274. package/apps/ui/dist/server/assets/cursor-threads._composerId-p9-ARAzc.js +0 -18
  275. package/apps/ui/dist/server/assets/cursor-transcript-B7tGnqtn.js +0 -125
  276. package/apps/ui/dist/server/assets/cursor._workspaceKey-CZuohIUL.js +0 -28
  277. package/apps/ui/dist/server/assets/cursor._workspaceKey-DPp5JSjy.js +0 -401
  278. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  279. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  280. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  281. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  282. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  283. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  284. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  285. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  286. package/apps/ui/dist/server/assets/export-dialog-D88ze9Gy.js +0 -240
  287. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  288. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +0 -552
  289. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +0 -37
  290. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +0 -72
  291. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +0 -11
  292. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +0 -449
  293. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +0 -18
  294. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +0 -112
  295. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +0 -27
  296. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +0 -11
  297. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +0 -28
  298. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +0 -193
  299. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +0 -11
  300. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +0 -93
  301. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  302. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  303. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  304. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +0 -397
  305. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +0 -50
  306. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +0 -72
  307. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +0 -518
  308. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +0 -18
  309. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +0 -11
  310. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +0 -87
  311. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +0 -141
  312. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +0 -32
  313. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +0 -11
  314. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +0 -28
  315. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +0 -207
  316. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +0 -11
  317. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +0 -93
  318. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  319. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  320. package/apps/ui/dist/server/assets/projects._project-4io5LO0E.js +0 -20
  321. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  322. package/apps/ui/dist/server/assets/projects._project-D6ZoGhJ8.js +0 -395
  323. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  324. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  325. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  326. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  327. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  328. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +0 -518
  329. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  330. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  331. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  332. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  333. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  334. package/apps/ui/dist/server/assets/shared-DyhChtHf.js +0 -137
  335. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +0 -38
  336. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  337. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +0 -4
  338. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +0 -501
  339. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  340. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +0 -45
  341. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  342. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +0 -648
  343. package/apps/ui/dist/server/assets/threads._threadId-Ci0S4mKU.js +0 -18
  344. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  345. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  346. package/apps/ui/dist/server/server.js +0 -5895
  347. package/bin/codex-chats-claude.js +0 -5
  348. package/bin/codex-chats.js +0 -5
  349. package/bin/spiracha.js +0 -5
  350. package/src/export-chats.ts +0 -120
  351. package/src/export-claude.ts +0 -36
  352. package/src/export-cursor.ts +0 -244
  353. package/src/lib/claude-exporter.ts +0 -864
  354. package/src/lib/codex-exporter-cli.ts +0 -277
  355. package/src/lib/codex-exporter-db.ts +0 -319
  356. package/src/lib/codex-exporter.ts +0 -115
  357. package/src/lib/cursor-exporter.ts +0 -266
  358. package/src/lib/interactive-cli.ts +0 -433
  359. package/src/lib/native-open.ts +0 -54
  360. package/src/mcp-server.ts +0 -137
  361. package/src/spiracha.ts +0 -116
  362. package/src/ui-cli.ts +0 -310
  363. /package/apps/ui/{dist/client → public}/icon.svg +0 -0
  364. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  365. /package/apps/ui/{dist/client → public}/robots.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import { constants, Database } from 'bun:sqlite';
2
- import { closeSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
3
- import { rm } from 'node:fs/promises';
2
+ import { closeSync, openSync, readdirSync, readSync, type Stats, statSync } from 'node:fs';
3
+ import { rename, rm } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { StringDecoder } from 'node:string_decoder';
@@ -14,9 +14,9 @@ import type {
14
14
  ThreadBrowseData,
15
15
  ThreadListEntry,
16
16
  } from './codex-browser-types';
17
- import type { ThreadRelations, ThreadRow } from './codex-exporter-types';
18
- import { DEFAULT_CODEX_DIR, DEFAULT_DB_PATH } from './codex-exporter-types';
19
17
  import { getCachedParsedCodexTranscript, getThreadRolloutLoadState } from './codex-thread-cache';
18
+ import type { ThreadRelations, ThreadRow } from './codex-thread-types';
19
+ import { DEFAULT_CODEX_DIR, DEFAULT_DB_PATH } from './codex-thread-types';
20
20
  import { mapWithConcurrency } from './concurrency';
21
21
  import { cleanInlineTitle, getPortablePathBasename } from './shared';
22
22
  import { runWithSqliteRetry } from './sqlite-retry';
@@ -36,7 +36,12 @@ const THREAD_LIST_IO_CONCURRENCY = 8;
36
36
  const CODEX_READONLY_DB_OPEN_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
37
37
  const JSONL_READ_CHUNK_BYTES = 64 * 1024;
38
38
  const SESSION_META_READ_LIMIT_BYTES = 4 * 1024 * 1024;
39
+ const FALLBACK_STATS_HEAD_READ_LIMIT_BYTES = 512 * 1024;
40
+ const FALLBACK_STATS_TAIL_READ_LIMIT_BYTES = 512 * 1024;
41
+ const FALLBACK_STATS_RECORD_PATTERN = /"type"\s*:\s*"(?:agent_message|message|token_count|turn_context)"/u;
39
42
  const THREAD_ID_PATTERN = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/iu;
43
+ const sessionFileIndexCache = new Map<string, Map<string, string>>();
44
+ let sessionIndexMutationQueue = Promise.resolve();
40
45
 
41
46
  type SessionIndexEntry = {
42
47
  id: string;
@@ -68,10 +73,16 @@ type FallbackRolloutStats = {
68
73
  tokensUsed: number;
69
74
  };
70
75
 
76
+ type FallbackThreadRowOptions = ReadFallbackThreadRowsOptions & {
77
+ projectName?: string | null;
78
+ };
79
+
71
80
  const isSqliteCantOpenError = (error: unknown) => {
72
81
  return (error as { code?: unknown }).code === 'SQLITE_CANTOPEN';
73
82
  };
74
83
 
84
+ const uniqueValues = <T>(values: T[]) => [...new Set(values)];
85
+
75
86
  const chunkValues = <T>(values: T[], chunkSize: number) => {
76
87
  const chunks: T[][] = [];
77
88
 
@@ -329,6 +340,31 @@ const collectSessionFilesByThreadId = (sessionsDir: string): Map<string, string>
329
340
  return sessionFiles;
330
341
  };
331
342
 
343
+ const findSessionFileByThreadId = (sessionsDir: string, threadId: string): string | null => {
344
+ const lookup = (sessionFilesByThreadId: Map<string, string>) => {
345
+ const sessionFile = sessionFilesByThreadId.get(threadId);
346
+ if (!sessionFile) {
347
+ return null;
348
+ }
349
+
350
+ try {
351
+ return statSync(sessionFile).isFile() ? sessionFile : null;
352
+ } catch {
353
+ return null;
354
+ }
355
+ };
356
+
357
+ const cached = sessionFileIndexCache.get(sessionsDir);
358
+ const cachedMatch = cached ? lookup(cached) : null;
359
+ if (cachedMatch) {
360
+ return cachedMatch;
361
+ }
362
+
363
+ const refreshed = collectSessionFilesByThreadId(sessionsDir);
364
+ sessionFileIndexCache.set(sessionsDir, refreshed);
365
+ return lookup(refreshed);
366
+ };
367
+
332
368
  const readSessionMetaLine = (sessionFile: string): string | null => {
333
369
  let descriptor: number | null = null;
334
370
  try {
@@ -402,40 +438,183 @@ const isFallbackSubagent = (sessionMeta: FallbackSessionMeta) => {
402
438
  );
403
439
  };
404
440
 
405
- const readFallbackRolloutStats = (sessionFile: string): FallbackRolloutStats => {
406
- let model: string | null = null;
407
- let tokensUsed = 0;
441
+ const updateFallbackRolloutStatsFromRecord = (record: Record<string, unknown>, stats: FallbackRolloutStats): void => {
442
+ const payload = objectOrNull(record.payload);
443
+ if (!payload) {
444
+ return;
445
+ }
408
446
 
409
- readJsonlObjects<Record<string, unknown>>(sessionFile, (record) => {
410
- const payload = objectOrNull(record.payload);
411
- if (!payload) {
412
- return;
447
+ if (record.type === 'turn_context') {
448
+ stats.model = stringOrNull(payload.model) ?? stats.model;
449
+ return;
450
+ }
451
+
452
+ const payloadType = stringOrNull(payload.type);
453
+ if (payloadType === 'message' || payloadType === 'agent_message') {
454
+ stats.model = stringOrNull(payload.model) ?? stats.model;
455
+ return;
456
+ }
457
+
458
+ if (payloadType !== 'token_count') {
459
+ return;
460
+ }
461
+
462
+ const info = objectOrNull(payload.info);
463
+ const totalTokenUsage = objectOrNull(info?.total_token_usage);
464
+ stats.tokensUsed = numberOrNull(totalTokenUsage?.total_tokens) ?? stats.tokensUsed;
465
+ };
466
+
467
+ const readFallbackStatsLine = (line: string, stats: FallbackRolloutStats) => {
468
+ const trimmed = line.trim();
469
+ if (!trimmed || !FALLBACK_STATS_RECORD_PATTERN.test(trimmed)) {
470
+ return;
471
+ }
472
+
473
+ const record = parseJsonlObject<Record<string, unknown>>(trimmed);
474
+ if (record) {
475
+ updateFallbackRolloutStatsFromRecord(record, stats);
476
+ }
477
+ };
478
+
479
+ const emitCompleteFallbackStatsLines = (text: string, stats: FallbackRolloutStats): string => {
480
+ const lines = text.split(/\r?\n/u);
481
+ const pending = lines.pop() ?? '';
482
+ for (const line of lines) {
483
+ readFallbackStatsLine(line, stats);
484
+ }
485
+ return pending;
486
+ };
487
+
488
+ const readFallbackRolloutStatsHead = (sessionFile: string, stats: FallbackRolloutStats, fileStats: Stats) => {
489
+ let descriptor: number | null = null;
490
+ try {
491
+ if (!fileStats.isFile()) {
492
+ return 0;
413
493
  }
414
494
 
415
- if (record.type === 'turn_context') {
416
- model = stringOrNull(payload.model) ?? model;
417
- return;
495
+ descriptor = openSync(sessionFile, 'r');
496
+ const buffer = Buffer.alloc(JSONL_READ_CHUNK_BYTES);
497
+ const decoder = new StringDecoder('utf8');
498
+ const readLimitBytes = Math.min(fileStats.size, FALLBACK_STATS_HEAD_READ_LIMIT_BYTES);
499
+ let position = 0;
500
+ let pending = '';
501
+
502
+ while (position < readLimitBytes) {
503
+ const bytesRead = readSync(
504
+ descriptor,
505
+ buffer,
506
+ 0,
507
+ Math.min(buffer.length, readLimitBytes - position),
508
+ position,
509
+ );
510
+ if (bytesRead === 0) {
511
+ break;
512
+ }
513
+
514
+ position += bytesRead;
515
+ pending += decoder.write(buffer.subarray(0, bytesRead));
516
+ pending = emitCompleteFallbackStatsLines(pending, stats);
517
+ }
518
+
519
+ if (position >= fileStats.size) {
520
+ readFallbackStatsLine(pending + decoder.end(), stats);
521
+ return position;
418
522
  }
419
523
 
420
- const payloadType = stringOrNull(payload.type);
421
- if (payloadType === 'message' || payloadType === 'agent_message') {
422
- model = stringOrNull(payload.model) ?? model;
524
+ decoder.end();
525
+ return position;
526
+ } catch {
527
+ return 0;
528
+ } finally {
529
+ if (descriptor !== null) {
530
+ closeSync(descriptor);
531
+ }
532
+ }
533
+ };
534
+
535
+ const trimPartialLeadingJsonlLine = (text: string) => {
536
+ if (text.startsWith('\r\n')) {
537
+ return text.slice(2);
538
+ }
539
+
540
+ if (text.startsWith('\n')) {
541
+ return text.slice(1);
542
+ }
543
+
544
+ const match = /\r?\n/u.exec(text);
545
+ return match ? text.slice(match.index + match[0].length) : '';
546
+ };
547
+
548
+ const readFallbackRolloutStatsTail = (
549
+ sessionFile: string,
550
+ stats: FallbackRolloutStats,
551
+ fileStats: Stats,
552
+ coveredPrefixBytes: number,
553
+ ) => {
554
+ let descriptor: number | null = null;
555
+ try {
556
+ if (!fileStats.isFile() || fileStats.size === 0) {
423
557
  return;
424
558
  }
425
559
 
426
- if (payloadType !== 'token_count') {
560
+ const suffixStart = Math.max(coveredPrefixBytes, fileStats.size - FALLBACK_STATS_TAIL_READ_LIMIT_BYTES);
561
+ if (suffixStart >= fileStats.size) {
427
562
  return;
428
563
  }
429
564
 
430
- const info = objectOrNull(payload.info);
431
- const totalTokenUsage = objectOrNull(info?.total_token_usage);
432
- tokensUsed = numberOrNull(totalTokenUsage?.total_tokens) ?? tokensUsed;
433
- });
565
+ const readStart = suffixStart > 0 ? suffixStart - 1 : 0;
566
+ const readLimitBytes = fileStats.size - readStart;
567
+ descriptor = openSync(sessionFile, 'r');
568
+ const buffer = Buffer.alloc(JSONL_READ_CHUNK_BYTES);
569
+ const decoder = new StringDecoder('utf8');
570
+ let position = readStart;
571
+ let remainingBytes = readLimitBytes;
572
+ let text = '';
434
573
 
435
- return {
436
- model,
437
- tokensUsed,
574
+ while (remainingBytes > 0) {
575
+ const bytesRead = readSync(descriptor, buffer, 0, Math.min(buffer.length, remainingBytes), position);
576
+ if (bytesRead === 0) {
577
+ break;
578
+ }
579
+
580
+ position += bytesRead;
581
+ remainingBytes -= bytesRead;
582
+ text += decoder.write(buffer.subarray(0, bytesRead));
583
+ }
584
+
585
+ text += decoder.end();
586
+ const completeText = readStart > 0 ? trimPartialLeadingJsonlLine(text) : text;
587
+ for (const line of completeText.split(/\r?\n/u)) {
588
+ readFallbackStatsLine(line, stats);
589
+ }
590
+ } catch {
591
+ return;
592
+ } finally {
593
+ if (descriptor !== null) {
594
+ closeSync(descriptor);
595
+ }
596
+ }
597
+ };
598
+
599
+ const readFallbackRolloutStats = (sessionFile: string): FallbackRolloutStats => {
600
+ const stats: FallbackRolloutStats = {
601
+ model: null,
602
+ tokensUsed: 0,
438
603
  };
604
+
605
+ try {
606
+ const fileStats = statSync(sessionFile);
607
+ if (!fileStats.isFile()) {
608
+ return stats;
609
+ }
610
+
611
+ const coveredPrefixBytes = readFallbackRolloutStatsHead(sessionFile, stats, fileStats);
612
+ readFallbackRolloutStatsTail(sessionFile, stats, fileStats, coveredPrefixBytes);
613
+ } catch {
614
+ return stats;
615
+ }
616
+
617
+ return stats;
439
618
  };
440
619
 
441
620
  const buildFallbackThreadRow = (
@@ -492,6 +671,28 @@ const buildFallbackThreadRow = (
492
671
  };
493
672
  };
494
673
 
674
+ const readFallbackThreadRow = (
675
+ entry: SessionIndexEntry,
676
+ sessionFile: string,
677
+ options: FallbackThreadRowOptions = {},
678
+ ): ThreadRow | null => {
679
+ const sessionMeta = readFallbackSessionMeta(sessionFile);
680
+ if (!sessionMeta) {
681
+ return null;
682
+ }
683
+
684
+ if (!options.includeSubagents && isFallbackSubagent(sessionMeta)) {
685
+ return null;
686
+ }
687
+
688
+ const cwd = stringOrNull(sessionMeta.cwd);
689
+ if (!cwd || (options.projectName && getPortablePathBasename(cwd) !== options.projectName)) {
690
+ return null;
691
+ }
692
+
693
+ return buildFallbackThreadRow(entry, sessionFile, sessionMeta, readFallbackRolloutStats(sessionFile));
694
+ };
695
+
495
696
  const readFallbackThreadRows = (
496
697
  dbPath: string,
497
698
  existingThreadIds: Set<string>,
@@ -512,22 +713,11 @@ const readFallbackThreadRows = (
512
713
  continue;
513
714
  }
514
715
 
515
- const sessionMeta = readFallbackSessionMeta(sessionFile);
516
- if (!sessionMeta) {
517
- continue;
518
- }
519
-
520
- if (!options.includeSubagents && isFallbackSubagent(sessionMeta)) {
521
- continue;
522
- }
523
-
524
- const fallbackThread = buildFallbackThreadRow(
525
- entry,
526
- sessionFile,
527
- sessionMeta,
528
- readFallbackRolloutStats(sessionFile),
529
- );
530
- if (!fallbackThread || (projectName && getPortablePathBasename(fallbackThread.cwd) !== projectName)) {
716
+ const fallbackThread = readFallbackThreadRow(entry, sessionFile, {
717
+ ...options,
718
+ projectName,
719
+ });
720
+ if (!fallbackThread) {
531
721
  continue;
532
722
  }
533
723
 
@@ -537,6 +727,25 @@ const readFallbackThreadRows = (
537
727
  return fallbackThreads;
538
728
  };
539
729
 
730
+ const readFallbackThreadRowById = (
731
+ dbPath: string,
732
+ threadId: string,
733
+ options: ReadFallbackThreadRowsOptions = {},
734
+ ): ThreadRow | null => {
735
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
736
+ const entry = readSessionIndexEntries(codexDir).find((candidate) => candidate.id === threadId);
737
+ if (!entry) {
738
+ return null;
739
+ }
740
+
741
+ const sessionFile = findSessionFileByThreadId(path.join(codexDir, 'sessions'), threadId);
742
+ if (!sessionFile) {
743
+ return null;
744
+ }
745
+
746
+ return readFallbackThreadRow(entry, sessionFile, options);
747
+ };
748
+
540
749
  const mergeFallbackThreadRows = (dbPath: string, threads: ThreadRow[], projectName: string | null = null) => {
541
750
  const threadIds = new Set(threads.map((thread) => thread.id));
542
751
  return [...threads, ...readFallbackThreadRows(dbPath, threadIds, projectName)].sort((left, right) => {
@@ -803,6 +1012,132 @@ const deleteThreadSessionFiles = async (sessionFiles: string[]) => {
803
1012
  return uniqueSessionFiles;
804
1013
  };
805
1014
 
1015
+ const getSessionFilesForThreadIds = (dbPath: string, threadIds: string[]) => {
1016
+ if (threadIds.length === 0) {
1017
+ return [];
1018
+ }
1019
+
1020
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
1021
+ if (threadIds.length === 1) {
1022
+ const sessionFile = findSessionFileByThreadId(path.join(codexDir, 'sessions'), threadIds[0]!);
1023
+ return sessionFile ? [sessionFile] : [];
1024
+ }
1025
+
1026
+ const sessionFilesByThreadId = collectSessionFilesByThreadId(path.join(codexDir, 'sessions'));
1027
+ return threadIds
1028
+ .map((threadId) => sessionFilesByThreadId.get(threadId))
1029
+ .filter((value): value is string => Boolean(value));
1030
+ };
1031
+
1032
+ const filterSessionIndexLines = (lines: string[], threadIds: Set<string>) => {
1033
+ const removedThreadIds: string[] = [];
1034
+ const retainedLines: string[] = [];
1035
+
1036
+ for (const line of lines) {
1037
+ const trimmed = line.trim();
1038
+ if (!trimmed) {
1039
+ continue;
1040
+ }
1041
+
1042
+ const entry = parseJsonlObject<SessionIndexEntry>(trimmed);
1043
+ if (entry?.id && threadIds.has(entry.id)) {
1044
+ removedThreadIds.push(entry.id);
1045
+ continue;
1046
+ }
1047
+
1048
+ retainedLines.push(trimmed);
1049
+ }
1050
+
1051
+ return { removedThreadIds, retainedLines };
1052
+ };
1053
+
1054
+ const writeSessionIndexLines = async (sessionIndexPath: string, codexDir: string, retainedLines: string[]) => {
1055
+ const tempSessionIndexPath = path.join(
1056
+ codexDir,
1057
+ `.session_index.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`,
1058
+ );
1059
+ try {
1060
+ await Bun.write(tempSessionIndexPath, retainedLines.length > 0 ? `${retainedLines.join('\n')}\n` : '');
1061
+ await rename(tempSessionIndexPath, sessionIndexPath);
1062
+ } catch (error) {
1063
+ await rm(tempSessionIndexPath, { force: true });
1064
+ throw error;
1065
+ }
1066
+ };
1067
+
1068
+ const removeSessionIndexEntries = async (codexDir: string, threadIds: string[]) => {
1069
+ const runMutation = async () => {
1070
+ const uniqueThreadIds = new Set(threadIds);
1071
+ if (uniqueThreadIds.size === 0) {
1072
+ return [];
1073
+ }
1074
+
1075
+ const sessionIndexPath = path.join(codexDir, 'session_index.jsonl');
1076
+ if (!(await Bun.file(sessionIndexPath).exists())) {
1077
+ return [];
1078
+ }
1079
+
1080
+ const lines = (await Bun.file(sessionIndexPath).text()).split(/\r?\n/u);
1081
+ const { removedThreadIds, retainedLines } = filterSessionIndexLines(lines, uniqueThreadIds);
1082
+
1083
+ if (removedThreadIds.length === 0) {
1084
+ return [];
1085
+ }
1086
+
1087
+ await writeSessionIndexLines(sessionIndexPath, codexDir, retainedLines);
1088
+ return uniqueValues(removedThreadIds);
1089
+ };
1090
+
1091
+ const mutation = sessionIndexMutationQueue.then(runMutation, runMutation);
1092
+ sessionIndexMutationQueue = mutation.then(
1093
+ () => undefined,
1094
+ () => undefined,
1095
+ );
1096
+ return mutation;
1097
+ };
1098
+
1099
+ const listFallbackThreadIdsForProject = (dbPath: string, existingThreadIds: Set<string>, projectName: string) => {
1100
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
1101
+ const sessionFilesByThreadId = collectSessionFilesByThreadId(path.join(codexDir, 'sessions'));
1102
+ const fallbackThreadIds: string[] = [];
1103
+
1104
+ for (const entry of readSessionIndexEntries(codexDir)) {
1105
+ if (existingThreadIds.has(entry.id) || !sessionFilesByThreadId.has(entry.id)) {
1106
+ continue;
1107
+ }
1108
+
1109
+ const sessionMeta = readFallbackSessionMeta(sessionFilesByThreadId.get(entry.id)!);
1110
+ if (!sessionMeta || isFallbackSubagent(sessionMeta)) {
1111
+ continue;
1112
+ }
1113
+
1114
+ const cwd = stringOrNull(sessionMeta.cwd);
1115
+ if (cwd && getPortablePathBasename(cwd) === projectName) {
1116
+ fallbackThreadIds.push(entry.id);
1117
+ }
1118
+ }
1119
+
1120
+ return fallbackThreadIds;
1121
+ };
1122
+
1123
+ const deleteSessionIndexEntriesForThreads = async (
1124
+ dbPath: string,
1125
+ threadIds: string[],
1126
+ dbDeletedSessionFiles: string[],
1127
+ deleteSessionFiles: boolean,
1128
+ ) => {
1129
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
1130
+ const removedThreadIds = await removeSessionIndexEntries(codexDir, threadIds);
1131
+ const fallbackSessionFiles = deleteSessionFiles ? getSessionFilesForThreadIds(dbPath, removedThreadIds) : [];
1132
+
1133
+ return {
1134
+ deletedSessionFiles: deleteSessionFiles
1135
+ ? await deleteThreadSessionFiles([...dbDeletedSessionFiles, ...fallbackSessionFiles])
1136
+ : [],
1137
+ deletedThreadIds: removedThreadIds,
1138
+ };
1139
+ };
1140
+
806
1141
  export const listCodexProjects = (dbPath: string): ProjectSummary[] => {
807
1142
  return mapProjectSummaries(
808
1143
  buildProjectSummaryMap(applyRolloutActivityTimestamps(mergeFallbackThreadRows(dbPath, readAllThreads(dbPath)))),
@@ -810,6 +1145,7 @@ export const listCodexProjects = (dbPath: string): ProjectSummary[] => {
810
1145
  };
811
1146
 
812
1147
  type ListProjectThreadsOptions = {
1148
+ includeTranscriptStats?: boolean;
813
1149
  largeTranscriptThresholdBytes?: number;
814
1150
  };
815
1151
 
@@ -849,7 +1185,7 @@ export const listProjectThreads = async (
849
1185
  };
850
1186
  }
851
1187
 
852
- if (rollout.shouldDeferTranscriptLoad) {
1188
+ if (rollout.shouldDeferTranscriptLoad || options.includeTranscriptStats === false) {
853
1189
  return {
854
1190
  project: projectName,
855
1191
  rolloutSizeBytes: rollout.fileSizeBytes,
@@ -885,12 +1221,7 @@ export const getThreadBrowseData = (dbPath: string, threadId: string): ThreadBro
885
1221
  return withReadonlyDb(dbPath, (db) => {
886
1222
  const existingTableNames = getExistingTableNames(db);
887
1223
  const dbThread = db.query('SELECT * FROM threads WHERE id = ? LIMIT 1').get(threadId) as ThreadRow | null;
888
- const thread =
889
- dbThread ??
890
- readFallbackThreadRows(dbPath, new Set(), null, { includeSubagents: true }).find(
891
- (fallbackThread) => fallbackThread.id === threadId,
892
- ) ??
893
- null;
1224
+ const thread = dbThread ?? readFallbackThreadRowById(dbPath, threadId, { includeSubagents: true }) ?? null;
894
1225
  if (!thread) {
895
1226
  throw new Error(`Thread not found: ${threadId}`);
896
1227
  }
@@ -955,21 +1286,22 @@ export const deleteCodexThread = async (
955
1286
  threadId: string,
956
1287
  options: DeleteThreadOptions = {},
957
1288
  ): Promise<DeleteThreadsResult> => {
1289
+ const threadIds = [threadId];
958
1290
  const result = withWritableDb(dbPath, (db) => {
959
- return deleteThreadIds(db, [threadId]);
1291
+ return deleteThreadIds(db, threadIds);
960
1292
  });
961
1293
 
962
1294
  try {
963
- if (options.deleteSessionFiles) {
964
- return {
965
- ...result,
966
- deletedSessionFiles: await deleteThreadSessionFiles(result.deletedSessionFiles),
967
- };
968
- }
1295
+ const sessionIndexResult = await deleteSessionIndexEntriesForThreads(
1296
+ dbPath,
1297
+ threadIds,
1298
+ result.deletedSessionFiles,
1299
+ Boolean(options.deleteSessionFiles),
1300
+ );
969
1301
 
970
1302
  return {
971
- ...result,
972
- deletedSessionFiles: [],
1303
+ deletedSessionFiles: sessionIndexResult.deletedSessionFiles,
1304
+ deletedThreadIds: uniqueValues([...result.deletedThreadIds, ...sessionIndexResult.deletedThreadIds]),
973
1305
  };
974
1306
  } finally {
975
1307
  await invalidateCodexUiCaches();
@@ -981,21 +1313,22 @@ export const deleteCodexThreads = async (
981
1313
  threadIds: string[],
982
1314
  options: DeleteThreadOptions = {},
983
1315
  ): Promise<DeleteThreadsResult> => {
1316
+ const uniqueThreadIds = uniqueValues(threadIds);
984
1317
  const result = withWritableDb(dbPath, (db) => {
985
- return deleteThreadIds(db, threadIds);
1318
+ return deleteThreadIds(db, uniqueThreadIds);
986
1319
  });
987
1320
 
988
1321
  try {
989
- if (options.deleteSessionFiles) {
990
- return {
991
- ...result,
992
- deletedSessionFiles: await deleteThreadSessionFiles(result.deletedSessionFiles),
993
- };
994
- }
1322
+ const sessionIndexResult = await deleteSessionIndexEntriesForThreads(
1323
+ dbPath,
1324
+ uniqueThreadIds,
1325
+ result.deletedSessionFiles,
1326
+ Boolean(options.deleteSessionFiles),
1327
+ );
995
1328
 
996
1329
  return {
997
- ...result,
998
- deletedSessionFiles: [],
1330
+ deletedSessionFiles: sessionIndexResult.deletedSessionFiles,
1331
+ deletedThreadIds: uniqueValues([...result.deletedThreadIds, ...sessionIndexResult.deletedThreadIds]),
999
1332
  };
1000
1333
  } finally {
1001
1334
  await invalidateCodexUiCaches();
@@ -1007,6 +1340,8 @@ export const deleteCodexProject = async (
1007
1340
  projectName: string,
1008
1341
  options: DeleteProjectOptions = {},
1009
1342
  ): Promise<DeleteProjectResult> => {
1343
+ const existingThreadIds = new Set(readAllThreads(dbPath).map((thread) => thread.id));
1344
+ const fallbackThreadIds = listFallbackThreadIdsForProject(dbPath, existingThreadIds, projectName);
1010
1345
  const result = withWritableDb(dbPath, (db) => {
1011
1346
  const threads = db.query('SELECT id, cwd FROM threads').all() as Array<{ cwd: string; id: string }>;
1012
1347
  const threadIds = threads
@@ -1021,16 +1356,17 @@ export const deleteCodexProject = async (
1021
1356
  });
1022
1357
 
1023
1358
  try {
1024
- if (options.deleteSessionFiles) {
1025
- return {
1026
- ...result,
1027
- deletedSessionFiles: await deleteThreadSessionFiles(result.deletedSessionFiles),
1028
- };
1029
- }
1359
+ const sessionIndexResult = await deleteSessionIndexEntriesForThreads(
1360
+ dbPath,
1361
+ [...result.deletedThreadIds, ...fallbackThreadIds],
1362
+ result.deletedSessionFiles,
1363
+ Boolean(options.deleteSessionFiles),
1364
+ );
1030
1365
 
1031
1366
  return {
1032
1367
  ...result,
1033
- deletedSessionFiles: [],
1368
+ deletedSessionFiles: sessionIndexResult.deletedSessionFiles,
1369
+ deletedThreadIds: uniqueValues([...result.deletedThreadIds, ...sessionIndexResult.deletedThreadIds]),
1034
1370
  };
1035
1371
  } finally {
1036
1372
  await invalidateCodexUiCaches();
@@ -1042,5 +1378,5 @@ export const listScopedThreads = (dbPath: string, projectName: string | null): T
1042
1378
  };
1043
1379
 
1044
1380
  export const invalidateCodexUiCaches = async () => {
1045
- await invalidateCacheByPrefix('analytics-', 'thread-');
1381
+ await invalidateCacheByPrefix('analytics-', 'thread-', 'thread-preview-');
1046
1382
  };