spiracha 1.5.0 → 2.1.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 (401) hide show
  1. package/AGENTS.md +114 -205
  2. package/README.md +94 -269
  3. package/apps/ui/AGENTS.md +17 -7
  4. package/apps/ui/README.md +52 -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 +245 -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 +125 -0
  11. package/apps/ui/src/components/breadcrumbs.tsx +71 -0
  12. package/apps/ui/src/components/claude-code-sessions-table.tsx +150 -0
  13. package/apps/ui/src/components/claude-code-workspaces-table.tsx +57 -0
  14. package/apps/ui/src/components/cursor-threads-table.tsx +152 -0
  15. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  16. package/apps/ui/src/components/data-table.tsx +243 -0
  17. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  18. package/apps/ui/src/components/export-dialog.tsx +174 -0
  19. package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
  20. package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
  21. package/apps/ui/src/components/json-panel.tsx +17 -0
  22. package/apps/ui/src/components/kiro-sessions-table.tsx +148 -0
  23. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  24. package/apps/ui/src/components/list-search-input.tsx +18 -0
  25. package/apps/ui/src/components/loading-panel.tsx +23 -0
  26. package/apps/ui/src/components/metadata-section.tsx +31 -0
  27. package/apps/ui/src/components/metric-card.tsx +19 -0
  28. package/apps/ui/src/components/opencode-sessions-table.tsx +164 -0
  29. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  30. package/apps/ui/src/components/page-header.tsx +39 -0
  31. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  32. package/apps/ui/src/components/projects-table.tsx +108 -0
  33. package/apps/ui/src/components/qoder-sessions-table.tsx +133 -0
  34. package/apps/ui/src/components/qoder-workspaces-table.tsx +57 -0
  35. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  36. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  37. package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
  38. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  39. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  40. package/apps/ui/src/components/threads-table.tsx +165 -0
  41. package/apps/ui/src/components/transcript-view.tsx +648 -0
  42. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  43. package/apps/ui/src/components/ui/badge.tsx +45 -0
  44. package/apps/ui/src/components/ui/button.tsx +62 -0
  45. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  46. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  47. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  48. package/apps/ui/src/components/ui/input.tsx +21 -0
  49. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  50. package/apps/ui/src/components/ui/select.tsx +163 -0
  51. package/apps/ui/src/components/ui/separator.tsx +28 -0
  52. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  53. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  54. package/apps/ui/src/components/ui/table.tsx +76 -0
  55. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  56. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  57. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  58. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  59. package/apps/ui/src/lib/antigravity-conversation-state.ts +41 -0
  60. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  61. package/apps/ui/src/lib/antigravity-server.ts +273 -0
  62. package/apps/ui/src/lib/antigravity-transcript-events.ts +343 -0
  63. package/apps/ui/src/lib/claude-code-queries.ts +34 -0
  64. package/apps/ui/src/lib/claude-code-server.ts +243 -0
  65. package/apps/ui/src/lib/claude-code-transcript-events.ts +161 -0
  66. package/apps/ui/src/lib/codex-queries.ts +82 -0
  67. package/apps/ui/src/lib/codex-server.ts +291 -0
  68. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  69. package/apps/ui/src/lib/cursor-server.ts +315 -0
  70. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  71. package/apps/ui/src/lib/download.ts +166 -0
  72. package/apps/ui/src/lib/formatters.ts +147 -0
  73. package/apps/ui/src/lib/grok-queries.ts +22 -0
  74. package/apps/ui/src/lib/grok-server.ts +169 -0
  75. package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
  76. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  77. package/apps/ui/src/lib/kiro-server.ts +151 -0
  78. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  79. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  80. package/apps/ui/src/lib/opencode-server.ts +151 -0
  81. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  82. package/apps/ui/src/lib/package-metadata.ts +16 -0
  83. package/apps/ui/src/lib/path-utils.ts +13 -0
  84. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  85. package/apps/ui/src/lib/qoder-server.ts +135 -0
  86. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  87. package/apps/ui/src/lib/route-search.ts +182 -0
  88. package/apps/ui/src/lib/settings-store.tsx +59 -0
  89. package/apps/ui/src/lib/source-session-export-server.ts +139 -0
  90. package/apps/ui/src/lib/text-filter.ts +22 -0
  91. package/apps/ui/src/lib/thread-id.ts +3 -0
  92. package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
  93. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  94. package/apps/ui/src/lib/utils.ts +7 -0
  95. package/apps/ui/src/lib/workspace-delete-navigation.ts +12 -0
  96. package/apps/ui/src/routeTree.gen.ts +837 -0
  97. package/apps/ui/src/router.tsx +20 -0
  98. package/apps/ui/src/routes/$threadId.tsx +22 -0
  99. package/apps/ui/src/routes/__root.tsx +127 -0
  100. package/apps/ui/src/routes/analytics.tsx +143 -0
  101. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +504 -0
  102. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +365 -0
  103. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  104. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  105. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  106. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +16 -0
  107. package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
  108. package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
  109. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  110. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  111. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  112. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +468 -0
  113. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +274 -0
  114. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  115. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  116. package/apps/ui/src/routes/codex.index.tsx +116 -0
  117. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  118. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +426 -0
  119. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  120. package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +388 -0
  121. package/apps/ui/src/routes/grok.$workspaceKey.tsx +301 -0
  122. package/apps/ui/src/routes/grok.index.tsx +48 -0
  123. package/apps/ui/src/routes/index.tsx +124 -0
  124. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +396 -0
  125. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +303 -0
  126. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  127. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +420 -0
  128. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +327 -0
  129. package/apps/ui/src/routes/opencode.index.tsx +61 -0
  130. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +344 -0
  131. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +186 -0
  132. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  133. package/apps/ui/src/routes/settings.tsx +91 -0
  134. package/apps/ui/src/routes/threads.$threadId.tsx +1103 -0
  135. package/apps/ui/src/styles.css +122 -0
  136. package/apps/ui/tsconfig.json +29 -0
  137. package/apps/ui/vite.config.ts +84 -0
  138. package/bin/spiracha.ts +32 -0
  139. package/package.json +54 -80
  140. package/src/client.ts +423 -0
  141. package/src/lib/antigravity-db.ts +414 -44
  142. package/src/lib/antigravity-exporter-types.ts +4 -0
  143. package/src/lib/antigravity-projects.ts +201 -0
  144. package/src/lib/claude-code-db.ts +498 -32
  145. package/src/lib/claude-code-exporter-types.ts +5 -0
  146. package/src/lib/claude-code-transcript-phase.ts +60 -1
  147. package/src/lib/claude-code-transcript.ts +8 -5
  148. package/src/lib/codex-analytics.ts +1 -1
  149. package/src/lib/codex-browser-db.ts +410 -74
  150. package/src/lib/codex-browser-export.ts +29 -41
  151. package/src/lib/codex-browser-types.ts +1 -1
  152. package/src/lib/codex-thread-cache.ts +41 -13
  153. package/src/lib/codex-thread-parser.ts +86 -35
  154. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  155. package/src/lib/codex-transcript-filter.ts +31 -0
  156. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +104 -48
  157. package/src/lib/concurrency.ts +41 -0
  158. package/src/lib/conversation-api.ts +1172 -0
  159. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  160. package/src/lib/conversation-data/antigravity-adapter.ts +167 -0
  161. package/src/lib/conversation-data/claude-code-adapter.ts +206 -0
  162. package/src/lib/conversation-data/codex-adapter.ts +269 -0
  163. package/src/lib/conversation-data/cursor-adapter.ts +205 -0
  164. package/src/lib/conversation-data/grok-adapter.ts +210 -0
  165. package/src/lib/conversation-data/index.ts +386 -0
  166. package/src/lib/conversation-data/kiro-adapter.ts +174 -0
  167. package/src/lib/conversation-data/message-selector.ts +37 -0
  168. package/src/lib/conversation-data/opencode-adapter.ts +202 -0
  169. package/src/lib/conversation-data/path-match.ts +79 -0
  170. package/src/lib/conversation-data/qoder-adapter.ts +255 -0
  171. package/src/lib/conversation-data/types.ts +159 -0
  172. package/src/lib/conversation-zip-export.ts +57 -0
  173. package/src/lib/cursor-db.ts +100 -25
  174. package/src/lib/cursor-exporter-types.ts +0 -19
  175. package/src/lib/grok-db.ts +1026 -0
  176. package/src/lib/grok-exporter-types.ts +110 -0
  177. package/src/lib/grok-transcript-phase.ts +52 -0
  178. package/src/lib/grok-transcript.ts +154 -0
  179. package/src/lib/kiro-db.ts +52 -1
  180. package/src/lib/model-label.ts +11 -3
  181. package/src/lib/opencode-db.ts +598 -55
  182. package/src/lib/opencode-transcript.ts +8 -5
  183. package/src/lib/qoder-acp-client.ts +268 -0
  184. package/src/lib/qoder-db.ts +1601 -0
  185. package/src/lib/qoder-exporter-types.ts +114 -0
  186. package/src/lib/qoder-transcript-phase.ts +41 -0
  187. package/src/lib/qoder-transcript.ts +112 -0
  188. package/src/lib/shared.ts +12 -0
  189. package/src/lib/transcript-load-limiter.ts +82 -0
  190. package/src/lib/ui-cache.ts +43 -17
  191. package/src/lib/ui-export-archive.ts +60 -0
  192. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  193. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +0 -1
  194. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +0 -7
  195. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  196. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +0 -1
  197. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +0 -1
  198. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  199. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +0 -1
  200. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  201. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +0 -1
  202. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  203. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +0 -1
  204. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +0 -1
  205. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +0 -1
  206. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +0 -1
  207. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +0 -1
  208. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +0 -1
  209. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +0 -3
  210. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  211. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +0 -1
  212. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +0 -1
  213. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  214. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +0 -2
  215. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  216. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  217. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +0 -7
  218. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  219. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +0 -5
  220. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +0 -1
  221. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  222. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  223. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +0 -1
  224. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +0 -41
  225. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +0 -1
  226. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +0 -1
  227. package/apps/ui/dist/client/assets/index-C-STgIH4.js +0 -166
  228. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  229. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +0 -1
  230. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +0 -1
  231. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +0 -1
  232. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +0 -1
  233. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +0 -1
  234. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +0 -1
  235. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  236. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +0 -1
  237. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +0 -8
  238. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +0 -1
  239. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +0 -1
  240. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +0 -1
  241. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +0 -1
  242. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  243. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +0 -1
  244. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +0 -1
  245. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  246. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +0 -3
  247. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  248. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  249. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  250. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  251. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +0 -1
  252. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  253. package/apps/ui/dist/client/assets/select-CYF6pF37.js +0 -1
  254. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +0 -1
  255. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  256. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +0 -1
  257. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +0 -7
  258. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  259. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +0 -1
  260. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  261. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +0 -1
  262. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +0 -1
  263. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +0 -1
  264. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +0 -387
  265. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  266. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  267. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  268. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  269. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +0 -613
  270. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  271. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-eS87Dx7g.js +0 -20
  272. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  273. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  274. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  275. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  276. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  277. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  278. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  279. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  280. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  281. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  282. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  283. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  284. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  285. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +0 -361
  286. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +0 -37
  287. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +0 -72
  288. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +0 -18
  289. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +0 -473
  290. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +0 -11
  291. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +0 -139
  292. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +0 -10
  293. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +0 -28
  294. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +0 -11
  295. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +0 -191
  296. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +0 -92
  297. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +0 -11
  298. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  299. package/apps/ui/dist/server/assets/codex-server-CGXU4H8-.js +0 -2294
  300. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  301. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  302. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  303. package/apps/ui/dist/server/assets/cursor-db-CZnYy7aT.js +0 -830
  304. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  305. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  306. package/apps/ui/dist/server/assets/cursor-recovery-CXpO8KcD.js +0 -361
  307. package/apps/ui/dist/server/assets/cursor-server-DNu52A3K.js +0 -213
  308. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  309. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +0 -582
  310. package/apps/ui/dist/server/assets/cursor-threads._composerId-p9-ARAzc.js +0 -18
  311. package/apps/ui/dist/server/assets/cursor-transcript-B7tGnqtn.js +0 -125
  312. package/apps/ui/dist/server/assets/cursor._workspaceKey-CZuohIUL.js +0 -28
  313. package/apps/ui/dist/server/assets/cursor._workspaceKey-DPp5JSjy.js +0 -401
  314. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  315. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  316. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  317. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  318. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  319. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  320. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  321. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  322. package/apps/ui/dist/server/assets/export-dialog-D88ze9Gy.js +0 -240
  323. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  324. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +0 -552
  325. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +0 -37
  326. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +0 -72
  327. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +0 -11
  328. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +0 -449
  329. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +0 -18
  330. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +0 -112
  331. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +0 -27
  332. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +0 -11
  333. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +0 -28
  334. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +0 -193
  335. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +0 -11
  336. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +0 -93
  337. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  338. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  339. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  340. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +0 -397
  341. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +0 -50
  342. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +0 -72
  343. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +0 -518
  344. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +0 -18
  345. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +0 -11
  346. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +0 -87
  347. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +0 -141
  348. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +0 -32
  349. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +0 -11
  350. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +0 -28
  351. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +0 -207
  352. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +0 -11
  353. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +0 -93
  354. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  355. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  356. package/apps/ui/dist/server/assets/projects._project-4io5LO0E.js +0 -20
  357. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  358. package/apps/ui/dist/server/assets/projects._project-D6ZoGhJ8.js +0 -395
  359. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  360. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  361. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  362. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  363. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  364. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +0 -518
  365. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  366. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  367. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  368. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  369. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  370. package/apps/ui/dist/server/assets/shared-DyhChtHf.js +0 -137
  371. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +0 -38
  372. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  373. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +0 -4
  374. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +0 -501
  375. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  376. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +0 -45
  377. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  378. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +0 -648
  379. package/apps/ui/dist/server/assets/threads._threadId-Ci0S4mKU.js +0 -18
  380. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  381. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  382. package/apps/ui/dist/server/server.js +0 -5895
  383. package/bin/codex-chats-claude.js +0 -5
  384. package/bin/codex-chats.js +0 -5
  385. package/bin/spiracha.js +0 -5
  386. package/src/export-chats.ts +0 -120
  387. package/src/export-claude.ts +0 -36
  388. package/src/export-cursor.ts +0 -244
  389. package/src/lib/claude-exporter.ts +0 -864
  390. package/src/lib/codex-exporter-cli.ts +0 -277
  391. package/src/lib/codex-exporter-db.ts +0 -319
  392. package/src/lib/codex-exporter.ts +0 -115
  393. package/src/lib/cursor-exporter.ts +0 -266
  394. package/src/lib/interactive-cli.ts +0 -433
  395. package/src/lib/native-open.ts +0 -54
  396. package/src/mcp-server.ts +0 -137
  397. package/src/spiracha.ts +0 -116
  398. package/src/ui-cli.ts +0 -310
  399. /package/apps/ui/{dist/client → public}/icon.svg +0 -0
  400. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  401. /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
  };