spiracha 1.4.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 (319) hide show
  1. package/AGENTS.md +116 -164
  2. package/README.md +92 -237
  3. package/apps/ui/AGENTS.md +36 -7
  4. package/apps/ui/README.md +79 -6
  5. package/apps/ui/components.json +21 -0
  6. package/apps/ui/package.json +65 -0
  7. package/apps/ui/{dist/client → public}/icon.svg +2 -1
  8. package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
  9. package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
  10. package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
  11. package/apps/ui/src/components/app-shell.tsx +124 -0
  12. package/apps/ui/src/components/breadcrumbs.tsx +53 -0
  13. package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
  14. package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
  15. package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
  16. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  17. package/apps/ui/src/components/data-table.tsx +241 -0
  18. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  19. package/apps/ui/src/components/export-dialog.tsx +184 -0
  20. package/apps/ui/src/components/json-panel.tsx +17 -0
  21. package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
  22. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  23. package/apps/ui/src/components/list-search-input.tsx +18 -0
  24. package/apps/ui/src/components/loading-panel.tsx +23 -0
  25. package/apps/ui/src/components/metadata-section.tsx +31 -0
  26. package/apps/ui/src/components/metric-card.tsx +19 -0
  27. package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
  28. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  29. package/apps/ui/src/components/page-header.tsx +33 -0
  30. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  31. package/apps/ui/src/components/projects-table.tsx +108 -0
  32. package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
  33. package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
  34. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  35. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  36. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  37. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  38. package/apps/ui/src/components/threads-table.tsx +202 -0
  39. package/apps/ui/src/components/transcript-view.tsx +552 -0
  40. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  41. package/apps/ui/src/components/ui/badge.tsx +45 -0
  42. package/apps/ui/src/components/ui/button.tsx +62 -0
  43. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  44. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  45. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  46. package/apps/ui/src/components/ui/input.tsx +21 -0
  47. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  48. package/apps/ui/src/components/ui/select.tsx +163 -0
  49. package/apps/ui/src/components/ui/separator.tsx +28 -0
  50. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  51. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  52. package/apps/ui/src/components/ui/table.tsx +76 -0
  53. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  54. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  55. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  56. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  57. package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
  58. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  59. package/apps/ui/src/lib/antigravity-server.ts +128 -0
  60. package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
  61. package/apps/ui/src/lib/claude-code-queries.ts +26 -0
  62. package/apps/ui/src/lib/claude-code-server.ts +75 -0
  63. package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
  64. package/apps/ui/src/lib/codex-queries.ts +66 -0
  65. package/apps/ui/src/lib/codex-server.ts +209 -0
  66. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  67. package/apps/ui/src/lib/cursor-server.ts +272 -0
  68. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  69. package/apps/ui/src/lib/download.ts +166 -0
  70. package/apps/ui/src/lib/formatters.ts +149 -0
  71. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  72. package/apps/ui/src/lib/kiro-server.ts +73 -0
  73. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  74. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  75. package/apps/ui/src/lib/opencode-server.ts +73 -0
  76. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  77. package/apps/ui/src/lib/package-metadata.ts +16 -0
  78. package/apps/ui/src/lib/path-utils.ts +13 -0
  79. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  80. package/apps/ui/src/lib/qoder-server.ts +79 -0
  81. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  82. package/apps/ui/src/lib/route-search.ts +68 -0
  83. package/apps/ui/src/lib/settings-store.tsx +59 -0
  84. package/apps/ui/src/lib/source-session-export-server.ts +56 -0
  85. package/apps/ui/src/lib/text-filter.ts +22 -0
  86. package/apps/ui/src/lib/thread-id.ts +3 -0
  87. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  88. package/apps/ui/src/lib/utils.ts +7 -0
  89. package/apps/ui/src/routeTree.gen.ts +730 -0
  90. package/apps/ui/src/router.tsx +20 -0
  91. package/apps/ui/src/routes/$threadId.tsx +22 -0
  92. package/apps/ui/src/routes/__root.tsx +127 -0
  93. package/apps/ui/src/routes/analytics.tsx +143 -0
  94. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
  95. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
  96. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  97. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  98. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  99. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
  100. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  101. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  102. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  103. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
  104. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
  105. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  106. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  107. package/apps/ui/src/routes/codex.index.tsx +116 -0
  108. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  109. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
  110. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  111. package/apps/ui/src/routes/index.tsx +124 -0
  112. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
  113. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
  114. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  115. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
  116. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
  117. package/apps/ui/src/routes/opencode.index.tsx +48 -0
  118. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
  119. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
  120. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  121. package/apps/ui/src/routes/settings.tsx +91 -0
  122. package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
  123. package/apps/ui/src/styles.css +122 -0
  124. package/apps/ui/tsconfig.json +29 -0
  125. package/apps/ui/vite.config.ts +84 -0
  126. package/bin/spiracha.ts +32 -0
  127. package/package.json +50 -60
  128. package/src/client.ts +290 -0
  129. package/src/lib/antigravity-db.ts +213 -19
  130. package/src/lib/antigravity-exporter-types.ts +1 -0
  131. package/src/lib/claude-code-db.ts +655 -0
  132. package/src/lib/claude-code-exporter-types.ts +110 -0
  133. package/src/lib/claude-code-transcript-phase.ts +25 -0
  134. package/src/lib/claude-code-transcript.ts +180 -0
  135. package/src/lib/codex-analytics.ts +1 -1
  136. package/src/lib/codex-browser-db.ts +856 -61
  137. package/src/lib/codex-browser-export.ts +24 -16
  138. package/src/lib/codex-browser-types.ts +1 -1
  139. package/src/lib/codex-thread-cache.ts +17 -1
  140. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  141. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
  142. package/src/lib/concurrency.ts +25 -0
  143. package/src/lib/conversation-api.ts +792 -0
  144. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  145. package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
  146. package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
  147. package/src/lib/conversation-data/codex-adapter.ts +245 -0
  148. package/src/lib/conversation-data/cursor-adapter.ts +171 -0
  149. package/src/lib/conversation-data/index.ts +311 -0
  150. package/src/lib/conversation-data/kiro-adapter.ts +140 -0
  151. package/src/lib/conversation-data/message-selector.ts +37 -0
  152. package/src/lib/conversation-data/opencode-adapter.ts +180 -0
  153. package/src/lib/conversation-data/path-match.ts +79 -0
  154. package/src/lib/conversation-data/qoder-adapter.ts +230 -0
  155. package/src/lib/conversation-data/types.ts +116 -0
  156. package/src/lib/cursor-db.ts +67 -21
  157. package/src/lib/cursor-exporter-types.ts +0 -19
  158. package/src/lib/kiro-db.ts +949 -0
  159. package/src/lib/kiro-exporter-types.ts +100 -0
  160. package/src/lib/kiro-transcript-phase.ts +41 -0
  161. package/src/lib/kiro-transcript.ts +115 -0
  162. package/src/lib/opencode-db.ts +610 -0
  163. package/src/lib/opencode-exporter-types.ts +129 -0
  164. package/src/lib/opencode-think-tags.ts +126 -0
  165. package/src/lib/opencode-transcript-phase.ts +50 -0
  166. package/src/lib/opencode-transcript.ts +194 -0
  167. package/src/lib/qoder-acp-client.ts +268 -0
  168. package/src/lib/qoder-db.ts +1601 -0
  169. package/src/lib/qoder-exporter-types.ts +114 -0
  170. package/src/lib/qoder-transcript-phase.ts +41 -0
  171. package/src/lib/qoder-transcript.ts +112 -0
  172. package/src/lib/shared.ts +25 -0
  173. package/src/lib/ui-export-archive.ts +61 -0
  174. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  175. package/apps/ui/dist/client/assets/analytics-DK2jdvmI.js +0 -1
  176. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DmD0sN8X.js +0 -7
  177. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  178. package/apps/ui/dist/client/assets/antigravity-keychain-panel-BRHVjFAM.js +0 -1
  179. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  180. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CGV7amas.js +0 -1
  181. package/apps/ui/dist/client/assets/antigravity.index-DEEkjlZa.js +0 -1
  182. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  183. package/apps/ui/dist/client/assets/badge-DfWvPfKF.js +0 -1
  184. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  185. package/apps/ui/dist/client/assets/createServerFn-CgRRVpBH.js +0 -3
  186. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  187. package/apps/ui/dist/client/assets/cursor-threads._composerId-Ti2gTm63.js +0 -1
  188. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  189. package/apps/ui/dist/client/assets/cursor._workspaceKey-DzD_c6j9.js +0 -1
  190. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  191. package/apps/ui/dist/client/assets/cursor.index-PkCFVcl4.js +0 -2
  192. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  193. package/apps/ui/dist/client/assets/delete-confirm-dialog-BHRmyLHr.js +0 -11
  194. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  195. package/apps/ui/dist/client/assets/dist-BhZOxAPP.js +0 -1
  196. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  197. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  198. package/apps/ui/dist/client/assets/dropdown-menu-Dy5cLzTy.js +0 -1
  199. package/apps/ui/dist/client/assets/es2015-BkCWttyM.js +0 -41
  200. package/apps/ui/dist/client/assets/export-dialog-C86SYHJn.js +0 -1
  201. package/apps/ui/dist/client/assets/formatters-BM9kB7ed.js +0 -1
  202. package/apps/ui/dist/client/assets/index-CPvAP-jk.js +0 -149
  203. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  204. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  205. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  206. package/apps/ui/dist/client/assets/projects._project-Ckf6muWZ.js +0 -1
  207. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  208. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  209. package/apps/ui/dist/client/assets/projects.index-V5ibNZII.js +0 -3
  210. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  211. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  212. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  213. package/apps/ui/dist/client/assets/routes-Df2tgpyd.js +0 -1
  214. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  215. package/apps/ui/dist/client/assets/select-Bf2GwfPR.js +0 -1
  216. package/apps/ui/dist/client/assets/settings-5VQIzME0.js +0 -1
  217. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  218. package/apps/ui/dist/client/assets/styles-BhRkXgwB.css +0 -1
  219. package/apps/ui/dist/client/assets/tabs-OO4VU5KR.js +0 -7
  220. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  221. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  222. package/apps/ui/dist/client/assets/threads._threadId-BgDhFj2I.js +0 -1
  223. package/apps/ui/dist/client/assets/useMutation-CC_B7uy5.js +0 -1
  224. package/apps/ui/dist/client/assets/useQuery-C5By6WKU.js +0 -1
  225. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CRW2kqj0.js +0 -232
  226. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  227. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  228. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  229. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  230. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BtexWY-K.js +0 -613
  231. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  232. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-zMrR6v6R.js +0 -20
  233. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  234. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  235. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  236. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  237. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  238. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  239. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  240. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  241. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  242. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  243. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  244. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  245. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  246. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  247. package/apps/ui/dist/server/assets/codex-server-CNXSJuc2.js +0 -2004
  248. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  249. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  250. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  251. package/apps/ui/dist/server/assets/cursor-db-cYZEU3WQ.js +0 -830
  252. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  253. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  254. package/apps/ui/dist/server/assets/cursor-recovery-nq-kR62j.js +0 -361
  255. package/apps/ui/dist/server/assets/cursor-server-C3q7hrp-.js +0 -213
  256. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  257. package/apps/ui/dist/server/assets/cursor-threads._composerId-BPJFWfJj.js +0 -18
  258. package/apps/ui/dist/server/assets/cursor-threads._composerId-BkMMTQ3v.js +0 -582
  259. package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +0 -125
  260. package/apps/ui/dist/server/assets/cursor._workspaceKey-C5PdTaKB.js +0 -28
  261. package/apps/ui/dist/server/assets/cursor._workspaceKey-Ckj7apKI.js +0 -401
  262. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  263. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  264. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  265. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  266. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  267. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  268. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  269. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  270. package/apps/ui/dist/server/assets/export-dialog-BItjWgkZ.js +0 -240
  271. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  272. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  273. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  274. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  275. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  276. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  277. package/apps/ui/dist/server/assets/projects._project-Bpbc3C-L.js +0 -20
  278. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  279. package/apps/ui/dist/server/assets/projects._project-CvZho6EQ.js +0 -395
  280. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  281. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  282. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  283. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  284. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  285. package/apps/ui/dist/server/assets/router-6m-ihwqA.js +0 -410
  286. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  287. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  288. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  289. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  290. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  291. package/apps/ui/dist/server/assets/shared-CPRNYIql.js +0 -134
  292. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  293. package/apps/ui/dist/server/assets/start-CI_0bSiY.js +0 -4
  294. package/apps/ui/dist/server/assets/tabs-CGA13IZM.js +0 -502
  295. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  296. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  297. package/apps/ui/dist/server/assets/threads._threadId-Bs0yuqO9.js +0 -18
  298. package/apps/ui/dist/server/assets/threads._threadId-D2GYkVn6.js +0 -639
  299. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  300. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  301. package/apps/ui/dist/server/server.js +0 -5847
  302. package/bin/codex-chats-claude.js +0 -5
  303. package/bin/codex-chats.js +0 -5
  304. package/bin/spiracha.js +0 -5
  305. package/src/export-chats.ts +0 -120
  306. package/src/export-claude.ts +0 -36
  307. package/src/export-cursor.ts +0 -244
  308. package/src/lib/claude-exporter.ts +0 -864
  309. package/src/lib/codex-exporter-cli.ts +0 -277
  310. package/src/lib/codex-exporter-db.ts +0 -319
  311. package/src/lib/codex-exporter.ts +0 -115
  312. package/src/lib/cursor-exporter.ts +0 -266
  313. package/src/lib/interactive-cli.ts +0 -433
  314. package/src/lib/native-open.ts +0 -54
  315. package/src/mcp-server.ts +0 -137
  316. package/src/spiracha.ts +0 -116
  317. package/src/ui-cli.ts +0 -310
  318. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  319. /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
+ };