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,4 +1,4 @@
1
- import { readdir, stat } from 'node:fs/promises';
1
+ import { readdir, stat, unlink } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import {
4
4
  type ClaudeCodeSessionSummary,
@@ -9,6 +9,11 @@ import {
9
9
  getDefaultClaudeCodeDataDir,
10
10
  resolveClaudeCodeProjectsDir,
11
11
  } from './claude-code-exporter-types';
12
+ import {
13
+ getClaudeCodeAssistantMessagePhase,
14
+ isClaudeCodeRawFlagEnabled,
15
+ isClaudeCodeSyntheticTranscriptEntry,
16
+ } from './claude-code-transcript-phase';
12
17
  import { mapWithConcurrency } from './concurrency';
13
18
  import {
14
19
  asBoolean,
@@ -38,6 +43,18 @@ type ParsedTranscriptFile = {
38
43
  transcript: ClaudeCodeSessionTranscript;
39
44
  };
40
45
 
46
+ type ReadTranscriptFileOptions = {
47
+ includeRawPayloads?: boolean;
48
+ maxRawPayloadFileSizeBytes?: number;
49
+ };
50
+
51
+ export type ReadClaudeCodeSessionTranscriptOptions = ReadTranscriptFileOptions;
52
+
53
+ export type DeleteClaudeCodeSessionResult = {
54
+ deletedFiles: string[];
55
+ deletedSessionIds: string[];
56
+ };
57
+
41
58
  type SessionStats = {
42
59
  assistantMessageCount: number;
43
60
  attachmentCount: number;
@@ -52,6 +69,11 @@ type SessionStats = {
52
69
  userMessageCount: number;
53
70
  };
54
71
 
72
+ type SessionStatsTracker = {
73
+ assistantMessageIds: Set<string>;
74
+ usageMessageIds: Set<string>;
75
+ };
76
+
55
77
  type SessionIdentity = {
56
78
  cwd: string | null;
57
79
  firstUserText: string | null;
@@ -75,6 +97,18 @@ const pathExists = async (target: string): Promise<boolean> => {
75
97
  .catch(() => false);
76
98
  };
77
99
 
100
+ const unlinkIfPresent = async (target: string): Promise<boolean> => {
101
+ try {
102
+ await unlink(target);
103
+ return true;
104
+ } catch (error) {
105
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
106
+ return false;
107
+ }
108
+ throw error;
109
+ }
110
+ };
111
+
78
112
  const cleanLabel = (value: string | null | undefined): string | null => {
79
113
  const cleaned = value?.replace(/\s+/g, ' ').trim();
80
114
  return cleaned ? cleaned : null;
@@ -281,19 +315,21 @@ const parseTranscriptEntry = (raw: Record<string, JsonValue>): ClaudeCodeTranscr
281
315
  const type = asString(raw.type ?? null) ?? 'unknown';
282
316
  const message = asObject(raw.message ?? null);
283
317
  const parts = getTranscriptEntryParts(type, message, raw);
318
+ const role = getTranscriptEntryRole(type, message);
284
319
 
285
320
  if (parts.length === 0) {
286
321
  return null;
287
322
  }
288
323
 
289
324
  return {
325
+ assistantPhase: getClaudeCodeAssistantMessagePhase({ parts, raw, role }),
290
326
  cwd: asString(raw.cwd ?? null),
291
327
  entryId: getTranscriptEntryId(type, raw),
292
328
  model: asString(message?.model ?? null),
293
329
  parentEntryId: asString(raw.parentUuid ?? null),
294
330
  parts,
295
331
  raw,
296
- role: getTranscriptEntryRole(type, message),
332
+ role,
297
333
  timestamp: asString(raw.timestamp ?? null),
298
334
  type,
299
335
  };
@@ -313,31 +349,87 @@ const createEmptyStats = (): SessionStats => ({
313
349
  userMessageCount: 0,
314
350
  });
315
351
 
316
- const addUsageStats = (stats: SessionStats, message: Record<string, JsonValue> | null) => {
352
+ const createStatsTracker = (): SessionStatsTracker => ({
353
+ assistantMessageIds: new Set(),
354
+ usageMessageIds: new Set(),
355
+ });
356
+
357
+ const getMessageIdentity = (entry: ClaudeCodeTranscriptEntry): string => {
358
+ const message = asObject(entry.raw.message ?? null);
359
+ return asString(message?.id ?? null) ?? entry.entryId;
360
+ };
361
+
362
+ const addUsageStats = (stats: SessionStats, tracker: SessionStatsTracker, entry: ClaudeCodeTranscriptEntry) => {
363
+ const message = asObject(entry.raw.message ?? null);
317
364
  const usage = asObject(message?.usage ?? null);
318
365
  if (!usage) {
319
366
  return;
320
367
  }
321
368
 
369
+ const messageId = getMessageIdentity(entry);
370
+ if (tracker.usageMessageIds.has(messageId)) {
371
+ return;
372
+ }
373
+ tracker.usageMessageIds.add(messageId);
374
+
322
375
  stats.inputTokens += asNumber(usage.input_tokens ?? null) ?? 0;
323
376
  stats.outputTokens += asNumber(usage.output_tokens ?? null) ?? 0;
324
377
  stats.cacheCreationInputTokens += asNumber(usage.cache_creation_input_tokens ?? null) ?? 0;
325
378
  stats.cacheReadInputTokens += asNumber(usage.cache_read_input_tokens ?? null) ?? 0;
326
379
  };
327
380
 
328
- const updateStatsFromEntry = (stats: SessionStats, entry: ClaudeCodeTranscriptEntry) => {
329
- if (entry.type === 'user' || entry.type === 'assistant') {
330
- stats.messageCount += 1;
381
+ const hasTextPart = (entry: ClaudeCodeTranscriptEntry): boolean => {
382
+ return entry.parts.some((part) => part.type === 'text' && part.text?.trim());
383
+ };
384
+
385
+ const extractUserPromptText = (text: string): string | null => {
386
+ const cleaned = cleanExtractedText(text).trim();
387
+ if (!cleaned.startsWith('<!-- attach -->')) {
388
+ return cleaned || null;
331
389
  }
332
390
 
333
- if (entry.role === 'user') {
391
+ const lines = cleaned.split('\n').slice(1);
392
+ let sawQuotedAttachment = false;
393
+ for (const [index, line] of lines.entries()) {
394
+ const trimmed = line.trim();
395
+ if (trimmed.startsWith('>')) {
396
+ sawQuotedAttachment = true;
397
+ continue;
398
+ }
399
+ if (!trimmed) {
400
+ continue;
401
+ }
402
+ if (sawQuotedAttachment) {
403
+ return lines.slice(index).join('\n').trim() || null;
404
+ }
405
+ }
406
+
407
+ return null;
408
+ };
409
+
410
+ const updateMessageStats = (stats: SessionStats, tracker: SessionStatsTracker, entry: ClaudeCodeTranscriptEntry) => {
411
+ if (entry.role === 'user' && hasTextPart(entry) && !isClaudeCodeSyntheticTranscriptEntry(entry)) {
412
+ stats.messageCount += 1;
334
413
  stats.userMessageCount += 1;
414
+ return;
335
415
  }
336
416
 
337
- if (entry.role === 'assistant') {
338
- stats.assistantMessageCount += 1;
417
+ if (entry.role !== 'assistant' || !hasTextPart(entry)) {
418
+ return;
339
419
  }
340
420
 
421
+ const messageId = getMessageIdentity(entry);
422
+ if (tracker.assistantMessageIds.has(messageId)) {
423
+ return;
424
+ }
425
+ tracker.assistantMessageIds.add(messageId);
426
+ stats.messageCount += 1;
427
+ stats.assistantMessageCount += 1;
428
+ };
429
+
430
+ const updateStatsFromEntry = (stats: SessionStats, tracker: SessionStatsTracker, entry: ClaudeCodeTranscriptEntry) => {
431
+ updateMessageStats(stats, tracker, entry);
432
+
341
433
  if (entry.type === 'attachment') {
342
434
  stats.attachmentCount += 1;
343
435
  }
@@ -345,7 +437,7 @@ const updateStatsFromEntry = (stats: SessionStats, entry: ClaudeCodeTranscriptEn
345
437
  stats.toolCallCount += entry.parts.filter((part) => part.type === 'tool_use').length;
346
438
  stats.toolResultCount += entry.parts.filter((part) => part.type === 'tool_result').length;
347
439
  stats.renderablePartCount += entry.parts.filter(isRenderablePart).length;
348
- addUsageStats(stats, asObject(entry.raw.message ?? null));
440
+ addUsageStats(stats, tracker, entry);
349
441
  };
350
442
 
351
443
  const updateTimeline = (
@@ -382,9 +474,9 @@ const updateIdentityFromRaw = (identity: SessionIdentity, raw: Record<string, Js
382
474
  };
383
475
 
384
476
  const updateIdentityFromEntry = (identity: SessionIdentity, entry: ClaudeCodeTranscriptEntry) => {
385
- if (entry.role === 'user' && !identity.firstUserText) {
477
+ if (entry.role === 'user' && !identity.firstUserText && !isClaudeCodeSyntheticTranscriptEntry(entry)) {
386
478
  const textPart = entry.parts.find((part) => part.type === 'text' && part.text?.trim());
387
- identity.firstUserText = cleanExtractedText(textPart?.text ?? '').trim() || null;
479
+ identity.firstUserText = extractUserPromptText(textPart?.text ?? '');
388
480
  }
389
481
  };
390
482
 
@@ -450,10 +542,58 @@ const isRenderablePart = (part: ClaudeCodeTranscriptPart): boolean => {
450
542
  return false;
451
543
  };
452
544
 
453
- const readTranscriptFile = async (file: TranscriptFile): Promise<ParsedTranscriptFile | null> => {
454
- const fallbackMtimeMs = await stat(file.filePath)
455
- .then((stats) => stats.mtimeMs)
456
- .catch(() => null);
545
+ const stripEntryRawPayloads = (entry: ClaudeCodeTranscriptEntry): ClaudeCodeTranscriptEntry => {
546
+ return {
547
+ ...entry,
548
+ parts: entry.parts.map((part) => ({ ...part, raw: {} })),
549
+ raw: {},
550
+ };
551
+ };
552
+
553
+ const markIncompleteAssistantParentAsCommentary = (
554
+ entries: ClaudeCodeTranscriptEntry[],
555
+ entry: ClaudeCodeTranscriptEntry,
556
+ ): void => {
557
+ const interruptedByUser =
558
+ entry.role === 'user' &&
559
+ entry.parts.some(
560
+ (part) => part.type === 'text' && part.text?.trim().startsWith('[Request interrupted by user]'),
561
+ );
562
+ if ((!interruptedByUser && !isClaudeCodeRawFlagEnabled(entry.raw.isApiErrorMessage)) || !entry.parentEntryId) {
563
+ return;
564
+ }
565
+
566
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
567
+ const candidate = entries[index];
568
+ if (candidate?.entryId === entry.parentEntryId && candidate.role === 'assistant') {
569
+ candidate.assistantPhase = 'commentary';
570
+ return;
571
+ }
572
+ }
573
+ };
574
+
575
+ const markLatestAssistantAsCommentary = (entries: ClaudeCodeTranscriptEntry[]): void => {
576
+ const latestEntry = entries.at(-1);
577
+ if (latestEntry?.role === 'assistant') {
578
+ latestEntry.assistantPhase = 'commentary';
579
+ }
580
+ };
581
+
582
+ const isSubagentTaskNotification = (raw: Record<string, JsonValue>): boolean => {
583
+ return (
584
+ raw.type === 'queue-operation' &&
585
+ asString(raw.content ?? null)
586
+ ?.trimStart()
587
+ .startsWith('<task-notification>') === true
588
+ );
589
+ };
590
+
591
+ const buildTranscriptFromRawEvents = (
592
+ file: TranscriptFile,
593
+ sourceRawEvents: Record<string, JsonValue>[],
594
+ fallbackMtimeMs: number | null,
595
+ includeRawPayloads: boolean,
596
+ ): ClaudeCodeSessionTranscript => {
457
597
  const identity: SessionIdentity = {
458
598
  cwd: null,
459
599
  firstUserText: null,
@@ -464,33 +604,66 @@ const readTranscriptFile = async (file: TranscriptFile): Promise<ParsedTranscrip
464
604
  version: null,
465
605
  };
466
606
  const stats = createEmptyStats();
607
+ const statsTracker = createStatsTracker();
467
608
  const timeline = { firstMs: null as number | null, lastMs: null as number | null };
468
609
  const entries: ClaudeCodeTranscriptEntry[] = [];
469
- const rawEvents: Record<string, JsonValue>[] = [];
470
610
 
471
- for await (const raw of readJsonlObjects(file.filePath)) {
472
- rawEvents.push(raw);
611
+ for (const raw of sourceRawEvents) {
473
612
  updateTimeline(timeline, raw);
474
613
  updateIdentityFromRaw(identity, raw);
475
614
 
615
+ if (isSubagentTaskNotification(raw)) {
616
+ markLatestAssistantAsCommentary(entries);
617
+ }
618
+
476
619
  const entry = parseTranscriptEntry(raw);
477
620
  if (!entry) {
478
621
  continue;
479
622
  }
480
623
 
481
- entries.push(entry);
482
- updateStatsFromEntry(stats, entry);
624
+ markIncompleteAssistantParentAsCommentary(entries, entry);
625
+ if (isClaudeCodeSyntheticTranscriptEntry(entry)) {
626
+ continue;
627
+ }
628
+
629
+ entries.push(includeRawPayloads ? entry : stripEntryRawPayloads(entry));
630
+ updateStatsFromEntry(stats, statsTracker, entry);
483
631
  updateIdentityFromEntry(identity, entry);
484
632
  }
485
633
 
486
634
  const session = toSessionSummary(file, identity, stats, toTimeline(timeline, fallbackMtimeMs));
487
635
  return {
488
- transcript: {
489
- entries,
490
- rawEvents,
491
- renderablePartCount: stats.renderablePartCount,
492
- session,
493
- },
636
+ entries,
637
+ rawEvents: includeRawPayloads ? sourceRawEvents : [],
638
+ rawPayloadsOmitted: includeRawPayloads ? undefined : true,
639
+ renderablePartCount: stats.renderablePartCount,
640
+ session,
641
+ };
642
+ };
643
+
644
+ const readTranscriptFile = async (
645
+ file: TranscriptFile,
646
+ options: ReadTranscriptFileOptions = {},
647
+ ): Promise<ParsedTranscriptFile | null> => {
648
+ const includeRawPayloads = options.includeRawPayloads ?? true;
649
+ const fallbackMtimeMs = await stat(file.filePath)
650
+ .then((stats) => stats.mtimeMs)
651
+ .catch(() => null);
652
+ const rawEvents: Record<string, JsonValue>[] = [];
653
+
654
+ try {
655
+ for await (const raw of readJsonlObjects(file.filePath)) {
656
+ rawEvents.push(raw);
657
+ }
658
+ } catch (error) {
659
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
660
+ return null;
661
+ }
662
+ throw error;
663
+ }
664
+
665
+ return {
666
+ transcript: buildTranscriptFromRawEvents(file, rawEvents, fallbackMtimeMs, includeRawPayloads),
494
667
  };
495
668
  };
496
669
 
@@ -521,10 +694,235 @@ const listTranscriptFiles = async (projectsDir: string): Promise<TranscriptFile[
521
694
  };
522
695
 
523
696
  const readTranscriptFiles = async (files: TranscriptFile[]): Promise<ClaudeCodeSessionTranscript[]> => {
524
- const parsed = await mapWithConcurrency(files, READ_CONCURRENCY, readTranscriptFile);
697
+ const parsed = await mapWithConcurrency(files, READ_CONCURRENCY, (file) => readTranscriptFile(file));
525
698
  return parsed.flatMap((item) => (item ? [item.transcript] : []));
526
699
  };
527
700
 
701
+ const getCompactionSummaryId = (raw: Record<string, JsonValue>): string | null => {
702
+ return raw.isCompactSummary === true ? asString(raw.uuid ?? null) : null;
703
+ };
704
+
705
+ const getCompactionSummaryIds = (transcript: ClaudeCodeSessionTranscript): string[] => {
706
+ return [...new Set(transcript.rawEvents.map(getCompactionSummaryId).filter((id): id is string => id !== null))];
707
+ };
708
+
709
+ type TranscriptLineageIndex = {
710
+ anchorsByIndex: string[][];
711
+ indexesByAnchor: Map<string, number[]>;
712
+ };
713
+
714
+ const indexTranscriptLineageAnchors = (transcripts: ClaudeCodeSessionTranscript[]): TranscriptLineageIndex => {
715
+ const indexesByAnchor = new Map<string, number[]>();
716
+ const anchorsByIndex = transcripts.map((transcript, index) => {
717
+ const anchors = getCompactionSummaryIds(transcript).map(
718
+ (anchorId) => `${transcript.session.workspaceKey}\0${anchorId}`,
719
+ );
720
+ for (const anchor of anchors) {
721
+ const indexes = indexesByAnchor.get(anchor) ?? [];
722
+ indexes.push(index);
723
+ indexesByAnchor.set(anchor, indexes);
724
+ }
725
+ return anchors;
726
+ });
727
+
728
+ return { anchorsByIndex, indexesByAnchor };
729
+ };
730
+
731
+ const collectTranscriptLineage = (
732
+ startIndex: number,
733
+ transcripts: ClaudeCodeSessionTranscript[],
734
+ index: TranscriptLineageIndex,
735
+ visited: Set<number>,
736
+ ): ClaudeCodeSessionTranscript[] => {
737
+ const lineage: ClaudeCodeSessionTranscript[] = [];
738
+ const pending = [startIndex];
739
+ visited.add(startIndex);
740
+
741
+ while (pending.length > 0) {
742
+ const currentIndex = pending.pop() as number;
743
+ const transcript = transcripts[currentIndex];
744
+ if (!transcript) {
745
+ continue;
746
+ }
747
+ lineage.push(transcript);
748
+
749
+ const neighborIndexes = (index.anchorsByIndex[currentIndex] ?? []).flatMap(
750
+ (anchor) => index.indexesByAnchor.get(anchor) ?? [],
751
+ );
752
+ for (const neighborIndex of neighborIndexes) {
753
+ if (!visited.has(neighborIndex)) {
754
+ visited.add(neighborIndex);
755
+ pending.push(neighborIndex);
756
+ }
757
+ }
758
+ }
759
+
760
+ return lineage;
761
+ };
762
+
763
+ const getTranscriptLineages = (transcripts: ClaudeCodeSessionTranscript[]): ClaudeCodeSessionTranscript[][] => {
764
+ const index = indexTranscriptLineageAnchors(transcripts);
765
+ const visited = new Set<number>();
766
+ const lineages: ClaudeCodeSessionTranscript[][] = [];
767
+
768
+ for (const startIndex of transcripts.keys()) {
769
+ if (visited.has(startIndex)) {
770
+ continue;
771
+ }
772
+ lineages.push(collectTranscriptLineage(startIndex, transcripts, index, visited));
773
+ }
774
+
775
+ return lineages;
776
+ };
777
+
778
+ const countContentEntriesBefore = (
779
+ transcript: ClaudeCodeSessionTranscript,
780
+ rawEventIndex: number,
781
+ prefixCounts: WeakMap<ClaudeCodeSessionTranscript, number[]>,
782
+ ): number => {
783
+ let counts = prefixCounts.get(transcript);
784
+ if (!counts) {
785
+ counts = [0];
786
+ for (const raw of transcript.rawEvents) {
787
+ const entry = parseTranscriptEntry(raw);
788
+ counts.push((counts.at(-1) ?? 0) + (entry && !isClaudeCodeSyntheticTranscriptEntry(entry) ? 1 : 0));
789
+ }
790
+ prefixCounts.set(transcript, counts);
791
+ }
792
+ return counts[Math.max(0, Math.min(rawEventIndex, counts.length - 1))] ?? 0;
793
+ };
794
+
795
+ const deduplicateRawEvents = (rawEvents: Record<string, JsonValue>[]): Record<string, JsonValue>[] => {
796
+ const seenIds = new Set<string>();
797
+ return rawEvents.filter((raw) => {
798
+ const eventId = asString(raw.uuid ?? null);
799
+ if (!eventId) {
800
+ return true;
801
+ }
802
+ if (seenIds.has(eventId)) {
803
+ return false;
804
+ }
805
+ seenIds.add(eventId);
806
+ return true;
807
+ });
808
+ };
809
+
810
+ const MAX_CLAUDE_LINEAGE_DEPTH = 64;
811
+
812
+ const buildLogicalRawEvents = (
813
+ lineage: ClaudeCodeSessionTranscript[],
814
+ current: ClaudeCodeSessionTranscript,
815
+ context: {
816
+ memo: WeakMap<ClaudeCodeSessionTranscript, Record<string, JsonValue>[]>;
817
+ prefixCounts: WeakMap<ClaudeCodeSessionTranscript, number[]>;
818
+ } = { memo: new WeakMap(), prefixCounts: new WeakMap() },
819
+ visiting = new Set<string>(),
820
+ depth = 0,
821
+ ): Record<string, JsonValue>[] => {
822
+ const cached = context.memo.get(current);
823
+ if (cached) {
824
+ return cached;
825
+ }
826
+
827
+ const finish = (events: Record<string, JsonValue>[]) => {
828
+ const deduplicated = deduplicateRawEvents(events);
829
+ context.memo.set(current, deduplicated);
830
+ return deduplicated;
831
+ };
832
+
833
+ if (visiting.has(current.session.sessionId) || depth >= MAX_CLAUDE_LINEAGE_DEPTH) {
834
+ return deduplicateRawEvents(current.rawEvents);
835
+ }
836
+
837
+ const firstAnchorIndex = current.rawEvents.findIndex((raw) => getCompactionSummaryId(raw) !== null);
838
+ const firstAnchor = firstAnchorIndex < 0 ? null : getCompactionSummaryId(current.rawEvents[firstAnchorIndex] ?? {});
839
+ if (!firstAnchor) {
840
+ return finish(current.rawEvents);
841
+ }
842
+
843
+ const currentPrefixSize = countContentEntriesBefore(current, firstAnchorIndex, context.prefixCounts);
844
+ const ancestor = lineage
845
+ .filter((candidate) => candidate.session.sessionId !== current.session.sessionId)
846
+ .map((candidate) => {
847
+ const anchorIndex = candidate.rawEvents.findIndex((raw) => getCompactionSummaryId(raw) === firstAnchor);
848
+ return {
849
+ anchorIndex,
850
+ candidate,
851
+ prefixSize:
852
+ anchorIndex < 0 ? -1 : countContentEntriesBefore(candidate, anchorIndex, context.prefixCounts),
853
+ };
854
+ })
855
+ .filter((candidate) => candidate.prefixSize > currentPrefixSize)
856
+ .sort(
857
+ (left, right) =>
858
+ right.prefixSize - left.prefixSize ||
859
+ (left.candidate.session.createdAtMs ?? 0) - (right.candidate.session.createdAtMs ?? 0),
860
+ )[0];
861
+ if (!ancestor) {
862
+ return finish(current.rawEvents);
863
+ }
864
+
865
+ const nextVisiting = new Set(visiting);
866
+ nextVisiting.add(current.session.sessionId);
867
+ const ancestorEvents = buildLogicalRawEvents(lineage, ancestor.candidate, context, nextVisiting, depth + 1);
868
+ const ancestorAnchorIndex = ancestorEvents.findIndex((raw) => getCompactionSummaryId(raw) === firstAnchor);
869
+ if (ancestorAnchorIndex < 0) {
870
+ return finish(current.rawEvents);
871
+ }
872
+
873
+ return finish([...ancestorEvents.slice(0, ancestorAnchorIndex), ...current.rawEvents.slice(firstAnchorIndex)]);
874
+ };
875
+
876
+ const compareTranscriptsByActivity = (
877
+ left: ClaudeCodeSessionTranscript,
878
+ right: ClaudeCodeSessionTranscript,
879
+ ): number => {
880
+ return (
881
+ compareNullableMsDesc(left.session.lastActiveAtMs, right.session.lastActiveAtMs) ||
882
+ left.session.sessionId.localeCompare(right.session.sessionId)
883
+ );
884
+ };
885
+
886
+ const coalesceTranscriptLineage = (lineage: ClaudeCodeSessionTranscript[]): ClaudeCodeSessionTranscript | null => {
887
+ const canonical = [...lineage].sort(compareTranscriptsByActivity)[0];
888
+ if (!canonical) {
889
+ return null;
890
+ }
891
+
892
+ const directoryName = getDirectoryNameFromWorkspaceKey(canonical.session.workspaceKey);
893
+ if (!directoryName) {
894
+ return canonical;
895
+ }
896
+
897
+ const transcript = buildTranscriptFromRawEvents(
898
+ { directoryName, filePath: canonical.session.filePath },
899
+ buildLogicalRawEvents(lineage, canonical),
900
+ canonical.session.lastActiveAtMs,
901
+ true,
902
+ );
903
+ transcript.session.filePath = canonical.session.filePath;
904
+ transcript.session.sessionId = canonical.session.sessionId;
905
+ return transcript;
906
+ };
907
+
908
+ const coalesceTranscriptLineages = (transcripts: ClaudeCodeSessionTranscript[]): ClaudeCodeSessionTranscript[] => {
909
+ return getTranscriptLineages(transcripts).flatMap((lineage) => {
910
+ const transcript = coalesceTranscriptLineage(lineage);
911
+ return transcript ? [transcript] : [];
912
+ });
913
+ };
914
+
915
+ const omitTranscriptRawPayloads = (transcript: ClaudeCodeSessionTranscript): ClaudeCodeSessionTranscript => ({
916
+ ...transcript,
917
+ entries: transcript.entries.map(stripEntryRawPayloads),
918
+ rawEvents: [],
919
+ rawPayloadsOmitted: true,
920
+ });
921
+
922
+ const hasSessionContent = (transcript: ClaudeCodeSessionTranscript): boolean => {
923
+ return transcript.session.userMessageCount > 0 || transcript.session.assistantMessageCount > 0;
924
+ };
925
+
528
926
  const compareNullableMsDesc = (left: number | null, right: number | null): number => {
529
927
  return (right ?? 0) - (left ?? 0);
530
928
  };
@@ -560,7 +958,7 @@ export const listClaudeCodeWorkspaceGroups = async (
560
958
  projectsDir = resolveClaudeCodeProjectsDir(),
561
959
  ): Promise<ClaudeCodeWorkspaceGroup[]> => {
562
960
  const files = await listTranscriptFiles(projectsDir);
563
- const transcripts = await readTranscriptFiles(files);
961
+ const transcripts = coalesceTranscriptLineages(await readTranscriptFiles(files));
564
962
  const sessionsByDirectory = new Map<string, ClaudeCodeSessionSummary[]>();
565
963
 
566
964
  for (const transcript of transcripts) {
@@ -569,6 +967,10 @@ export const listClaudeCodeWorkspaceGroups = async (
569
967
  continue;
570
968
  }
571
969
 
970
+ if (!hasSessionContent(transcript)) {
971
+ continue;
972
+ }
973
+
572
974
  const sessions = sessionsByDirectory.get(directoryName) ?? [];
573
975
  sessions.push(transcript.session);
574
976
  sessionsByDirectory.set(directoryName, sessions);
@@ -629,8 +1031,8 @@ export const listClaudeCodeSessionsForGroup = async (
629
1031
  }
630
1032
 
631
1033
  const files = await listTranscriptFilesForProject(projectsDir, directoryName);
632
- const transcripts = await readTranscriptFiles(files);
633
- return sortSessions(transcripts.map((transcript) => transcript.session));
1034
+ const transcripts = coalesceTranscriptLineages(await readTranscriptFiles(files));
1035
+ return sortSessions(transcripts.filter(hasSessionContent).map((transcript) => transcript.session));
634
1036
  };
635
1037
 
636
1038
  const locateSessionFile = async (projectsDir: string, sessionId: string): Promise<TranscriptFile | null> => {
@@ -641,6 +1043,7 @@ const locateSessionFile = async (projectsDir: string, sessionId: string): Promis
641
1043
  export const readClaudeCodeSessionTranscript = async (
642
1044
  projectsDir: string,
643
1045
  sessionId: string,
1046
+ options: ReadClaudeCodeSessionTranscriptOptions = {},
644
1047
  ): Promise<ClaudeCodeSessionTranscript | null> => {
645
1048
  if (!(await pathExists(projectsDir))) {
646
1049
  return null;
@@ -651,5 +1054,68 @@ export const readClaudeCodeSessionTranscript = async (
651
1054
  return null;
652
1055
  }
653
1056
 
654
- return (await readTranscriptFile(file))?.transcript ?? null;
1057
+ const files = await listTranscriptFilesForProject(projectsDir, file.directoryName);
1058
+ const transcripts = await readTranscriptFiles(files);
1059
+ const lineage = getTranscriptLineages(transcripts).find((candidate) =>
1060
+ candidate.some((transcript) => transcript.session.sessionId === sessionId),
1061
+ );
1062
+ if (!lineage) {
1063
+ return null;
1064
+ }
1065
+
1066
+ const transcript = coalesceTranscriptLineage(lineage);
1067
+ if (!transcript) {
1068
+ return null;
1069
+ }
1070
+ const lineageFileSizeBytes =
1071
+ options.maxRawPayloadFileSizeBytes === undefined
1072
+ ? 0
1073
+ : (
1074
+ await Promise.all(
1075
+ lineage.map((candidate) =>
1076
+ stat(candidate.session.filePath)
1077
+ .then((metadata) => metadata.size)
1078
+ .catch(() => options.maxRawPayloadFileSizeBytes! + 1),
1079
+ ),
1080
+ )
1081
+ ).reduce((total, size) => total + size, 0);
1082
+ const omitRawPayloads =
1083
+ options.includeRawPayloads === false ||
1084
+ (options.maxRawPayloadFileSizeBytes !== undefined && lineageFileSizeBytes > options.maxRawPayloadFileSizeBytes);
1085
+ return omitRawPayloads ? omitTranscriptRawPayloads(transcript) : transcript;
1086
+ };
1087
+
1088
+ export const deleteClaudeCodeSession = async (
1089
+ projectsDir: string,
1090
+ sessionId: string,
1091
+ ): Promise<DeleteClaudeCodeSessionResult> => {
1092
+ if (!(await pathExists(projectsDir))) {
1093
+ return { deletedFiles: [], deletedSessionIds: [] };
1094
+ }
1095
+
1096
+ const file = await locateSessionFile(projectsDir, sessionId);
1097
+ if (!file) {
1098
+ return { deletedFiles: [], deletedSessionIds: [] };
1099
+ }
1100
+
1101
+ const files = await listTranscriptFilesForProject(projectsDir, file.directoryName);
1102
+ const transcripts = await readTranscriptFiles(files);
1103
+ const lineage = getTranscriptLineages(transcripts).find((candidate) =>
1104
+ candidate.some((transcript) => transcript.session.sessionId === sessionId),
1105
+ );
1106
+ const targets = (lineage ?? [])
1107
+ .map((transcript) => ({
1108
+ filePath: transcript.session.filePath,
1109
+ sessionId: transcript.session.sessionId,
1110
+ }))
1111
+ .sort((left, right) => left.filePath.localeCompare(right.filePath));
1112
+ if (targets.length === 0) {
1113
+ return { deletedFiles: [], deletedSessionIds: [] };
1114
+ }
1115
+
1116
+ const removed = await Promise.all(targets.map((target) => unlinkIfPresent(target.filePath)));
1117
+ return {
1118
+ deletedFiles: targets.flatMap((target, index) => (removed[index] ? [target.filePath] : [])),
1119
+ deletedSessionIds: targets.flatMap((target, index) => (removed[index] ? [target.sessionId] : [])),
1120
+ };
655
1121
  };