spiracha 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/AGENTS.md +114 -205
  2. package/README.md +89 -269
  3. package/apps/ui/AGENTS.md +17 -7
  4. package/apps/ui/README.md +34 -8
  5. package/apps/ui/components.json +21 -0
  6. package/apps/ui/package.json +65 -0
  7. package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
  8. package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
  9. package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
  10. package/apps/ui/src/components/app-shell.tsx +124 -0
  11. package/apps/ui/src/components/breadcrumbs.tsx +53 -0
  12. package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
  13. package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
  14. package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
  15. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  16. package/apps/ui/src/components/data-table.tsx +241 -0
  17. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  18. package/apps/ui/src/components/export-dialog.tsx +184 -0
  19. package/apps/ui/src/components/json-panel.tsx +17 -0
  20. package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
  21. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  22. package/apps/ui/src/components/list-search-input.tsx +18 -0
  23. package/apps/ui/src/components/loading-panel.tsx +23 -0
  24. package/apps/ui/src/components/metadata-section.tsx +31 -0
  25. package/apps/ui/src/components/metric-card.tsx +19 -0
  26. package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
  27. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  28. package/apps/ui/src/components/page-header.tsx +33 -0
  29. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  30. package/apps/ui/src/components/projects-table.tsx +108 -0
  31. package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
  32. package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
  33. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  34. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  35. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  36. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  37. package/apps/ui/src/components/threads-table.tsx +202 -0
  38. package/apps/ui/src/components/transcript-view.tsx +552 -0
  39. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  40. package/apps/ui/src/components/ui/badge.tsx +45 -0
  41. package/apps/ui/src/components/ui/button.tsx +62 -0
  42. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  43. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  44. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  45. package/apps/ui/src/components/ui/input.tsx +21 -0
  46. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  47. package/apps/ui/src/components/ui/select.tsx +163 -0
  48. package/apps/ui/src/components/ui/separator.tsx +28 -0
  49. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  50. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  51. package/apps/ui/src/components/ui/table.tsx +76 -0
  52. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  53. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  54. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  55. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  56. package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
  57. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  58. package/apps/ui/src/lib/antigravity-server.ts +128 -0
  59. package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
  60. package/apps/ui/src/lib/claude-code-queries.ts +26 -0
  61. package/apps/ui/src/lib/claude-code-server.ts +75 -0
  62. package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
  63. package/apps/ui/src/lib/codex-queries.ts +66 -0
  64. package/apps/ui/src/lib/codex-server.ts +209 -0
  65. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  66. package/apps/ui/src/lib/cursor-server.ts +272 -0
  67. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  68. package/apps/ui/src/lib/download.ts +166 -0
  69. package/apps/ui/src/lib/formatters.ts +149 -0
  70. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  71. package/apps/ui/src/lib/kiro-server.ts +73 -0
  72. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  73. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  74. package/apps/ui/src/lib/opencode-server.ts +73 -0
  75. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  76. package/apps/ui/src/lib/package-metadata.ts +16 -0
  77. package/apps/ui/src/lib/path-utils.ts +13 -0
  78. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  79. package/apps/ui/src/lib/qoder-server.ts +79 -0
  80. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  81. package/apps/ui/src/lib/route-search.ts +68 -0
  82. package/apps/ui/src/lib/settings-store.tsx +59 -0
  83. package/apps/ui/src/lib/source-session-export-server.ts +56 -0
  84. package/apps/ui/src/lib/text-filter.ts +22 -0
  85. package/apps/ui/src/lib/thread-id.ts +3 -0
  86. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  87. package/apps/ui/src/lib/utils.ts +7 -0
  88. package/apps/ui/src/routeTree.gen.ts +730 -0
  89. package/apps/ui/src/router.tsx +20 -0
  90. package/apps/ui/src/routes/$threadId.tsx +22 -0
  91. package/apps/ui/src/routes/__root.tsx +127 -0
  92. package/apps/ui/src/routes/analytics.tsx +143 -0
  93. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
  94. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
  95. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  96. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  97. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  98. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
  99. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  100. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  101. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  102. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
  103. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
  104. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  105. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  106. package/apps/ui/src/routes/codex.index.tsx +116 -0
  107. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  108. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
  109. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  110. package/apps/ui/src/routes/index.tsx +124 -0
  111. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
  112. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
  113. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  114. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
  115. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
  116. package/apps/ui/src/routes/opencode.index.tsx +48 -0
  117. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
  118. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
  119. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  120. package/apps/ui/src/routes/settings.tsx +91 -0
  121. package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
  122. package/apps/ui/src/styles.css +122 -0
  123. package/apps/ui/tsconfig.json +29 -0
  124. package/apps/ui/vite.config.ts +84 -0
  125. package/bin/spiracha.ts +32 -0
  126. package/package.json +46 -73
  127. package/src/client.ts +290 -0
  128. package/src/lib/antigravity-db.ts +213 -19
  129. package/src/lib/antigravity-exporter-types.ts +1 -0
  130. package/src/lib/codex-analytics.ts +1 -1
  131. package/src/lib/codex-browser-db.ts +410 -74
  132. package/src/lib/codex-browser-export.ts +6 -14
  133. package/src/lib/codex-browser-types.ts +1 -1
  134. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  135. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
  136. package/src/lib/conversation-api.ts +792 -0
  137. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  138. package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
  139. package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
  140. package/src/lib/conversation-data/codex-adapter.ts +245 -0
  141. package/src/lib/conversation-data/cursor-adapter.ts +171 -0
  142. package/src/lib/conversation-data/index.ts +311 -0
  143. package/src/lib/conversation-data/kiro-adapter.ts +140 -0
  144. package/src/lib/conversation-data/message-selector.ts +37 -0
  145. package/src/lib/conversation-data/opencode-adapter.ts +180 -0
  146. package/src/lib/conversation-data/path-match.ts +79 -0
  147. package/src/lib/conversation-data/qoder-adapter.ts +230 -0
  148. package/src/lib/conversation-data/types.ts +116 -0
  149. package/src/lib/cursor-db.ts +67 -21
  150. package/src/lib/cursor-exporter-types.ts +0 -19
  151. package/src/lib/qoder-acp-client.ts +268 -0
  152. package/src/lib/qoder-db.ts +1601 -0
  153. package/src/lib/qoder-exporter-types.ts +114 -0
  154. package/src/lib/qoder-transcript-phase.ts +41 -0
  155. package/src/lib/qoder-transcript.ts +112 -0
  156. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  157. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +0 -1
  158. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +0 -7
  159. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  160. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +0 -1
  161. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +0 -1
  162. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  163. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +0 -1
  164. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  165. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +0 -1
  166. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  167. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +0 -1
  168. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +0 -1
  169. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +0 -1
  170. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +0 -1
  171. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +0 -1
  172. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +0 -1
  173. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +0 -3
  174. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  175. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +0 -1
  176. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +0 -1
  177. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  178. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +0 -2
  179. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  180. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  181. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +0 -7
  182. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  183. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +0 -5
  184. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +0 -1
  185. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  186. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  187. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +0 -1
  188. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +0 -41
  189. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +0 -1
  190. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +0 -1
  191. package/apps/ui/dist/client/assets/index-C-STgIH4.js +0 -166
  192. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  193. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +0 -1
  194. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +0 -1
  195. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +0 -1
  196. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +0 -1
  197. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +0 -1
  198. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +0 -1
  199. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  200. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +0 -1
  201. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +0 -8
  202. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +0 -1
  203. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +0 -1
  204. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +0 -1
  205. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +0 -1
  206. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  207. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +0 -1
  208. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +0 -1
  209. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  210. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +0 -3
  211. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  212. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  213. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  214. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  215. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +0 -1
  216. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  217. package/apps/ui/dist/client/assets/select-CYF6pF37.js +0 -1
  218. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +0 -1
  219. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  220. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +0 -1
  221. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +0 -7
  222. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  223. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +0 -1
  224. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  225. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +0 -1
  226. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +0 -1
  227. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +0 -1
  228. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +0 -387
  229. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  230. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  231. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  232. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  233. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +0 -613
  234. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  235. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-eS87Dx7g.js +0 -20
  236. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  237. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  238. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  239. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  240. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  241. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  242. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  243. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  244. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  245. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  246. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  247. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  248. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  249. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +0 -361
  250. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +0 -37
  251. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +0 -72
  252. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +0 -18
  253. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +0 -473
  254. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +0 -11
  255. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +0 -139
  256. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +0 -10
  257. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +0 -28
  258. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +0 -11
  259. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +0 -191
  260. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +0 -92
  261. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +0 -11
  262. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  263. package/apps/ui/dist/server/assets/codex-server-CGXU4H8-.js +0 -2294
  264. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  265. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  266. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  267. package/apps/ui/dist/server/assets/cursor-db-CZnYy7aT.js +0 -830
  268. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  269. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  270. package/apps/ui/dist/server/assets/cursor-recovery-CXpO8KcD.js +0 -361
  271. package/apps/ui/dist/server/assets/cursor-server-DNu52A3K.js +0 -213
  272. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  273. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +0 -582
  274. package/apps/ui/dist/server/assets/cursor-threads._composerId-p9-ARAzc.js +0 -18
  275. package/apps/ui/dist/server/assets/cursor-transcript-B7tGnqtn.js +0 -125
  276. package/apps/ui/dist/server/assets/cursor._workspaceKey-CZuohIUL.js +0 -28
  277. package/apps/ui/dist/server/assets/cursor._workspaceKey-DPp5JSjy.js +0 -401
  278. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  279. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  280. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  281. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  282. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  283. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  284. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  285. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  286. package/apps/ui/dist/server/assets/export-dialog-D88ze9Gy.js +0 -240
  287. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  288. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +0 -552
  289. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +0 -37
  290. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +0 -72
  291. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +0 -11
  292. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +0 -449
  293. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +0 -18
  294. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +0 -112
  295. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +0 -27
  296. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +0 -11
  297. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +0 -28
  298. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +0 -193
  299. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +0 -11
  300. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +0 -93
  301. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  302. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  303. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  304. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +0 -397
  305. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +0 -50
  306. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +0 -72
  307. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +0 -518
  308. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +0 -18
  309. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +0 -11
  310. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +0 -87
  311. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +0 -141
  312. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +0 -32
  313. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +0 -11
  314. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +0 -28
  315. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +0 -207
  316. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +0 -11
  317. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +0 -93
  318. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  319. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  320. package/apps/ui/dist/server/assets/projects._project-4io5LO0E.js +0 -20
  321. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  322. package/apps/ui/dist/server/assets/projects._project-D6ZoGhJ8.js +0 -395
  323. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  324. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  325. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  326. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  327. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  328. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +0 -518
  329. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  330. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  331. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  332. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  333. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  334. package/apps/ui/dist/server/assets/shared-DyhChtHf.js +0 -137
  335. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +0 -38
  336. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  337. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +0 -4
  338. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +0 -501
  339. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  340. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +0 -45
  341. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  342. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +0 -648
  343. package/apps/ui/dist/server/assets/threads._threadId-Ci0S4mKU.js +0 -18
  344. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  345. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  346. package/apps/ui/dist/server/server.js +0 -5895
  347. package/bin/codex-chats-claude.js +0 -5
  348. package/bin/codex-chats.js +0 -5
  349. package/bin/spiracha.js +0 -5
  350. package/src/export-chats.ts +0 -120
  351. package/src/export-claude.ts +0 -36
  352. package/src/export-cursor.ts +0 -244
  353. package/src/lib/claude-exporter.ts +0 -864
  354. package/src/lib/codex-exporter-cli.ts +0 -277
  355. package/src/lib/codex-exporter-db.ts +0 -319
  356. package/src/lib/codex-exporter.ts +0 -115
  357. package/src/lib/cursor-exporter.ts +0 -266
  358. package/src/lib/interactive-cli.ts +0 -433
  359. package/src/lib/native-open.ts +0 -54
  360. package/src/mcp-server.ts +0 -137
  361. package/src/spiracha.ts +0 -116
  362. package/src/ui-cli.ts +0 -310
  363. /package/apps/ui/{dist/client → public}/icon.svg +0 -0
  364. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  365. /package/apps/ui/{dist/client → public}/robots.txt +0 -0
@@ -0,0 +1,171 @@
1
+ import { mapWithConcurrency } from '../concurrency';
2
+ import {
3
+ listCursorThreadsForGroup,
4
+ listCursorWorkspaceGroups,
5
+ readCursorThreadTranscriptWithAgentFiles,
6
+ } from '../cursor-db';
7
+ import type {
8
+ CursorBubble,
9
+ CursorThreadSummary,
10
+ CursorThreadTranscript,
11
+ CursorWorkspaceGroup,
12
+ } from '../cursor-exporter-types';
13
+ import { getCursorGlobalDbPath, resolveCursorUserDir } from '../cursor-exporter-types';
14
+ import { cleanInlineTitle } from '../shared';
15
+ import { createDeepLinks, createTextMessage, finalizeMessages } from './adapter-helpers';
16
+ import { selectConversationMessages } from './message-selector';
17
+ import { getFirstConversationPathMatch } from './path-match';
18
+ import type {
19
+ ConversationAdapter,
20
+ ConversationDetail,
21
+ ConversationMessage,
22
+ ConversationPathMatch,
23
+ GetConversationOptions,
24
+ ListConversationsForPathOptions,
25
+ } from './types';
26
+
27
+ const CURSOR_CONVERSATION_HYDRATION_CONCURRENCY = 4;
28
+
29
+ const getUserDir = (options: { locations?: { cursorUserDir?: string } }) =>
30
+ options.locations?.cursorUserDir ?? resolveCursorUserDir();
31
+
32
+ const isWithinUpdatedWindow = (
33
+ thread: CursorThreadSummary,
34
+ options: Pick<ListConversationsForPathOptions, 'updatedAfterMs' | 'updatedBeforeMs'>,
35
+ ): boolean => {
36
+ const updatedAtMs = thread.lastUpdatedAtMs ?? 0;
37
+ if (options.updatedAfterMs !== undefined && updatedAtMs < options.updatedAfterMs) {
38
+ return false;
39
+ }
40
+ if (options.updatedBeforeMs !== undefined && updatedAtMs > options.updatedBeforeMs) {
41
+ return false;
42
+ }
43
+ return true;
44
+ };
45
+
46
+ const bubbleToMessages = (bubble: CursorBubble, order: number): ConversationMessage[] => {
47
+ const thinking = createTextMessage({
48
+ createdAtMs: bubble.createdAtMs,
49
+ id: `${bubble.bubbleId}:thinking`,
50
+ order,
51
+ phase: 'reasoning',
52
+ role: 'assistant',
53
+ text: bubble.thinking,
54
+ });
55
+ const text = createTextMessage({
56
+ createdAtMs: bubble.createdAtMs,
57
+ id: bubble.bubbleId,
58
+ order,
59
+ phase: bubble.kind === 'assistant' ? 'final_answer' : 'unknown',
60
+ role: bubble.kind === 'assistant' ? 'assistant' : bubble.kind === 'user' ? 'user' : 'unknown',
61
+ text: bubble.text,
62
+ });
63
+ const tool = bubble.toolCall
64
+ ? createTextMessage({
65
+ createdAtMs: bubble.createdAtMs,
66
+ id: `${bubble.bubbleId}:tool`,
67
+ metadata: { callId: bubble.toolCall.callId, status: bubble.toolCall.status },
68
+ order,
69
+ phase: bubble.toolCall.resultText ? 'tool_output' : 'tool_call',
70
+ role: 'tool',
71
+ text: bubble.toolCall.resultText ?? bubble.toolCall.argumentsText ?? bubble.toolCall.name,
72
+ })
73
+ : [];
74
+
75
+ return [...thinking, ...text, ...tool];
76
+ };
77
+
78
+ const transcriptToMessages = (transcript: CursorThreadTranscript) => {
79
+ return finalizeMessages(transcript.bubbles.flatMap((bubble, order) => bubbleToMessages(bubble, order)));
80
+ };
81
+
82
+ const getWorkspacePath = (group: CursorWorkspaceGroup) => {
83
+ return group.folders[0] ?? null;
84
+ };
85
+
86
+ const buildConversation = async (
87
+ thread: CursorThreadSummary,
88
+ group: CursorWorkspaceGroup,
89
+ userDir: string,
90
+ matches: ConversationPathMatch[],
91
+ options: Pick<ListConversationsForPathOptions, 'includeMessages' | 'messageSelector'>,
92
+ ): Promise<ConversationDetail> => {
93
+ const transcript = options.includeMessages
94
+ ? await readCursorThreadTranscriptWithAgentFiles(getCursorGlobalDbPath(userDir), thread.composerId, userDir)
95
+ : null;
96
+ const allMessages = transcript ? transcriptToMessages(transcript) : [];
97
+ const messages = options.includeMessages
98
+ ? selectConversationMessages(allMessages, options.messageSelector ?? 'last_final_answer')
99
+ : [];
100
+
101
+ return {
102
+ createdAtMs: thread.createdAtMs,
103
+ deepLinks: createDeepLinks('cursor', thread.composerId, `/cursor-threads/${thread.composerId}`),
104
+ id: thread.composerId,
105
+ matches,
106
+ messageCount: options.includeMessages ? allMessages.length : thread.bubbleCount,
107
+ messages,
108
+ metadata: {
109
+ bubbleBytes: thread.bubbleBytes,
110
+ bucketId: thread.bucketId,
111
+ mode: thread.mode,
112
+ transcriptDirs: thread.transcriptDirs,
113
+ },
114
+ source: 'cursor',
115
+ title: cleanInlineTitle(thread.name),
116
+ updatedAtMs: thread.lastUpdatedAtMs,
117
+ workspaceKey: group.key,
118
+ workspacePath: getWorkspacePath(group),
119
+ };
120
+ };
121
+
122
+ const listCursorConversationsForPath = async (options: ListConversationsForPathOptions) => {
123
+ const userDir = getUserDir(options);
124
+ const groups = await listCursorWorkspaceGroups(userDir, { updatedAfterMs: options.updatedAfterMs });
125
+ const candidates: { group: CursorWorkspaceGroup; match: ConversationPathMatch; thread: CursorThreadSummary }[] = [];
126
+
127
+ for (const group of groups) {
128
+ const match = await getFirstConversationPathMatch(options.cwd, group.folders);
129
+ if (!match) {
130
+ continue;
131
+ }
132
+ const threads = await listCursorThreadsForGroup(group, userDir, {
133
+ includeTranscriptDirs: false,
134
+ updatedAfterMs: options.updatedAfterMs,
135
+ });
136
+ for (const thread of threads) {
137
+ if (!isWithinUpdatedWindow(thread, options)) {
138
+ continue;
139
+ }
140
+
141
+ candidates.push({ group, match, thread });
142
+ }
143
+ }
144
+
145
+ return await mapWithConcurrency(candidates, CURSOR_CONVERSATION_HYDRATION_CONCURRENCY, ({ group, match, thread }) =>
146
+ buildConversation(thread, group, userDir, [match], options),
147
+ );
148
+ };
149
+
150
+ const getCursorConversation = async (options: GetConversationOptions): Promise<ConversationDetail | null> => {
151
+ const userDir = getUserDir(options);
152
+ const groups = await listCursorWorkspaceGroups(userDir);
153
+ for (const group of groups) {
154
+ const threads = await listCursorThreadsForGroup(group, userDir, { includeTranscriptDirs: false });
155
+ const thread = threads.find((entry) => entry.composerId === options.id);
156
+ if (thread) {
157
+ return buildConversation(thread, group, userDir, [], {
158
+ includeMessages: true,
159
+ messageSelector: options.messageSelector ?? 'all',
160
+ });
161
+ }
162
+ }
163
+
164
+ return null;
165
+ };
166
+
167
+ export const cursorConversationAdapter: ConversationAdapter = {
168
+ getConversation: getCursorConversation,
169
+ listConversationsForPath: listCursorConversationsForPath,
170
+ source: 'cursor',
171
+ };
@@ -0,0 +1,311 @@
1
+ import { antigravityConversationAdapter } from './antigravity-adapter';
2
+ import { claudeCodeConversationAdapter } from './claude-code-adapter';
3
+ import { codexConversationAdapter } from './codex-adapter';
4
+ import { cursorConversationAdapter } from './cursor-adapter';
5
+ import { kiroConversationAdapter } from './kiro-adapter';
6
+ import { selectConversationMessages } from './message-selector';
7
+ import { opencodeConversationAdapter } from './opencode-adapter';
8
+ import { qoderConversationAdapter } from './qoder-adapter';
9
+ import {
10
+ CONVERSATION_SOURCES,
11
+ type ConversationAdapter,
12
+ type ConversationMessage,
13
+ type ConversationMessageSelector,
14
+ type ConversationPage,
15
+ type ConversationSource,
16
+ type ConversationSourceInfo,
17
+ type GetConversationOptions,
18
+ type ListConversationsForPathOptions,
19
+ type ResolvedConversationRef,
20
+ } from './types';
21
+
22
+ export { selectConversationMessages } from './message-selector';
23
+ export { getConversationPathMatch, normalizeConversationPath } from './path-match';
24
+ export {
25
+ CONVERSATION_SOURCES,
26
+ type ConversationAdapter,
27
+ type ConversationDataLocations,
28
+ type ConversationDeepLinks,
29
+ type ConversationDetail,
30
+ type ConversationMessage,
31
+ type ConversationMessagePhase,
32
+ type ConversationMessageRole,
33
+ type ConversationMessageSelector,
34
+ type ConversationPage,
35
+ type ConversationPathMatch,
36
+ type ConversationSource,
37
+ type ConversationSourceInfo,
38
+ type GetConversationOptions,
39
+ type ListConversationsForPathOptions,
40
+ type ResolvedConversationRef,
41
+ } from './types';
42
+
43
+ const SOURCE_LABELS: Record<ConversationSource, string> = {
44
+ antigravity: 'Antigravity',
45
+ 'claude-code': 'Claude Code',
46
+ codex: 'Codex',
47
+ cursor: 'Cursor',
48
+ kiro: 'Kiro',
49
+ opencode: 'OpenCode',
50
+ qoder: 'Qoder',
51
+ };
52
+
53
+ const SOURCE_INFOS: ConversationSourceInfo[] = CONVERSATION_SOURCES.map((source) => ({
54
+ label: SOURCE_LABELS[source],
55
+ source,
56
+ }));
57
+
58
+ export const isConversationSource = (value: unknown): value is ConversationSource => {
59
+ return typeof value === 'string' && (CONVERSATION_SOURCES as readonly string[]).includes(value);
60
+ };
61
+
62
+ const ADAPTERS: Partial<Record<ConversationSource, ConversationAdapter>> = {
63
+ antigravity: antigravityConversationAdapter,
64
+ 'claude-code': claudeCodeConversationAdapter,
65
+ codex: codexConversationAdapter,
66
+ cursor: cursorConversationAdapter,
67
+ kiro: kiroConversationAdapter,
68
+ opencode: opencodeConversationAdapter,
69
+ qoder: qoderConversationAdapter,
70
+ };
71
+
72
+ const MAX_LIMIT = 200;
73
+ const DEFAULT_LIMIT = 100;
74
+
75
+ const getEnabledSources = (sources: ListConversationsForPathOptions['sources']): ConversationSource[] => {
76
+ if (!sources || sources === 'all') {
77
+ return SOURCE_INFOS.map((sourceInfo) => sourceInfo.source);
78
+ }
79
+
80
+ return sources;
81
+ };
82
+
83
+ const isAllSourcesRequest = (sources: ListConversationsForPathOptions['sources']) => !sources || sources === 'all';
84
+
85
+ const getAdapter = (source: ConversationSource): ConversationAdapter | null => {
86
+ return ADAPTERS[source] ?? null;
87
+ };
88
+
89
+ const decodeCursor = (cursor: string | null | undefined) => {
90
+ if (!cursor) {
91
+ return 0;
92
+ }
93
+
94
+ const parsed = Number.parseInt(Buffer.from(cursor, 'base64url').toString('utf8'), 10);
95
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
96
+ };
97
+
98
+ const encodeCursor = (offset: number) => Buffer.from(String(offset), 'utf8').toString('base64url');
99
+
100
+ const getLimit = (limit: number | undefined) => {
101
+ if (!limit || limit <= 0) {
102
+ return DEFAULT_LIMIT;
103
+ }
104
+
105
+ return Math.min(limit, MAX_LIMIT);
106
+ };
107
+
108
+ const filterByUpdatedAt = (
109
+ conversations: Awaited<ReturnType<ConversationAdapter['listConversationsForPath']>>,
110
+ options: Pick<ListConversationsForPathOptions, 'updatedAfterMs' | 'updatedBeforeMs'>,
111
+ ) => {
112
+ return conversations.filter((conversation) => {
113
+ const updatedAtMs = conversation.updatedAtMs ?? 0;
114
+ if (options.updatedAfterMs !== undefined && updatedAtMs < options.updatedAfterMs) {
115
+ return false;
116
+ }
117
+ if (options.updatedBeforeMs !== undefined && updatedAtMs > options.updatedBeforeMs) {
118
+ return false;
119
+ }
120
+ return true;
121
+ });
122
+ };
123
+
124
+ const sortConversations = (conversations: Awaited<ReturnType<ConversationAdapter['listConversationsForPath']>>) => {
125
+ return [...conversations].sort(
126
+ (left, right) =>
127
+ (right.updatedAtMs ?? 0) - (left.updatedAtMs ?? 0) ||
128
+ left.source.localeCompare(right.source) ||
129
+ left.id.localeCompare(right.id),
130
+ );
131
+ };
132
+
133
+ export const listConversationSources = async (): Promise<ConversationSourceInfo[]> => SOURCE_INFOS;
134
+
135
+ const listSourceConversationsForPath = async (
136
+ source: ConversationSource,
137
+ options: ListConversationsForPathOptions,
138
+ ignoreSourceFailures: boolean,
139
+ ) => {
140
+ const adapter = getAdapter(source);
141
+ if (!adapter) {
142
+ return [];
143
+ }
144
+
145
+ try {
146
+ return await adapter.listConversationsForPath(options);
147
+ } catch (error) {
148
+ if (!ignoreSourceFailures) {
149
+ throw error;
150
+ }
151
+ return [];
152
+ }
153
+ };
154
+
155
+ export const listConversationsForPath = async (options: ListConversationsForPathOptions): Promise<ConversationPage> => {
156
+ const ignoreSourceFailures = isAllSourcesRequest(options.sources);
157
+ const conversations = (
158
+ await Promise.all(
159
+ getEnabledSources(options.sources).map((source) =>
160
+ listSourceConversationsForPath(source, options, ignoreSourceFailures),
161
+ ),
162
+ )
163
+ ).flat();
164
+
165
+ const sorted = sortConversations(filterByUpdatedAt(conversations, options));
166
+ const offset = decodeCursor(options.cursor);
167
+ const limit = getLimit(options.limit);
168
+ const data = sorted.slice(offset, offset + limit);
169
+ const nextOffset = offset + data.length;
170
+
171
+ return {
172
+ data,
173
+ meta: {
174
+ hasNext: nextOffset < sorted.length,
175
+ nextCursor: nextOffset < sorted.length ? encodeCursor(nextOffset) : null,
176
+ },
177
+ };
178
+ };
179
+
180
+ export const getConversation = async (options: GetConversationOptions) => {
181
+ return getAdapter(options.source)?.getConversation(options) ?? null;
182
+ };
183
+
184
+ const sourceFromSessionRoute = (segment: string): ConversationSource | null => {
185
+ if (segment === 'claude-code-sessions') {
186
+ return 'claude-code';
187
+ }
188
+ if (segment === 'kiro-sessions') {
189
+ return 'kiro';
190
+ }
191
+ if (segment === 'qoder-sessions') {
192
+ return 'qoder';
193
+ }
194
+ if (segment === 'cursor-threads') {
195
+ return 'cursor';
196
+ }
197
+ if (segment === 'antigravity-conversations') {
198
+ return 'antigravity';
199
+ }
200
+ if (segment === 'opencode-sessions') {
201
+ return 'opencode';
202
+ }
203
+ return null;
204
+ };
205
+
206
+ const decodeRefId = (value: string | undefined): string | null => {
207
+ if (!value) {
208
+ return null;
209
+ }
210
+
211
+ try {
212
+ const decoded = decodeURIComponent(value);
213
+ return decoded.trim() ? decoded : null;
214
+ } catch {
215
+ return null;
216
+ }
217
+ };
218
+
219
+ const refFromPathSegmentAt = (segments: string[], index: number): ResolvedConversationRef | null => {
220
+ const segment = segments[index];
221
+ const next = segments[index + 1];
222
+ const nextNext = segments[index + 2];
223
+
224
+ if (segment === 'threads') {
225
+ const id = decodeRefId(next);
226
+ return id ? { id, source: 'codex' } : null;
227
+ }
228
+
229
+ if (segment === 'conversations' && isConversationSource(next)) {
230
+ const id = decodeRefId(nextNext);
231
+ return id ? { id, source: next } : null;
232
+ }
233
+
234
+ const source = segment ? sourceFromSessionRoute(segment) : null;
235
+ if (!source) {
236
+ return null;
237
+ }
238
+
239
+ const id = decodeRefId(next);
240
+ return id ? { id, source } : null;
241
+ };
242
+
243
+ const refFromPathSegments = (segments: string[]): ResolvedConversationRef | null => {
244
+ for (let index = 0; index < segments.length - 1; index += 1) {
245
+ const ref = refFromPathSegmentAt(segments, index);
246
+ if (ref) {
247
+ return ref;
248
+ }
249
+ }
250
+
251
+ return null;
252
+ };
253
+
254
+ const parseUrlRef = (ref: string): ResolvedConversationRef | null => {
255
+ let url: URL;
256
+ try {
257
+ url = new URL(ref);
258
+ } catch {
259
+ return null;
260
+ }
261
+
262
+ if (url.protocol === 'codex:' && url.hostname === 'threads') {
263
+ const id = decodeRefId(url.pathname.replace(/^\/+/u, ''));
264
+ return id ? { id, source: 'codex' } : null;
265
+ }
266
+
267
+ if (url.protocol === 'spiracha:' && url.hostname === 'conversation') {
268
+ const [source, id, extra] = url.pathname.split('/').filter(Boolean);
269
+ const decodedId = decodeRefId(id);
270
+ return isConversationSource(source) && decodedId && !extra ? { id: decodedId, source } : null;
271
+ }
272
+
273
+ return refFromPathSegments(url.pathname.split('/').filter(Boolean));
274
+ };
275
+
276
+ export const resolveConversationRef = async (ref: string): Promise<ResolvedConversationRef | null> => {
277
+ const trimmed = ref.trim();
278
+ if (!trimmed) {
279
+ return null;
280
+ }
281
+
282
+ return parseUrlRef(trimmed);
283
+ };
284
+
285
+ export const renderConversationMarkdown = (
286
+ conversation: {
287
+ messages: ConversationMessage[];
288
+ title: string | null;
289
+ },
290
+ options: { messageSelector?: ConversationMessageSelector } = {},
291
+ ) => {
292
+ const selectedMessages = options.messageSelector
293
+ ? selectConversationMessages(conversation.messages, options.messageSelector)
294
+ : conversation.messages;
295
+ const title = conversation.title?.trim() || 'Conversation';
296
+ const roleLabels: Record<ConversationMessage['role'], string> = {
297
+ assistant: 'Assistant',
298
+ system: 'System',
299
+ tool: 'Tool',
300
+ unknown: 'Unknown',
301
+ user: 'User',
302
+ };
303
+ const sections = selectedMessages.map((message) => {
304
+ const text = message.text.trim() || '_No message content._';
305
+ return `## ${roleLabels[message.role]}\n\n${text}`;
306
+ });
307
+ if (sections.length === 0) {
308
+ sections.push('_No messages selected._');
309
+ }
310
+ return [`# ${title}`, ...sections].join('\n\n').trimEnd() + '\n';
311
+ };
@@ -0,0 +1,140 @@
1
+ import { listKiroSessionsForGroup, listKiroWorkspaceGroups, readKiroSessionTranscript } from '../kiro-db';
2
+ import type {
3
+ KiroSessionSummary,
4
+ KiroSessionTranscript,
5
+ KiroTranscriptEntry,
6
+ KiroTranscriptPart,
7
+ } from '../kiro-exporter-types';
8
+ import { resolveKiroWorkspaceSessionsDir } from '../kiro-exporter-types';
9
+ import { getFinalKiroAssistantMessageEntryIds, getKiroMessagePhase } from '../kiro-transcript-phase';
10
+ import { cleanInlineTitle } from '../shared';
11
+ import {
12
+ createDeepLinks,
13
+ createTextMessage,
14
+ finalizeMessages,
15
+ normalizeAssistantPhase,
16
+ normalizeRole,
17
+ toDateMs,
18
+ } from './adapter-helpers';
19
+ import { selectConversationMessages } from './message-selector';
20
+ import { getConversationPathMatch } from './path-match';
21
+ import type {
22
+ ConversationAdapter,
23
+ ConversationDetail,
24
+ ConversationMessage,
25
+ ConversationPathMatch,
26
+ GetConversationOptions,
27
+ ListConversationsForPathOptions,
28
+ } from './types';
29
+
30
+ const getSessionsDir = (options: { locations?: { kiroWorkspaceSessionsDir?: string } }) =>
31
+ options.locations?.kiroWorkspaceSessionsDir ?? resolveKiroWorkspaceSessionsDir();
32
+
33
+ const partToMessages = (
34
+ entry: KiroTranscriptEntry,
35
+ part: KiroTranscriptPart,
36
+ partIndex: number,
37
+ finalEntryIds: Set<string>,
38
+ ): ConversationMessage[] => {
39
+ const createdAtMs = toDateMs(entry.timestamp);
40
+ if (entry.entryType === 'tool_call') {
41
+ return createTextMessage({
42
+ createdAtMs,
43
+ id: `${entry.entryId}:${partIndex}`,
44
+ metadata: { executionId: entry.executionId },
45
+ order: partIndex,
46
+ phase: 'tool_call',
47
+ role: 'tool',
48
+ text: part.text,
49
+ });
50
+ }
51
+
52
+ return createTextMessage({
53
+ createdAtMs,
54
+ id: `${entry.entryId}:${partIndex}`,
55
+ metadata: part.imageUrl ? { imageUrl: part.imageUrl } : {},
56
+ order: partIndex,
57
+ phase: normalizeAssistantPhase(getKiroMessagePhase(entry, finalEntryIds), 'unknown'),
58
+ role: normalizeRole(entry.role),
59
+ text: part.text ?? part.imageUrl,
60
+ });
61
+ };
62
+
63
+ const transcriptToMessages = (transcript: KiroSessionTranscript) => {
64
+ const finalEntryIds = getFinalKiroAssistantMessageEntryIds(transcript.entries);
65
+ return finalizeMessages(
66
+ transcript.entries.flatMap((entry) =>
67
+ entry.parts.flatMap((part, partIndex) => partToMessages(entry, part, partIndex, finalEntryIds)),
68
+ ),
69
+ );
70
+ };
71
+
72
+ const buildConversation = async (
73
+ session: KiroSessionSummary,
74
+ sessionsDir: string,
75
+ matches: ConversationPathMatch[],
76
+ options: Pick<ListConversationsForPathOptions, 'includeMessages' | 'messageSelector'>,
77
+ ): Promise<ConversationDetail> => {
78
+ const transcript = options.includeMessages ? await readKiroSessionTranscript(sessionsDir, session.sessionId) : null;
79
+ const allMessages = transcript ? transcriptToMessages(transcript) : [];
80
+ const messages = options.includeMessages
81
+ ? selectConversationMessages(allMessages, options.messageSelector ?? 'last_final_answer')
82
+ : [];
83
+
84
+ return {
85
+ createdAtMs: session.createdAtMs,
86
+ deepLinks: createDeepLinks('kiro', session.sessionId, `/kiro-sessions/${session.sessionId}`),
87
+ id: session.sessionId,
88
+ matches,
89
+ messageCount: options.includeMessages ? allMessages.length : session.messageCount,
90
+ messages,
91
+ metadata: {
92
+ defaultModelTitle: session.defaultModelTitle,
93
+ filePath: session.filePath,
94
+ model: session.selectedModel ?? session.defaultModelTitle,
95
+ selectedModel: session.selectedModel,
96
+ sessionType: session.sessionType,
97
+ },
98
+ source: 'kiro',
99
+ title: cleanInlineTitle(session.title),
100
+ updatedAtMs: session.lastActiveAtMs,
101
+ workspaceKey: session.workspaceKey,
102
+ workspacePath: session.worktree,
103
+ };
104
+ };
105
+
106
+ const listKiroConversationsForPath = async (options: ListConversationsForPathOptions) => {
107
+ const sessionsDir = getSessionsDir(options);
108
+ const groups = await listKiroWorkspaceGroups(sessionsDir);
109
+ const conversations: ConversationDetail[] = [];
110
+
111
+ for (const group of groups) {
112
+ const match = await getConversationPathMatch(options.cwd, group.worktree);
113
+ if (!match) {
114
+ continue;
115
+ }
116
+ const sessions = await listKiroSessionsForGroup(group.key, sessionsDir);
117
+ for (const session of sessions) {
118
+ conversations.push(await buildConversation(session, sessionsDir, [match], options));
119
+ }
120
+ }
121
+
122
+ return conversations;
123
+ };
124
+
125
+ const getKiroConversation = async (options: GetConversationOptions): Promise<ConversationDetail | null> => {
126
+ const sessionsDir = getSessionsDir(options);
127
+ const transcript = await readKiroSessionTranscript(sessionsDir, options.id);
128
+ return transcript
129
+ ? buildConversation(transcript.session, sessionsDir, [], {
130
+ includeMessages: true,
131
+ messageSelector: options.messageSelector ?? 'all',
132
+ })
133
+ : null;
134
+ };
135
+
136
+ export const kiroConversationAdapter: ConversationAdapter = {
137
+ getConversation: getKiroConversation,
138
+ listConversationsForPath: listKiroConversationsForPath,
139
+ source: 'kiro',
140
+ };
@@ -0,0 +1,37 @@
1
+ import type { ConversationMessage, ConversationMessageSelector } from './types';
2
+
3
+ const latestByOrder = (messages: ConversationMessage[]) => {
4
+ let latest: ConversationMessage | null = null;
5
+ for (const message of messages) {
6
+ if (!latest || message.order > latest.order) {
7
+ latest = message;
8
+ }
9
+ }
10
+ return latest;
11
+ };
12
+
13
+ const selectLastAssistantMessage = (messages: ConversationMessage[]) => {
14
+ return latestByOrder(messages.filter((message) => message.role === 'assistant'));
15
+ };
16
+
17
+ // Not every integration can identify final answers. In that case, API list/export defaults still
18
+ // return the latest assistant message instead of silently omitting the conversation.
19
+ const selectLastFinalAnswer = (messages: ConversationMessage[]) => {
20
+ return (
21
+ latestByOrder(messages.filter((message) => message.role === 'assistant' && message.phase === 'final_answer')) ??
22
+ selectLastAssistantMessage(messages)
23
+ );
24
+ };
25
+
26
+ export const selectConversationMessages = (
27
+ messages: ConversationMessage[],
28
+ selector: ConversationMessageSelector,
29
+ ): ConversationMessage[] => {
30
+ if (selector === 'all') {
31
+ return messages;
32
+ }
33
+
34
+ const selected =
35
+ selector === 'last_assistant' ? selectLastAssistantMessage(messages) : selectLastFinalAnswer(messages);
36
+ return selected ? [selected] : [];
37
+ };