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,149 @@
1
+ import { formatModelLabel as formatSharedModelLabel } from '@spiracha/lib/model-label';
2
+
3
+ type DateTimeFormatOptions = {
4
+ now?: Date;
5
+ timeZone?: string;
6
+ };
7
+
8
+ type DateTimeFormatterSet = {
9
+ dayKeyFormatter: Intl.DateTimeFormat;
10
+ timePartsFormatter: Intl.DateTimeFormat;
11
+ };
12
+
13
+ const DATE_TIME_FORMATTERS = new Map<string, DateTimeFormatterSet>();
14
+ const DEFAULT_TIME_ZONE = 'UTC';
15
+
16
+ const getDateTimeFormatters = (timeZone?: string): DateTimeFormatterSet => {
17
+ const resolvedTimeZone = timeZone ?? DEFAULT_TIME_ZONE;
18
+ const cacheKey = resolvedTimeZone;
19
+ const cached = DATE_TIME_FORMATTERS.get(cacheKey);
20
+ if (cached) {
21
+ return cached;
22
+ }
23
+
24
+ const created = {
25
+ dayKeyFormatter: new Intl.DateTimeFormat('en-CA', {
26
+ day: '2-digit',
27
+ month: '2-digit',
28
+ timeZone: resolvedTimeZone,
29
+ year: 'numeric',
30
+ }),
31
+ timePartsFormatter: new Intl.DateTimeFormat('en-US', {
32
+ day: 'numeric',
33
+ hour: 'numeric',
34
+ hour12: true,
35
+ minute: '2-digit',
36
+ month: 'short',
37
+ timeZone: resolvedTimeZone,
38
+ year: 'numeric',
39
+ }),
40
+ };
41
+
42
+ DATE_TIME_FORMATTERS.set(cacheKey, created);
43
+ return created;
44
+ };
45
+
46
+ const buildDayKey = (date: Date, timeZone?: string) => {
47
+ return getDateTimeFormatters(timeZone).dayKeyFormatter.format(date);
48
+ };
49
+
50
+ const formatTimeParts = (date: Date, timeZone?: string) => {
51
+ const parts = getDateTimeFormatters(timeZone).timePartsFormatter.formatToParts(date);
52
+ const partMap = new Map(parts.map((part) => [part.type, part.value]));
53
+ const hour = partMap.get('hour');
54
+ const minute = partMap.get('minute');
55
+ const dayPeriod = partMap.get('dayPeriod');
56
+
57
+ if (!hour || !minute || !dayPeriod) {
58
+ return null;
59
+ }
60
+
61
+ return {
62
+ day: partMap.get('day') ?? '',
63
+ month: partMap.get('month') ?? '',
64
+ time: `${hour}:${minute} ${dayPeriod}`.trim(),
65
+ year: partMap.get('year') ?? '',
66
+ };
67
+ };
68
+
69
+ export const formatNumber = (value: number) => {
70
+ return new Intl.NumberFormat('en-US').format(value);
71
+ };
72
+
73
+ export const formatTokens = (value: number) => {
74
+ return `${formatNumber(value)} tokens`;
75
+ };
76
+
77
+ export const formatBytes = (value: number | null | undefined) => {
78
+ if (!value || value <= 0) {
79
+ return '0 B';
80
+ }
81
+
82
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
83
+ let size = value;
84
+ let unitIndex = 0;
85
+
86
+ while (size >= 1024 && unitIndex < units.length - 1) {
87
+ size /= 1024;
88
+ unitIndex += 1;
89
+ }
90
+
91
+ const fractionDigits = size >= 100 || unitIndex === 0 ? 0 : 1;
92
+ return `${size.toFixed(fractionDigits)} ${units[unitIndex]}`;
93
+ };
94
+
95
+ export const formatDateTime = (
96
+ value: number | string | null | undefined,
97
+ options: DateTimeFormatOptions = {},
98
+ ): string => {
99
+ if (value === null || value === undefined || value === '') {
100
+ return 'n/a';
101
+ }
102
+
103
+ const date = new Date(value);
104
+ if (Number.isNaN(date.getTime())) {
105
+ return 'n/a';
106
+ }
107
+
108
+ const now = options.now ?? new Date();
109
+ const parts = formatTimeParts(date, options.timeZone);
110
+ if (!parts) {
111
+ return 'n/a';
112
+ }
113
+
114
+ const { day, month, time, year } = parts;
115
+ const isToday = buildDayKey(date, options.timeZone) === buildDayKey(now, options.timeZone);
116
+
117
+ if (isToday) {
118
+ return time;
119
+ }
120
+
121
+ const currentYear = formatTimeParts(now, options.timeZone)?.year;
122
+ if (year && currentYear && year !== currentYear) {
123
+ return `${month} ${day}, ${year} · ${time}`;
124
+ }
125
+
126
+ return `${month} ${day} · ${time}`;
127
+ };
128
+
129
+ export const formatList = (values: string[]) => {
130
+ if (values.length === 0) {
131
+ return 'n/a';
132
+ }
133
+
134
+ return values.join(', ');
135
+ };
136
+
137
+ export const formatPercent = (value: number, total: number) => {
138
+ if (total <= 0) {
139
+ return '0%';
140
+ }
141
+
142
+ return `${Math.round((value / total) * 100)}%`;
143
+ };
144
+
145
+ export const formatBooleanLabel = (value: boolean) => {
146
+ return value ? 'Yes' : 'No';
147
+ };
148
+
149
+ export const formatModelLabel = formatSharedModelLabel;
@@ -0,0 +1,22 @@
1
+ import { queryOptions } from '@tanstack/react-query';
2
+ import { getKiroSessionDetailFn, listKiroSessionsFn, listKiroWorkspacesFn } from './kiro-server';
3
+
4
+ export const kiroWorkspacesQueryOptions = () =>
5
+ queryOptions({
6
+ queryFn: () => listKiroWorkspacesFn(),
7
+ queryKey: ['kiro-workspaces'],
8
+ });
9
+
10
+ export const kiroSessionsQueryOptions = (workspaceKey: string | null) =>
11
+ queryOptions({
12
+ enabled: workspaceKey !== null,
13
+ queryFn: () => listKiroSessionsFn({ data: { workspaceKey: workspaceKey ?? '' } }),
14
+ queryKey: ['kiro-sessions', workspaceKey ?? 'none'],
15
+ });
16
+
17
+ export const kiroSessionDetailQueryOptions = (sessionId: string | null) =>
18
+ queryOptions({
19
+ enabled: sessionId !== null,
20
+ queryFn: () => getKiroSessionDetailFn({ data: { sessionId: sessionId ?? '' } }),
21
+ queryKey: ['kiro-session', sessionId ?? 'none'],
22
+ });
@@ -0,0 +1,73 @@
1
+ import { createServerFn } from '@tanstack/react-start';
2
+ import { z } from 'zod';
3
+ import { renderSourceSessionDownload } from './source-session-export-server';
4
+
5
+ const workspaceSchema = z.object({
6
+ workspaceKey: z.string().min(1),
7
+ });
8
+
9
+ const sessionSchema = z.object({
10
+ sessionId: z.string().min(1),
11
+ });
12
+
13
+ const exportSessionSchema = z.object({
14
+ includeCommentary: z.boolean().default(true),
15
+ includeMetadata: z.boolean().default(true),
16
+ includeTools: z.boolean().default(true),
17
+ outputFormat: z.enum(['md', 'txt']).default('md'),
18
+ sessionId: z.string().min(1),
19
+ zipArchive: z.boolean().default(false),
20
+ });
21
+
22
+ export const listKiroWorkspacesFn = createServerFn({ method: 'GET' }).handler(async () => {
23
+ const { listKiroWorkspaceGroups } = await import('@spiracha/lib/kiro-db');
24
+ return listKiroWorkspaceGroups();
25
+ });
26
+
27
+ export const listKiroSessionsFn = createServerFn({ method: 'GET' })
28
+ .validator(workspaceSchema)
29
+ .handler(async ({ data }) => {
30
+ const { listKiroSessionsForGroup } = await import('@spiracha/lib/kiro-db');
31
+ return listKiroSessionsForGroup(data.workspaceKey);
32
+ });
33
+
34
+ const loadKiroSessionTranscript = async (sessionId: string) => {
35
+ const { readKiroSessionTranscript, resolveKiroWorkspaceSessionsDir } = await import('@spiracha/lib/kiro-db');
36
+ const transcript = await readKiroSessionTranscript(resolveKiroWorkspaceSessionsDir(), sessionId);
37
+ if (!transcript) {
38
+ throw new Error(`Kiro session not found: ${sessionId}`);
39
+ }
40
+
41
+ return transcript;
42
+ };
43
+
44
+ export const getKiroSessionDetailFn = createServerFn({ method: 'GET' })
45
+ .validator(sessionSchema)
46
+ .handler(async ({ data }) => {
47
+ return loadKiroSessionTranscript(data.sessionId);
48
+ });
49
+
50
+ export const exportKiroSessionFn = createServerFn({ method: 'POST' })
51
+ .validator(exportSessionSchema)
52
+ .handler(async ({ data }) => {
53
+ const { renderKiroTranscript } = await import('@spiracha/lib/kiro-transcript');
54
+ const transcript = await loadKiroSessionTranscript(data.sessionId);
55
+ const content = renderKiroTranscript(transcript, {
56
+ includeCommentary: data.includeCommentary,
57
+ includeMetadata: data.includeMetadata,
58
+ includeTools: data.includeTools,
59
+ outputFormat: data.outputFormat,
60
+ });
61
+
62
+ if (!content) {
63
+ throw new Error(`Kiro session has no exportable content: ${data.sessionId}`);
64
+ }
65
+
66
+ return renderSourceSessionDownload({
67
+ content,
68
+ fallbackBaseName: 'kiro-session',
69
+ fileBaseName: transcript.session.title || transcript.session.sessionId,
70
+ outputFormat: data.outputFormat,
71
+ zipArchive: data.zipArchive,
72
+ });
73
+ });
@@ -0,0 +1,113 @@
1
+ import type { ThreadEvent, ThreadTranscriptStats } from '@spiracha/lib/codex-browser-types';
2
+ import type { KiroSessionTranscript, KiroTranscriptEntry, KiroTranscriptPart } from '@spiracha/lib/kiro-exporter-types';
3
+ import { getFinalKiroAssistantMessageEntryIds, getKiroMessagePhase } from '@spiracha/lib/kiro-transcript-phase';
4
+ import type { JsonValue } from '@spiracha/lib/shared';
5
+ import { getThreadTranscriptStats } from './thread-transcript-stats';
6
+
7
+ const buildRaw = (
8
+ entry: KiroTranscriptEntry,
9
+ part: KiroTranscriptPart,
10
+ eventType: string,
11
+ ): Record<string, JsonValue> => ({
12
+ entryId: entry.entryId,
13
+ eventType,
14
+ executionId: entry.executionId,
15
+ role: entry.role,
16
+ source: 'kiro_workspace_sessions',
17
+ type: part.type,
18
+ ...part.raw,
19
+ });
20
+
21
+ const buildMessageEvent = (
22
+ transcript: KiroSessionTranscript,
23
+ entry: KiroTranscriptEntry,
24
+ part: KiroTranscriptPart,
25
+ sequence: number,
26
+ text: string,
27
+ phase: Extract<ThreadEvent, { kind: 'message' }>['phase'],
28
+ ): ThreadEvent => ({
29
+ isHiddenByDefault: entry.role !== 'assistant' && entry.role !== 'user',
30
+ kind: 'message',
31
+ memoryCitation: null,
32
+ model: transcript.session.selectedModel ?? transcript.session.defaultModelTitle,
33
+ phase,
34
+ raw: buildRaw(entry, part, 'message'),
35
+ role: entry.role,
36
+ sequence,
37
+ text,
38
+ timestamp: entry.timestamp,
39
+ variant: entry.role === 'user' ? 'user_message' : entry.role === 'assistant' ? 'agent_message' : 'message',
40
+ });
41
+
42
+ const getPartString = (part: KiroTranscriptPart, key: string): string | null => {
43
+ const value = part.raw[key];
44
+ return typeof value === 'string' && value.trim() ? value : null;
45
+ };
46
+
47
+ const buildToolCallEvent = (
48
+ transcript: KiroSessionTranscript,
49
+ entry: KiroTranscriptEntry,
50
+ part: KiroTranscriptPart,
51
+ sequence: number,
52
+ command: string,
53
+ ): ThreadEvent => ({
54
+ argumentsParseFailed: false,
55
+ argumentsText: command,
56
+ callId: entry.entryId,
57
+ command,
58
+ kind: 'tool_call',
59
+ name: getPartString(part, 'toolName') ?? 'kiro_action',
60
+ raw: buildRaw(entry, part, 'tool_call'),
61
+ sequence,
62
+ timestamp: entry.timestamp,
63
+ workdir: transcript.session.worktree,
64
+ });
65
+
66
+ const partToEvents = (
67
+ transcript: KiroSessionTranscript,
68
+ entry: KiroTranscriptEntry,
69
+ part: KiroTranscriptPart,
70
+ sequence: number,
71
+ finalAssistantMessageEntryIds: Set<string>,
72
+ ): ThreadEvent[] => {
73
+ if (entry.entryType === 'tool_call' && part.type === 'text') {
74
+ const command = part.text?.trim();
75
+ return command ? [buildToolCallEvent(transcript, entry, part, sequence, command)] : [];
76
+ }
77
+
78
+ if (part.type === 'text' || part.type === 'image') {
79
+ const text = part.text?.trim();
80
+ return text
81
+ ? [
82
+ buildMessageEvent(
83
+ transcript,
84
+ entry,
85
+ part,
86
+ sequence,
87
+ text,
88
+ getKiroMessagePhase(entry, finalAssistantMessageEntryIds),
89
+ ),
90
+ ]
91
+ : [];
92
+ }
93
+
94
+ return [];
95
+ };
96
+
97
+ export const kiroTranscriptToThreadEvents = (transcript: KiroSessionTranscript): ThreadEvent[] => {
98
+ const events: ThreadEvent[] = [];
99
+ const finalAssistantMessageEntryIds = getFinalKiroAssistantMessageEntryIds(transcript.entries);
100
+ let sequence = 0;
101
+
102
+ for (const entry of transcript.entries) {
103
+ for (const part of entry.parts) {
104
+ events.push(...partToEvents(transcript, entry, part, sequence, finalAssistantMessageEntryIds));
105
+ sequence += 1;
106
+ }
107
+ }
108
+ return events;
109
+ };
110
+
111
+ export const getKiroThreadTranscriptStats = (events: ThreadEvent[]): ThreadTranscriptStats => {
112
+ return getThreadTranscriptStats(events);
113
+ };
@@ -0,0 +1,37 @@
1
+ import { isRetryableSqliteError } from '@spiracha/lib/sqlite-error';
2
+ import { queryOptions } from '@tanstack/react-query';
3
+ import { getOpenCodeSessionDetailFn, listOpenCodeSessionsFn, listOpenCodeWorkspacesFn } from './opencode-server';
4
+
5
+ const retrySqliteQuery = (failureCount: number, error: unknown) => {
6
+ return failureCount < 3 && isRetryableSqliteError(error);
7
+ };
8
+
9
+ const retryDelay = (attemptIndex: number) => {
10
+ return [150, 400][attemptIndex] ?? 800;
11
+ };
12
+
13
+ export const openCodeWorkspacesQueryOptions = () =>
14
+ queryOptions({
15
+ queryFn: () => listOpenCodeWorkspacesFn(),
16
+ queryKey: ['opencode-workspaces'],
17
+ retry: retrySqliteQuery,
18
+ retryDelay,
19
+ });
20
+
21
+ export const openCodeSessionsQueryOptions = (workspaceKey: string | null) =>
22
+ queryOptions({
23
+ enabled: workspaceKey !== null,
24
+ queryFn: () => listOpenCodeSessionsFn({ data: { workspaceKey: workspaceKey ?? '' } }),
25
+ queryKey: ['opencode-sessions', workspaceKey ?? 'none'],
26
+ retry: retrySqliteQuery,
27
+ retryDelay,
28
+ });
29
+
30
+ export const openCodeSessionDetailQueryOptions = (sessionId: string | null) =>
31
+ queryOptions({
32
+ enabled: sessionId !== null,
33
+ queryFn: () => getOpenCodeSessionDetailFn({ data: { sessionId: sessionId ?? '' } }),
34
+ queryKey: ['opencode-session', sessionId ?? 'none'],
35
+ retry: retrySqliteQuery,
36
+ retryDelay,
37
+ });
@@ -0,0 +1,73 @@
1
+ import { createServerFn } from '@tanstack/react-start';
2
+ import { z } from 'zod';
3
+ import { renderSourceSessionDownload } from './source-session-export-server';
4
+
5
+ const workspaceSchema = z.object({
6
+ workspaceKey: z.string().min(1),
7
+ });
8
+
9
+ const sessionSchema = z.object({
10
+ sessionId: z.string().min(1),
11
+ });
12
+
13
+ const exportSessionSchema = z.object({
14
+ includeCommentary: z.boolean().default(true),
15
+ includeMetadata: z.boolean().default(true),
16
+ includeTools: z.boolean().default(true),
17
+ outputFormat: z.enum(['md', 'txt']).default('md'),
18
+ sessionId: z.string().min(1),
19
+ zipArchive: z.boolean().default(false),
20
+ });
21
+
22
+ export const listOpenCodeWorkspacesFn = createServerFn({ method: 'GET' }).handler(async () => {
23
+ const { listOpenCodeWorkspaceGroups } = await import('@spiracha/lib/opencode-db');
24
+ return listOpenCodeWorkspaceGroups();
25
+ });
26
+
27
+ export const listOpenCodeSessionsFn = createServerFn({ method: 'GET' })
28
+ .validator(workspaceSchema)
29
+ .handler(async ({ data }) => {
30
+ const { listOpenCodeSessionsForGroup } = await import('@spiracha/lib/opencode-db');
31
+ return listOpenCodeSessionsForGroup(data.workspaceKey);
32
+ });
33
+
34
+ const loadOpenCodeSessionTranscript = async (sessionId: string) => {
35
+ const { readOpenCodeSessionTranscript, resolveOpenCodeDbPath } = await import('@spiracha/lib/opencode-db');
36
+ const transcript = await readOpenCodeSessionTranscript(resolveOpenCodeDbPath(), sessionId);
37
+ if (!transcript) {
38
+ throw new Error(`OpenCode session not found: ${sessionId}`);
39
+ }
40
+
41
+ return transcript;
42
+ };
43
+
44
+ export const getOpenCodeSessionDetailFn = createServerFn({ method: 'GET' })
45
+ .validator(sessionSchema)
46
+ .handler(async ({ data }) => {
47
+ return loadOpenCodeSessionTranscript(data.sessionId);
48
+ });
49
+
50
+ export const exportOpenCodeSessionFn = createServerFn({ method: 'POST' })
51
+ .validator(exportSessionSchema)
52
+ .handler(async ({ data }) => {
53
+ const { renderOpenCodeTranscript } = await import('@spiracha/lib/opencode-transcript');
54
+ const transcript = await loadOpenCodeSessionTranscript(data.sessionId);
55
+ const content = renderOpenCodeTranscript(transcript, {
56
+ includeCommentary: data.includeCommentary,
57
+ includeMetadata: data.includeMetadata,
58
+ includeTools: data.includeTools,
59
+ outputFormat: data.outputFormat,
60
+ });
61
+
62
+ if (!content) {
63
+ throw new Error(`OpenCode session has no exportable content: ${data.sessionId}`);
64
+ }
65
+
66
+ return renderSourceSessionDownload({
67
+ content,
68
+ fallbackBaseName: 'opencode-session',
69
+ fileBaseName: transcript.session.title || transcript.session.slug || transcript.session.sessionId,
70
+ outputFormat: data.outputFormat,
71
+ zipArchive: data.zipArchive,
72
+ });
73
+ });
@@ -0,0 +1,221 @@
1
+ import type { ThreadEvent, ThreadTranscriptStats } from '@spiracha/lib/codex-browser-types';
2
+ import type { OpenCodeSessionTranscript, OpenCodeTranscriptPart } from '@spiracha/lib/opencode-exporter-types';
3
+ import { splitOpenCodeThinkTaggedText } from '@spiracha/lib/opencode-think-tags';
4
+ import {
5
+ getFinalOpenCodeAssistantTextPartIds,
6
+ getOpenCodeTextPartPhase,
7
+ } from '@spiracha/lib/opencode-transcript-phase';
8
+ import type { JsonValue } from '@spiracha/lib/shared';
9
+ import { getThreadTranscriptStats } from './thread-transcript-stats';
10
+
11
+ const toTimestamp = (value: number | null | undefined): string | null => {
12
+ if (value === null || value === undefined || !Number.isFinite(value)) {
13
+ return null;
14
+ }
15
+
16
+ return new Date(value).toISOString();
17
+ };
18
+
19
+ const buildRaw = (part: OpenCodeTranscriptPart, eventType: string): Record<string, JsonValue> => ({
20
+ eventType,
21
+ messageId: part.messageId,
22
+ partId: part.partId,
23
+ role: part.role,
24
+ source: 'opencode_part',
25
+ type: part.type,
26
+ ...part.raw,
27
+ });
28
+
29
+ const buildMessageEvent = (
30
+ part: OpenCodeTranscriptPart,
31
+ sequence: number,
32
+ text: string,
33
+ phase: string | null,
34
+ ): ThreadEvent => ({
35
+ isHiddenByDefault: part.role !== 'assistant' && part.role !== 'user',
36
+ kind: 'message',
37
+ memoryCitation: null,
38
+ model: null,
39
+ phase,
40
+ raw: buildRaw(part, 'message'),
41
+ role: part.role,
42
+ sequence,
43
+ text,
44
+ timestamp: toTimestamp(part.createdAtMs),
45
+ variant: part.role === 'user' ? 'user_message' : part.role === 'assistant' ? 'agent_message' : 'message',
46
+ });
47
+
48
+ const buildReasoningEventFromText = (
49
+ part: OpenCodeTranscriptPart,
50
+ sequence: number,
51
+ textValue: string | null | undefined,
52
+ ): ThreadEvent | null => {
53
+ const text = textValue?.trim();
54
+ if (!text) {
55
+ return null;
56
+ }
57
+
58
+ return {
59
+ content: text,
60
+ hasEncryptedContent: false,
61
+ kind: 'reasoning',
62
+ raw: buildRaw(part, 'reasoning'),
63
+ sequence,
64
+ summary: [text],
65
+ timestamp: toTimestamp(part.createdAtMs),
66
+ };
67
+ };
68
+
69
+ const buildReasoningEvent = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent | null => {
70
+ const { reasoningBlocks, visibleText } = splitOpenCodeThinkTaggedText(part.text ?? '');
71
+ return buildReasoningEventFromText(part, sequence, [...reasoningBlocks, visibleText].filter(Boolean).join('\n\n'));
72
+ };
73
+
74
+ const buildToolCallCommand = (part: OpenCodeTranscriptPart): string => {
75
+ const toolName = part.toolName ?? 'unknown';
76
+ if (!part.argumentsText?.trim()) {
77
+ return toolName;
78
+ }
79
+
80
+ return `${toolName}\n${part.argumentsText}`;
81
+ };
82
+
83
+ const buildToolCallEvent = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent => ({
84
+ argumentsParseFailed: false,
85
+ argumentsText: part.argumentsText ?? null,
86
+ callId: part.callId ?? null,
87
+ command: buildToolCallCommand(part),
88
+ kind: 'tool_call',
89
+ name: part.toolName ?? 'unknown',
90
+ raw: buildRaw(part, 'tool_call'),
91
+ sequence,
92
+ timestamp: toTimestamp(part.createdAtMs),
93
+ workdir: null,
94
+ });
95
+
96
+ const buildToolOutputEvent = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent | null => {
97
+ const outputText = part.outputText?.trim();
98
+ if (!outputText) {
99
+ return null;
100
+ }
101
+
102
+ return {
103
+ callId: part.callId ?? null,
104
+ exitCode: null,
105
+ kind: 'tool_output',
106
+ outputText,
107
+ raw: buildRaw(part, 'tool_output'),
108
+ sequence,
109
+ summary: outputText,
110
+ timestamp: toTimestamp(part.createdAtMs),
111
+ wallTime: null,
112
+ };
113
+ };
114
+
115
+ const buildStepStartedEvent = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent => ({
116
+ collaborationModeKind: null,
117
+ kind: 'task_started',
118
+ modelContextWindow: null,
119
+ raw: buildRaw(part, 'step_start'),
120
+ sequence,
121
+ startedAt: part.startTimeMs ?? part.createdAtMs,
122
+ timestamp: toTimestamp(part.createdAtMs),
123
+ turnId: part.messageId,
124
+ });
125
+
126
+ const buildStepCompleteEvent = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent => ({
127
+ completedAt: part.endTimeMs ?? part.updatedAtMs,
128
+ durationMs: part.startTimeMs && part.endTimeMs ? part.endTimeMs - part.startTimeMs : null,
129
+ kind: 'task_complete',
130
+ lastAgentMessage: part.reason ?? null,
131
+ raw: buildRaw(part, 'step_finish'),
132
+ sequence,
133
+ timestamp: toTimestamp(part.updatedAtMs),
134
+ timeToFirstTokenMs: null,
135
+ turnId: part.messageId,
136
+ });
137
+
138
+ const flattenParts = (transcript: OpenCodeSessionTranscript): OpenCodeTranscriptPart[] => {
139
+ return transcript.messages.flatMap((message) => message.parts);
140
+ };
141
+
142
+ const textPartToEvents = (
143
+ part: OpenCodeTranscriptPart,
144
+ sequence: number,
145
+ finalAssistantTextPartIds: Set<string>,
146
+ ): ThreadEvent[] => {
147
+ const { reasoningBlocks, visibleText } =
148
+ part.role === 'assistant'
149
+ ? splitOpenCodeThinkTaggedText(part.text ?? '')
150
+ : { reasoningBlocks: [], visibleText: part.text ?? '' };
151
+ const events: ThreadEvent[] = [];
152
+
153
+ if (part.role === 'assistant') {
154
+ events.push(
155
+ ...reasoningBlocks
156
+ .map((block, index) => buildReasoningEventFromText(part, sequence + index, block))
157
+ .filter((event): event is ThreadEvent => event !== null),
158
+ );
159
+ }
160
+
161
+ const text = visibleText.trim();
162
+ if (!text) {
163
+ return events;
164
+ }
165
+
166
+ const messageSequence = sequence + events.length;
167
+ const phase = getOpenCodeTextPartPhase(part, finalAssistantTextPartIds);
168
+ events.push(buildMessageEvent(part, messageSequence, text, phase));
169
+ return events;
170
+ };
171
+
172
+ const toolPartToEvents = (part: OpenCodeTranscriptPart, sequence: number): ThreadEvent[] => {
173
+ const output = buildToolOutputEvent(part, sequence + 1);
174
+ return [buildToolCallEvent(part, sequence), output].filter((event): event is ThreadEvent => event !== null);
175
+ };
176
+
177
+ const partToEvents = (
178
+ part: OpenCodeTranscriptPart,
179
+ sequence: number,
180
+ finalAssistantTextPartIds: Set<string>,
181
+ ): ThreadEvent[] => {
182
+ if (part.type === 'text') {
183
+ return textPartToEvents(part, sequence, finalAssistantTextPartIds);
184
+ }
185
+
186
+ if (part.type === 'reasoning') {
187
+ const event = buildReasoningEvent(part, sequence);
188
+ return event ? [event] : [];
189
+ }
190
+
191
+ if (part.type === 'tool') {
192
+ return toolPartToEvents(part, sequence);
193
+ }
194
+
195
+ if (part.type === 'step-start') {
196
+ return [buildStepStartedEvent(part, sequence)];
197
+ }
198
+
199
+ if (part.type === 'step-finish') {
200
+ return [buildStepCompleteEvent(part, sequence)];
201
+ }
202
+
203
+ return [];
204
+ };
205
+
206
+ export const openCodeTranscriptToThreadEvents = (transcript: OpenCodeSessionTranscript): ThreadEvent[] => {
207
+ const parts = flattenParts(transcript);
208
+ const finalAssistantTextPartIds = getFinalOpenCodeAssistantTextPartIds(parts);
209
+ const events: ThreadEvent[] = [];
210
+ let sequence = 0;
211
+ for (const part of parts) {
212
+ const partEvents = partToEvents(part, sequence, finalAssistantTextPartIds);
213
+ events.push(...partEvents);
214
+ sequence += Math.max(partEvents.length, 1);
215
+ }
216
+ return events;
217
+ };
218
+
219
+ export const getOpenCodeThreadTranscriptStats = (events: ThreadEvent[]): ThreadTranscriptStats => {
220
+ return getThreadTranscriptStats(events);
221
+ };