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,241 @@
1
+ import {
2
+ type ColumnDef,
3
+ flexRender,
4
+ getCoreRowModel,
5
+ getFilteredRowModel,
6
+ getSortedRowModel,
7
+ type RowSelectionState,
8
+ type SortingState,
9
+ useReactTable,
10
+ } from '@tanstack/react-table';
11
+ import { ArrowDownUp } from 'lucide-react';
12
+ import { type MouseEvent, type ReactNode, useRef, useState } from 'react';
13
+ import { Checkbox } from '#/components/ui/checkbox';
14
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '#/components/ui/table';
15
+ import { cn } from '#/lib/utils';
16
+
17
+ type DataTableProps<TData> = {
18
+ className?: string;
19
+ columns: ReadonlyArray<ColumnDef<TData, any>>;
20
+ data: TData[];
21
+ emptyMessage: string;
22
+ enableRowSelection?: boolean;
23
+ getRowId?: (row: TData, index: number) => string;
24
+ initialSorting?: SortingState;
25
+ onRowClick?: (row: TData) => void;
26
+ renderToolbar?: (input: { clearSelection: () => void; selectedRows: TData[] }) => ReactNode;
27
+ };
28
+
29
+ const getSortIndicator = (value: false | 'asc' | 'desc') => {
30
+ if (value === 'asc') {
31
+ return '↑';
32
+ }
33
+
34
+ if (value === 'desc') {
35
+ return '↓';
36
+ }
37
+
38
+ return <ArrowDownUp className="size-3" />;
39
+ };
40
+
41
+ const getRangeRowIds = (visibleRowIds: string[], anchorRowId: string, targetRowId: string) => {
42
+ const anchorIndex = visibleRowIds.indexOf(anchorRowId);
43
+ const targetIndex = visibleRowIds.indexOf(targetRowId);
44
+
45
+ if (anchorIndex === -1 || targetIndex === -1) {
46
+ return null;
47
+ }
48
+
49
+ const [startIndex, endIndex] = anchorIndex <= targetIndex ? [anchorIndex, targetIndex] : [targetIndex, anchorIndex];
50
+ return visibleRowIds.slice(startIndex, endIndex + 1);
51
+ };
52
+
53
+ const applySelectionState = (selection: RowSelectionState, rowIds: string[], checked: boolean) => {
54
+ const nextSelection = { ...selection };
55
+
56
+ for (const rowId of rowIds) {
57
+ if (checked) {
58
+ nextSelection[rowId] = true;
59
+ continue;
60
+ }
61
+
62
+ delete nextSelection[rowId];
63
+ }
64
+
65
+ return nextSelection;
66
+ };
67
+
68
+ export function DataTable<TData>({
69
+ className,
70
+ columns,
71
+ data,
72
+ emptyMessage,
73
+ enableRowSelection = false,
74
+ getRowId,
75
+ initialSorting = [],
76
+ onRowClick,
77
+ renderToolbar,
78
+ }: DataTableProps<TData>) {
79
+ const [sorting, setSorting] = useState<SortingState>(initialSorting);
80
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
81
+ const lastSelectedRowIdRef = useRef<string | null>(null);
82
+
83
+ const updateSelectionForRow = (rowId: string, checked: boolean, shiftKey: boolean) => {
84
+ const visibleRowIds = table.getRowModel().rows.map((row) => row.id);
85
+
86
+ if (shiftKey && lastSelectedRowIdRef.current) {
87
+ const rangeRowIds = getRangeRowIds(visibleRowIds, lastSelectedRowIdRef.current, rowId);
88
+ if (rangeRowIds) {
89
+ setRowSelection(applySelectionState(rowSelection, rangeRowIds, checked));
90
+ lastSelectedRowIdRef.current = rowId;
91
+ return;
92
+ }
93
+ }
94
+
95
+ setRowSelection(applySelectionState(rowSelection, [rowId], checked));
96
+ lastSelectedRowIdRef.current = rowId;
97
+ };
98
+
99
+ const selectionColumn: ColumnDef<TData, any> = {
100
+ cell: ({ row }) => (
101
+ <Checkbox
102
+ aria-label={`Select row ${row.id}`}
103
+ checked={row.getIsSelected()}
104
+ onClick={(event: MouseEvent<HTMLButtonElement>) => {
105
+ event.stopPropagation();
106
+ event.preventDefault();
107
+ updateSelectionForRow(row.id, !row.getIsSelected(), event.shiftKey);
108
+ }}
109
+ onCheckedChange={(checked) => {
110
+ if (typeof checked !== 'boolean') {
111
+ return;
112
+ }
113
+
114
+ updateSelectionForRow(row.id, checked, false);
115
+ }}
116
+ />
117
+ ),
118
+ enableSorting: false,
119
+ header: ({ table }) => (
120
+ <Checkbox
121
+ aria-label="Select all rows"
122
+ checked={
123
+ table.getIsAllPageRowsSelected()
124
+ ? true
125
+ : table.getIsSomePageRowsSelected()
126
+ ? 'indeterminate'
127
+ : false
128
+ }
129
+ onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked === true)}
130
+ />
131
+ ),
132
+ id: 'select',
133
+ };
134
+ const tableColumns = enableRowSelection ? [selectionColumn, ...columns] : [...columns];
135
+ const table = useReactTable({
136
+ autoResetPageIndex: false,
137
+ columns: tableColumns,
138
+ data,
139
+ enableRowSelection,
140
+ enableSortingRemoval: false,
141
+ getCoreRowModel: getCoreRowModel(),
142
+ getFilteredRowModel: getFilteredRowModel(),
143
+ getRowId,
144
+ getSortedRowModel: getSortedRowModel(),
145
+ onRowSelectionChange: setRowSelection,
146
+ onSortingChange: setSorting,
147
+ sortDescFirst: false,
148
+ state: {
149
+ rowSelection,
150
+ sorting,
151
+ },
152
+ });
153
+ const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);
154
+
155
+ return (
156
+ <div
157
+ className={cn(
158
+ 'w-full overflow-x-auto rounded-[1.5rem] border border-[var(--border)] bg-[var(--panel)]',
159
+ className,
160
+ )}
161
+ >
162
+ {renderToolbar ? (
163
+ <div className="border-[var(--border)] border-b px-4 py-3">
164
+ {renderToolbar({
165
+ clearSelection: () => setRowSelection({}),
166
+ selectedRows,
167
+ })}
168
+ </div>
169
+ ) : null}
170
+ <Table className="min-w-full">
171
+ <TableHeader className="bg-[var(--panel-secondary)]">
172
+ {table.getHeaderGroups().map((headerGroup) => (
173
+ <TableRow key={headerGroup.id} className="border-[var(--border)] hover:bg-transparent">
174
+ {headerGroup.headers.map((header) => (
175
+ <TableHead
176
+ key={header.id}
177
+ className="h-10 whitespace-nowrap px-4 font-semibold text-[11px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]"
178
+ >
179
+ {header.isPlaceholder ? null : header.column.getCanSort() ? (
180
+ <button
181
+ className="inline-flex items-center gap-1.5 text-left"
182
+ type="button"
183
+ onClick={header.column.getToggleSortingHandler()}
184
+ >
185
+ <span>
186
+ {flexRender(header.column.columnDef.header, header.getContext())}
187
+ </span>
188
+ <span aria-hidden="true" className="text-[10px]">
189
+ {getSortIndicator(header.column.getIsSorted())}
190
+ </span>
191
+ </button>
192
+ ) : (
193
+ flexRender(header.column.columnDef.header, header.getContext())
194
+ )}
195
+ </TableHead>
196
+ ))}
197
+ </TableRow>
198
+ ))}
199
+ </TableHeader>
200
+ <TableBody>
201
+ {table.getRowModel().rows.length === 0 ? (
202
+ <TableRow className="border-[var(--border)]">
203
+ <TableCell
204
+ className="px-4 py-10 text-center text-[var(--muted-foreground)] text-sm"
205
+ colSpan={tableColumns.length}
206
+ >
207
+ {emptyMessage}
208
+ </TableCell>
209
+ </TableRow>
210
+ ) : (
211
+ table.getRowModel().rows.map((row) => {
212
+ const clickable = Boolean(onRowClick);
213
+
214
+ return (
215
+ <TableRow
216
+ key={row.id}
217
+ className={cn(
218
+ 'border-[var(--border)] hover:bg-[var(--panel-secondary)]/75',
219
+ clickable ? 'cursor-pointer' : '',
220
+ )}
221
+ onClick={() => {
222
+ if (!onRowClick) {
223
+ return;
224
+ }
225
+ onRowClick(row.original);
226
+ }}
227
+ >
228
+ {row.getVisibleCells().map((cell) => (
229
+ <TableCell key={cell.id} className="px-4 py-2.5 align-top">
230
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
231
+ </TableCell>
232
+ ))}
233
+ </TableRow>
234
+ );
235
+ })
236
+ )}
237
+ </TableBody>
238
+ </Table>
239
+ </div>
240
+ );
241
+ }
@@ -0,0 +1,88 @@
1
+ import { useEffect, useId, useState } from 'react';
2
+ import {
3
+ AlertDialog,
4
+ AlertDialogAction,
5
+ AlertDialogCancel,
6
+ AlertDialogContent,
7
+ AlertDialogDescription,
8
+ AlertDialogFooter,
9
+ AlertDialogHeader,
10
+ AlertDialogTitle,
11
+ } from '#/components/ui/alert-dialog';
12
+ import { Checkbox } from '#/components/ui/checkbox';
13
+
14
+ type DeleteConfirmDialogProps = {
15
+ confirmLabel?: string;
16
+ defaultDeleteSessionFiles?: boolean;
17
+ description: string;
18
+ errorMessage?: string | null;
19
+ open: boolean;
20
+ showDeleteSessionFilesOption?: boolean;
21
+ title: string;
22
+ onConfirm: (options: { deleteSessionFiles: boolean }) => void;
23
+ onOpenChange: (open: boolean) => void;
24
+ };
25
+
26
+ export function DeleteConfirmDialog({
27
+ confirmLabel = 'Delete',
28
+ defaultDeleteSessionFiles = false,
29
+ description,
30
+ errorMessage = null,
31
+ open,
32
+ showDeleteSessionFilesOption = false,
33
+ title,
34
+ onConfirm,
35
+ onOpenChange,
36
+ }: DeleteConfirmDialogProps) {
37
+ const checkboxId = useId();
38
+ const checkboxDescriptionId = useId();
39
+ const [deleteSessionFiles, setDeleteSessionFiles] = useState(defaultDeleteSessionFiles);
40
+
41
+ useEffect(() => {
42
+ if (!open) {
43
+ setDeleteSessionFiles(defaultDeleteSessionFiles);
44
+ }
45
+ }, [defaultDeleteSessionFiles, open]);
46
+
47
+ return (
48
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
49
+ <AlertDialogContent className="border-[var(--border)] bg-[var(--panel)] text-[var(--foreground)]">
50
+ <AlertDialogHeader>
51
+ <AlertDialogTitle>{title}</AlertDialogTitle>
52
+ <AlertDialogDescription className="text-[var(--muted-foreground)]">
53
+ {description}
54
+ </AlertDialogDescription>
55
+ </AlertDialogHeader>
56
+ {showDeleteSessionFilesOption ? (
57
+ <div className="flex items-start gap-3 rounded-xl border border-[var(--border)] bg-[var(--background)]/70 px-4 py-3 text-sm">
58
+ <Checkbox
59
+ aria-label="Delete Session files"
60
+ aria-describedby={checkboxDescriptionId}
61
+ checked={deleteSessionFiles}
62
+ id={checkboxId}
63
+ onCheckedChange={(checked) => setDeleteSessionFiles(checked === true)}
64
+ />
65
+ <span className="space-y-1">
66
+ <label className="block font-medium" htmlFor={checkboxId}>
67
+ Delete Session files
68
+ </label>
69
+ <span className="block text-[var(--muted-foreground)] text-xs" id={checkboxDescriptionId}>
70
+ Remove the rollout JSONL from disk as well, so Codex cannot backfill this thread later.
71
+ </span>
72
+ </span>
73
+ </div>
74
+ ) : null}
75
+ {errorMessage ? <p className="text-[var(--destructive)] text-sm">{errorMessage}</p> : null}
76
+ <AlertDialogFooter>
77
+ <AlertDialogCancel className="border-[var(--border)]">Cancel</AlertDialogCancel>
78
+ <AlertDialogAction
79
+ className="bg-[var(--destructive)] text-[var(--destructive-foreground)] hover:bg-[var(--destructive)]/90"
80
+ onClick={() => onConfirm({ deleteSessionFiles })}
81
+ >
82
+ {confirmLabel}
83
+ </AlertDialogAction>
84
+ </AlertDialogFooter>
85
+ </AlertDialogContent>
86
+ </AlertDialog>
87
+ );
88
+ }
@@ -0,0 +1,184 @@
1
+ import { useEffect, useId, useState } from 'react';
2
+ import { Button } from '#/components/ui/button';
3
+ import { Checkbox } from '#/components/ui/checkbox';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '#/components/ui/dialog';
12
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '#/components/ui/select';
13
+
14
+ type ExportDialogProps = {
15
+ disabled?: boolean;
16
+ errorMessage?: string | null;
17
+ forceZipArchive?: boolean;
18
+ open: boolean;
19
+ pending?: boolean;
20
+ title?: string;
21
+ onExport: (options: {
22
+ includeCommentary: boolean;
23
+ includeMetadata: boolean;
24
+ includeTools: boolean;
25
+ outputFormat: 'md' | 'txt';
26
+ zipArchive: boolean;
27
+ }) => void;
28
+ onOpenChange: (open: boolean) => void;
29
+ };
30
+
31
+ export function ExportDialog({
32
+ disabled = false,
33
+ errorMessage = null,
34
+ forceZipArchive = false,
35
+ open,
36
+ pending = false,
37
+ title = 'Export thread',
38
+ onExport,
39
+ onOpenChange,
40
+ }: ExportDialogProps) {
41
+ const [outputFormat, setOutputFormat] = useState<'md' | 'txt'>('md');
42
+ const [includeMetadata, setIncludeMetadata] = useState(true);
43
+ const [includeCommentary, setIncludeCommentary] = useState(false);
44
+ const [includeTools, setIncludeTools] = useState(true);
45
+ const [zipArchive, setZipArchive] = useState(false);
46
+ const effectiveZipArchive = forceZipArchive || zipArchive;
47
+ const zipDescriptionId = useId();
48
+
49
+ useEffect(() => {
50
+ if (!open) {
51
+ setOutputFormat('md');
52
+ setIncludeMetadata(true);
53
+ setIncludeCommentary(false);
54
+ setIncludeTools(true);
55
+ setZipArchive(false);
56
+ }
57
+ }, [open]);
58
+
59
+ return (
60
+ <Dialog open={open} onOpenChange={onOpenChange}>
61
+ <DialogContent className="border-[var(--border)] bg-[var(--panel)] text-[var(--foreground)]">
62
+ <DialogHeader>
63
+ <DialogTitle>{title}</DialogTitle>
64
+ <DialogDescription className="text-[var(--muted-foreground)]">
65
+ Choose the transcript format and whether the export includes tool calls.
66
+ </DialogDescription>
67
+ </DialogHeader>
68
+
69
+ <div className="space-y-5 py-2">
70
+ <div className="space-y-2">
71
+ <label className="font-medium text-sm" htmlFor="output-format">
72
+ Output format
73
+ </label>
74
+ <Select value={outputFormat} onValueChange={(value) => setOutputFormat(value as 'md' | 'txt')}>
75
+ <SelectTrigger
76
+ id="output-format"
77
+ className="border-[var(--border)] bg-[var(--panel-secondary)] text-[var(--foreground)]"
78
+ >
79
+ <SelectValue placeholder="Choose a format" />
80
+ </SelectTrigger>
81
+ <SelectContent className="border-[var(--border)] bg-[var(--panel)] text-[var(--foreground)] shadow-[var(--panel-shadow)]">
82
+ <SelectItem
83
+ className="focus:bg-[var(--panel-secondary)] focus:text-[var(--foreground)] data-[highlighted]:bg-[var(--panel-secondary)] data-[highlighted]:text-[var(--foreground)]"
84
+ value="md"
85
+ >
86
+ Markdown (.md)
87
+ </SelectItem>
88
+ <SelectItem
89
+ className="focus:bg-[var(--panel-secondary)] focus:text-[var(--foreground)] data-[highlighted]:bg-[var(--panel-secondary)] data-[highlighted]:text-[var(--foreground)]"
90
+ value="txt"
91
+ >
92
+ Plain text (.txt)
93
+ </SelectItem>
94
+ </SelectContent>
95
+ </Select>
96
+ </div>
97
+
98
+ <div className="flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3">
99
+ <Checkbox
100
+ aria-label="Include metadata"
101
+ checked={includeMetadata}
102
+ onCheckedChange={(checked) => setIncludeMetadata(checked === true)}
103
+ />
104
+ <span className="space-y-1">
105
+ <span className="block font-medium text-sm">Include metadata</span>
106
+ <span className="block text-[var(--muted-foreground)] text-sm">
107
+ Includes the chat metadata section at the top of the exported transcript.
108
+ </span>
109
+ </span>
110
+ </div>
111
+
112
+ <div className="flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3">
113
+ <Checkbox
114
+ aria-label="Include commentary"
115
+ checked={includeCommentary}
116
+ onCheckedChange={(checked) => setIncludeCommentary(checked === true)}
117
+ />
118
+ <span className="space-y-1">
119
+ <span className="block font-medium text-sm">Include commentary</span>
120
+ <span className="block text-[var(--muted-foreground)] text-sm">
121
+ Includes assistant commentary-phase updates in the exported transcript.
122
+ </span>
123
+ </span>
124
+ </div>
125
+
126
+ <div className="flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3">
127
+ <Checkbox
128
+ aria-label="Include tool calls"
129
+ checked={includeTools}
130
+ onCheckedChange={(checked) => setIncludeTools(checked === true)}
131
+ />
132
+ <span className="space-y-1">
133
+ <span className="block font-medium text-sm">Include tool calls</span>
134
+ <span className="block text-[var(--muted-foreground)] text-sm">
135
+ Includes tool-call summaries and tool-output summaries in the export.
136
+ </span>
137
+ </span>
138
+ </div>
139
+
140
+ <div className="flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3">
141
+ <Checkbox
142
+ aria-label="Zip archive"
143
+ aria-describedby={zipDescriptionId}
144
+ checked={effectiveZipArchive}
145
+ disabled={forceZipArchive}
146
+ onCheckedChange={(checked) => setZipArchive(checked === true)}
147
+ />
148
+ <span className="space-y-1">
149
+ <span className="block font-medium text-sm">Zip archive</span>
150
+ <span className="block text-[var(--muted-foreground)] text-sm" id={zipDescriptionId}>
151
+ {forceZipArchive
152
+ ? 'Required when exporting multiple threads.'
153
+ : 'Downloads the exported transcript inside a .zip archive.'}
154
+ </span>
155
+ </span>
156
+ </div>
157
+ </div>
158
+
159
+ {errorMessage ? <p className="text-[var(--destructive)] text-sm">{errorMessage}</p> : null}
160
+
161
+ <DialogFooter>
162
+ <Button className="rounded-full" variant="outline" onClick={() => onOpenChange(false)}>
163
+ Cancel
164
+ </Button>
165
+ <Button
166
+ className="rounded-full"
167
+ disabled={pending || disabled}
168
+ onClick={() =>
169
+ onExport({
170
+ includeCommentary,
171
+ includeMetadata,
172
+ includeTools,
173
+ outputFormat,
174
+ zipArchive: effectiveZipArchive,
175
+ })
176
+ }
177
+ >
178
+ {pending ? 'Exporting...' : 'Download export'}
179
+ </Button>
180
+ </DialogFooter>
181
+ </DialogContent>
182
+ </Dialog>
183
+ );
184
+ }
@@ -0,0 +1,17 @@
1
+ type JsonPanelProps = {
2
+ title: string;
3
+ value: unknown;
4
+ };
5
+
6
+ export function JsonPanel({ title, value }: JsonPanelProps) {
7
+ return (
8
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
9
+ <h3 className="font-semibold text-[var(--muted-foreground)] text-sm uppercase tracking-[0.18em]">
10
+ {title}
11
+ </h3>
12
+ <pre className="mt-4 overflow-x-auto rounded-2xl border border-[var(--border)] bg-[var(--code-background)] p-4 text-[var(--code-foreground)] text-xs leading-5">
13
+ {JSON.stringify(value, null, 2)}
14
+ </pre>
15
+ </section>
16
+ );
17
+ }
@@ -0,0 +1,112 @@
1
+ import type { KiroSessionSummary } from '@spiracha/lib/kiro-exporter-types';
2
+ import { Link } from '@tanstack/react-router';
3
+ import type { SortingState } from '@tanstack/react-table';
4
+ import { createColumnHelper } from '@tanstack/react-table';
5
+ import { Download, MoreHorizontal } from 'lucide-react';
6
+ import { useMemo } from 'react';
7
+ import { DataTable } from '#/components/data-table';
8
+ import { Button } from '#/components/ui/button';
9
+ import {
10
+ DropdownMenu,
11
+ DropdownMenuContent,
12
+ DropdownMenuItem,
13
+ DropdownMenuTrigger,
14
+ } from '#/components/ui/dropdown-menu';
15
+ import { formatDateTime, formatNumber } from '#/lib/formatters';
16
+
17
+ type KiroSessionsTableProps = {
18
+ onExportSession: (session: KiroSessionSummary) => void;
19
+ sessions: KiroSessionSummary[];
20
+ };
21
+
22
+ const columnHelper = createColumnHelper<KiroSessionSummary>();
23
+ const defaultSorting: SortingState = [{ desc: true, id: 'lastActive' }];
24
+
25
+ const columns = (onExportSession: (session: KiroSessionSummary) => void) =>
26
+ [
27
+ columnHelper.accessor('title', {
28
+ cell: (info) => (
29
+ <Link
30
+ className="block w-[16rem] max-w-[22rem] space-y-1 rounded-md outline-none transition hover:opacity-80 focus-visible:ring-2 focus-visible:ring-[var(--accent)] lg:w-auto"
31
+ params={{ sessionId: info.row.original.sessionId }}
32
+ to="/kiro-sessions/$sessionId"
33
+ >
34
+ <p className="truncate font-medium underline-offset-2 hover:underline">{info.getValue()}</p>
35
+ <p className="truncate text-[var(--muted-foreground)] text-xs">{info.row.original.sessionId}</p>
36
+ </Link>
37
+ ),
38
+ header: 'Session',
39
+ }),
40
+ columnHelper.accessor('lastActiveAtMs', {
41
+ cell: (info) => (
42
+ <span className="whitespace-nowrap text-sm" suppressHydrationWarning>
43
+ {formatDateTime(info.getValue())}
44
+ </span>
45
+ ),
46
+ header: 'Updated',
47
+ id: 'lastActive',
48
+ }),
49
+ columnHelper.accessor('selectedModel', {
50
+ cell: (info) => <span className="text-sm">{info.getValue() ?? 'unknown'}</span>,
51
+ header: 'Model',
52
+ }),
53
+ columnHelper.accessor('messageCount', {
54
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
55
+ header: 'Messages',
56
+ }),
57
+ columnHelper.accessor('imageCount', {
58
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
59
+ header: 'Images',
60
+ }),
61
+ columnHelper.accessor('promptLogCount', {
62
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
63
+ header: 'Prompt logs',
64
+ }),
65
+ columnHelper.accessor('sessionType', {
66
+ cell: (info) => <span className="font-mono text-sm">{info.getValue() ?? 'unknown'}</span>,
67
+ header: 'Type',
68
+ }),
69
+ columnHelper.display({
70
+ cell: (info) => (
71
+ <DropdownMenu>
72
+ <DropdownMenuTrigger asChild>
73
+ <Button
74
+ aria-label={`Actions for ${info.row.original.title}`}
75
+ className="rounded-full"
76
+ size="icon"
77
+ type="button"
78
+ variant="ghost"
79
+ onClick={(event) => event.stopPropagation()}
80
+ >
81
+ <MoreHorizontal className="size-4" />
82
+ </Button>
83
+ </DropdownMenuTrigger>
84
+ <DropdownMenuContent align="end">
85
+ <DropdownMenuItem
86
+ disabled={info.row.original.renderablePartCount === 0}
87
+ onClick={() => onExportSession(info.row.original)}
88
+ >
89
+ <Download className="mr-2 size-4" />
90
+ Export session
91
+ </DropdownMenuItem>
92
+ </DropdownMenuContent>
93
+ </DropdownMenu>
94
+ ),
95
+ enableSorting: false,
96
+ header: '',
97
+ id: 'actions',
98
+ }),
99
+ ] as const;
100
+
101
+ export const KiroSessionsTable = ({ onExportSession, sessions }: KiroSessionsTableProps) => {
102
+ const tableColumns = useMemo(() => columns(onExportSession), [onExportSession]);
103
+
104
+ return (
105
+ <DataTable
106
+ columns={tableColumns}
107
+ data={sessions}
108
+ emptyMessage="No Kiro sessions match the current workspace filter."
109
+ initialSorting={defaultSorting}
110
+ />
111
+ );
112
+ };