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,53 @@
1
+ import type { KiroWorkspaceGroup } from '@spiracha/lib/kiro-exporter-types';
2
+ import { Link } from '@tanstack/react-router';
3
+ import { createColumnHelper } from '@tanstack/react-table';
4
+ import { DataTable } from '#/components/data-table';
5
+ import { formatDateTime, formatNumber } from '#/lib/formatters';
6
+
7
+ type KiroWorkspacesTableProps = {
8
+ workspaces: KiroWorkspaceGroup[];
9
+ };
10
+
11
+ const columnHelper = createColumnHelper<KiroWorkspaceGroup>();
12
+
13
+ const columns = [
14
+ columnHelper.accessor('label', {
15
+ cell: (info) => (
16
+ <Link
17
+ 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"
18
+ params={{ workspaceKey: info.row.original.key }}
19
+ to="/kiro/$workspaceKey"
20
+ >
21
+ <p className="truncate font-medium underline-offset-2 hover:underline">{info.getValue()}</p>
22
+ <p className="truncate text-[var(--muted-foreground)] text-xs">{info.row.original.worktree}</p>
23
+ </Link>
24
+ ),
25
+ header: 'Workspace',
26
+ }),
27
+ columnHelper.accessor('sessionCount', {
28
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
29
+ header: 'Sessions',
30
+ }),
31
+ columnHelper.accessor('messageCount', {
32
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
33
+ header: 'Messages',
34
+ }),
35
+ columnHelper.accessor('imageCount', {
36
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
37
+ header: 'Images',
38
+ }),
39
+ columnHelper.accessor('lastActiveAtMs', {
40
+ cell: (info) => (
41
+ <span className="whitespace-nowrap text-sm" suppressHydrationWarning>
42
+ {formatDateTime(info.getValue())}
43
+ </span>
44
+ ),
45
+ header: 'Last active',
46
+ }),
47
+ ] as const;
48
+
49
+ export const KiroWorkspacesTable = ({ workspaces }: KiroWorkspacesTableProps) => {
50
+ return (
51
+ <DataTable columns={columns} data={workspaces} emptyMessage="No Kiro workspaces match the current search." />
52
+ );
53
+ };
@@ -0,0 +1,18 @@
1
+ import { Input } from '#/components/ui/input';
2
+
3
+ type ListSearchInputProps = {
4
+ placeholder: string;
5
+ value: string;
6
+ onValueChange: (value: string) => void;
7
+ };
8
+
9
+ export function ListSearchInput({ placeholder, value, onValueChange }: ListSearchInputProps) {
10
+ return (
11
+ <Input
12
+ className="h-10 w-full rounded-full border-[var(--border)] bg-[var(--panel)] px-4 sm:w-[20rem]"
13
+ placeholder={placeholder}
14
+ value={value}
15
+ onChange={(event) => onValueChange(event.target.value)}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,23 @@
1
+ import { Loader2 } from 'lucide-react';
2
+
3
+ type LoadingPanelProps = {
4
+ description?: string;
5
+ title?: string;
6
+ };
7
+
8
+ export function LoadingPanel({
9
+ description = 'Fetching local data. Larger workspaces can take a moment.',
10
+ title = 'Loading',
11
+ }: LoadingPanelProps) {
12
+ return (
13
+ <div className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] px-6 py-10 text-center shadow-[var(--panel-shadow)]">
14
+ <div className="flex justify-center">
15
+ <div className="rounded-full border border-[var(--border)] bg-[var(--panel-secondary)] p-3">
16
+ <Loader2 className="size-5 animate-spin text-[var(--accent)]" />
17
+ </div>
18
+ </div>
19
+ <p className="mt-4 font-medium text-sm">{title}</p>
20
+ <p className="mt-2 text-[var(--muted-foreground)] text-sm">{description}</p>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ type MetadataItem = {
4
+ label: string;
5
+ value: ReactNode;
6
+ };
7
+
8
+ type MetadataSectionProps = {
9
+ items: MetadataItem[];
10
+ title: string;
11
+ };
12
+
13
+ export function MetadataSection({ items, title }: MetadataSectionProps) {
14
+ return (
15
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
16
+ <h3 className="font-semibold text-[var(--muted-foreground)] text-sm uppercase tracking-[0.18em]">
17
+ {title}
18
+ </h3>
19
+ <dl className="mt-4 space-y-3">
20
+ {items.map((item) => (
21
+ <div key={item.label} className="grid gap-1 sm:grid-cols-[11rem_1fr] sm:items-start">
22
+ <dt className="font-medium text-[var(--muted-foreground)] text-xs uppercase tracking-[0.14em]">
23
+ {item.label}
24
+ </dt>
25
+ <dd className="min-w-0 text-sm leading-6">{item.value}</dd>
26
+ </div>
27
+ ))}
28
+ </dl>
29
+ </section>
30
+ );
31
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ type MetricCardProps = {
4
+ helper?: string;
5
+ label: string;
6
+ value: ReactNode;
7
+ };
8
+
9
+ export function MetricCard({ helper, label, value }: MetricCardProps) {
10
+ return (
11
+ <div className="rounded-2xl border border-[var(--border)] bg-[var(--panel)] p-4 shadow-[var(--panel-shadow)]">
12
+ <p className="font-semibold text-[10px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]">
13
+ {label}
14
+ </p>
15
+ <p className="mt-2 truncate font-semibold text-lg tracking-[-0.03em]">{value}</p>
16
+ {helper ? <p className="mt-1.5 text-[var(--muted-foreground)] text-xs">{helper}</p> : null}
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,128 @@
1
+ import type { OpenCodeSessionSummary } from '@spiracha/lib/opencode-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 { Badge } from '#/components/ui/badge';
9
+ import { Button } from '#/components/ui/button';
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuTrigger,
15
+ } from '#/components/ui/dropdown-menu';
16
+ import { formatDateTime, formatNumber, formatTokens } from '#/lib/formatters';
17
+
18
+ type OpenCodeSessionsTableProps = {
19
+ onExportSession: (session: OpenCodeSessionSummary) => void;
20
+ sessions: OpenCodeSessionSummary[];
21
+ };
22
+
23
+ const columnHelper = createColumnHelper<OpenCodeSessionSummary>();
24
+ const defaultSorting: SortingState = [{ desc: true, id: 'updatedAt' }];
25
+
26
+ const formatCost = (value: number) => {
27
+ if (value <= 0) {
28
+ return '$0';
29
+ }
30
+
31
+ return `$${value.toFixed(value < 0.01 ? 4 : 2)}`;
32
+ };
33
+
34
+ const columns = (onExportSession: (session: OpenCodeSessionSummary) => void) =>
35
+ [
36
+ columnHelper.accessor('title', {
37
+ cell: (info) => (
38
+ <Link
39
+ 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"
40
+ params={{ sessionId: info.row.original.sessionId }}
41
+ to="/opencode-sessions/$sessionId"
42
+ >
43
+ <div className="flex items-center gap-2">
44
+ <p className="truncate font-medium underline-offset-2 hover:underline">{info.getValue()}</p>
45
+ {info.row.original.archivedAtMs ? <Badge variant="outline">archived</Badge> : null}
46
+ </div>
47
+ <p className="truncate text-[var(--muted-foreground)] text-xs">
48
+ {info.row.original.slug} · {info.row.original.sessionId}
49
+ </p>
50
+ </Link>
51
+ ),
52
+ header: 'Session',
53
+ }),
54
+ columnHelper.accessor('lastUpdatedAtMs', {
55
+ cell: (info) => (
56
+ <span className="whitespace-nowrap text-sm" suppressHydrationWarning>
57
+ {formatDateTime(info.getValue())}
58
+ </span>
59
+ ),
60
+ header: 'Updated',
61
+ id: 'updatedAt',
62
+ }),
63
+ columnHelper.accessor('agent', {
64
+ cell: (info) => <span className="font-mono text-sm">{info.getValue() ?? 'unknown'}</span>,
65
+ header: 'Agent',
66
+ }),
67
+ columnHelper.accessor('modelLabel', {
68
+ cell: (info) => <span className="text-sm">{info.getValue() ?? 'unknown'}</span>,
69
+ header: 'Model',
70
+ }),
71
+ columnHelper.accessor('messageCount', {
72
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
73
+ header: 'Messages',
74
+ }),
75
+ columnHelper.accessor('totalTokens', {
76
+ cell: (info) => (
77
+ <span className="whitespace-nowrap font-mono text-sm">{formatTokens(info.getValue())}</span>
78
+ ),
79
+ header: 'Tokens',
80
+ }),
81
+ columnHelper.accessor('cost', {
82
+ cell: (info) => <span className="font-mono text-sm">{formatCost(info.getValue())}</span>,
83
+ header: 'Cost',
84
+ }),
85
+ columnHelper.display({
86
+ cell: (info) => (
87
+ <DropdownMenu>
88
+ <DropdownMenuTrigger asChild>
89
+ <Button
90
+ aria-label={`Actions for ${info.row.original.title}`}
91
+ className="rounded-full"
92
+ size="icon"
93
+ type="button"
94
+ variant="ghost"
95
+ onClick={(event) => event.stopPropagation()}
96
+ >
97
+ <MoreHorizontal className="size-4" />
98
+ </Button>
99
+ </DropdownMenuTrigger>
100
+ <DropdownMenuContent align="end">
101
+ <DropdownMenuItem
102
+ disabled={info.row.original.renderablePartCount === 0}
103
+ onClick={() => onExportSession(info.row.original)}
104
+ >
105
+ <Download className="mr-2 size-4" />
106
+ Export session
107
+ </DropdownMenuItem>
108
+ </DropdownMenuContent>
109
+ </DropdownMenu>
110
+ ),
111
+ enableSorting: false,
112
+ header: '',
113
+ id: 'actions',
114
+ }),
115
+ ] as const;
116
+
117
+ export const OpenCodeSessionsTable = ({ onExportSession, sessions }: OpenCodeSessionsTableProps) => {
118
+ const tableColumns = useMemo(() => columns(onExportSession), [onExportSession]);
119
+
120
+ return (
121
+ <DataTable
122
+ columns={tableColumns}
123
+ data={sessions}
124
+ emptyMessage="No OpenCode sessions match the current workspace filter."
125
+ initialSorting={defaultSorting}
126
+ />
127
+ );
128
+ };
@@ -0,0 +1,57 @@
1
+ import type { OpenCodeWorkspaceGroup } from '@spiracha/lib/opencode-exporter-types';
2
+ import { Link } from '@tanstack/react-router';
3
+ import { createColumnHelper } from '@tanstack/react-table';
4
+ import { DataTable } from '#/components/data-table';
5
+ import { formatDateTime, formatNumber } from '#/lib/formatters';
6
+
7
+ type OpenCodeWorkspacesTableProps = {
8
+ workspaces: OpenCodeWorkspaceGroup[];
9
+ };
10
+
11
+ const columnHelper = createColumnHelper<OpenCodeWorkspaceGroup>();
12
+
13
+ const columns = [
14
+ columnHelper.accessor('label', {
15
+ cell: (info) => (
16
+ <Link
17
+ 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"
18
+ params={{ workspaceKey: info.row.original.key }}
19
+ to="/opencode/$workspaceKey"
20
+ >
21
+ <p className="truncate font-medium underline-offset-2 hover:underline">{info.getValue()}</p>
22
+ <p className="truncate text-[var(--muted-foreground)] text-xs">{info.row.original.worktree}</p>
23
+ </Link>
24
+ ),
25
+ header: 'Workspace',
26
+ }),
27
+ columnHelper.accessor('sessionCount', {
28
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
29
+ header: 'Sessions',
30
+ }),
31
+ columnHelper.accessor('messageCount', {
32
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
33
+ header: 'Messages',
34
+ }),
35
+ columnHelper.accessor('partCount', {
36
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
37
+ header: 'Parts',
38
+ }),
39
+ columnHelper.accessor('lastActiveMs', {
40
+ cell: (info) => (
41
+ <span className="whitespace-nowrap text-sm" suppressHydrationWarning>
42
+ {formatDateTime(info.getValue())}
43
+ </span>
44
+ ),
45
+ header: 'Last updated',
46
+ }),
47
+ ] as const;
48
+
49
+ export const OpenCodeWorkspacesTable = ({ workspaces }: OpenCodeWorkspacesTableProps) => {
50
+ return (
51
+ <DataTable
52
+ columns={columns}
53
+ data={workspaces}
54
+ emptyMessage="No OpenCode workspaces match the current search."
55
+ />
56
+ );
57
+ };
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ type PageHeaderProps = {
4
+ actions?: ReactNode;
5
+ breadcrumb?: ReactNode;
6
+ eyebrow?: string;
7
+ subtitle?: string;
8
+ title: string;
9
+ };
10
+
11
+ export function PageHeader({ actions, breadcrumb, eyebrow, subtitle, title }: PageHeaderProps) {
12
+ return (
13
+ <div className="flex flex-col gap-4 border-[var(--border)] border-b pb-5 sm:flex-row sm:items-end sm:justify-between">
14
+ <div className="space-y-2">
15
+ {breadcrumb ? <div>{breadcrumb}</div> : null}
16
+ {eyebrow ? (
17
+ <p className="font-semibold text-[11px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]">
18
+ {eyebrow}
19
+ </p>
20
+ ) : null}
21
+ <div>
22
+ <h2 className="font-semibold text-2xl tracking-[-0.03em] sm:text-[2rem]">{title}</h2>
23
+ {subtitle ? (
24
+ <p className="mt-2 max-w-[60rem] whitespace-pre-wrap break-words text-[var(--muted-foreground)] text-sm">
25
+ {subtitle}
26
+ </p>
27
+ ) : null}
28
+ </div>
29
+ </div>
30
+ {actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,74 @@
1
+ import { LoadingPanel } from '#/components/loading-panel';
2
+ import { PageHeader } from '#/components/page-header';
3
+ import { Skeleton } from '#/components/ui/skeleton';
4
+
5
+ const loadingColumns = ['Project', 'Threads', 'Tokens', 'Last updated', 'Models', 'Archived'] as const;
6
+ const loadingRows = ['project-a', 'project-b', 'project-c', 'project-d', 'project-e'] as const;
7
+
8
+ export function ProjectsLoadingState() {
9
+ return (
10
+ <div aria-busy="true" aria-live="polite" className="space-y-6">
11
+ <PageHeader
12
+ actions={<Skeleton className="h-10 w-full min-w-[16rem] max-w-[20rem]" />}
13
+ eyebrow="Inventory"
14
+ subtitle="Reading the local Codex database and checking fallback session files."
15
+ title="Codex"
16
+ />
17
+
18
+ <div role="status">
19
+ <LoadingPanel
20
+ description="Scanning project summaries. Large local histories can take a moment."
21
+ title="Loading Codex projects"
22
+ />
23
+ </div>
24
+
25
+ <div className="w-full overflow-x-auto rounded-[1.5rem] border border-[var(--border)] bg-[var(--panel)]">
26
+ <table className="w-full min-w-[56rem]">
27
+ <thead className="bg-[var(--panel-secondary)]">
28
+ <tr className="border-[var(--border)] border-b">
29
+ {loadingColumns.map((column) => (
30
+ <th
31
+ key={column}
32
+ className="h-10 whitespace-nowrap px-4 text-left font-semibold text-[11px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]"
33
+ >
34
+ {column}
35
+ </th>
36
+ ))}
37
+ </tr>
38
+ </thead>
39
+ <tbody>
40
+ {loadingRows.map((rowId, rowIndex) => (
41
+ <tr
42
+ key={rowId}
43
+ aria-label={`Loading project row ${rowIndex + 1}`}
44
+ className="border-[var(--border)] border-b last:border-b-0"
45
+ >
46
+ <td className="px-4 py-3">
47
+ <div className="space-y-2">
48
+ <Skeleton aria-hidden className="h-4 w-44" />
49
+ <Skeleton aria-hidden className="h-3 w-20" />
50
+ </div>
51
+ </td>
52
+ <td className="px-4 py-3">
53
+ <Skeleton aria-hidden className="h-4 w-12" />
54
+ </td>
55
+ <td className="px-4 py-3">
56
+ <Skeleton aria-hidden className="h-4 w-16" />
57
+ </td>
58
+ <td className="px-4 py-3">
59
+ <Skeleton aria-hidden className="h-4 w-28" />
60
+ </td>
61
+ <td className="px-4 py-3">
62
+ <Skeleton aria-hidden className="h-4 w-32" />
63
+ </td>
64
+ <td className="px-4 py-3">
65
+ <Skeleton aria-hidden className="h-4 w-10" />
66
+ </td>
67
+ </tr>
68
+ ))}
69
+ </tbody>
70
+ </table>
71
+ </div>
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,108 @@
1
+ import type { ProjectSummary } from '@spiracha/lib/codex-browser-types';
2
+ import { Link } from '@tanstack/react-router';
3
+ import { createColumnHelper } from '@tanstack/react-table';
4
+ import { MoreHorizontal, Trash2 } from 'lucide-react';
5
+ import { DataTable } from '#/components/data-table';
6
+ import { Button } from '#/components/ui/button';
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuTrigger,
12
+ } from '#/components/ui/dropdown-menu';
13
+ import { formatDateTime, formatList, formatNumber, formatTokens } from '#/lib/formatters';
14
+
15
+ type ProjectsTableProps = {
16
+ projects: ProjectSummary[];
17
+ onDeleteProject: (project: ProjectSummary) => void;
18
+ };
19
+
20
+ const columnHelper = createColumnHelper<ProjectSummary>();
21
+
22
+ const columns = (onDeleteProject: (project: ProjectSummary) => void) =>
23
+ [
24
+ columnHelper.accessor('name', {
25
+ cell: (info) => (
26
+ <Link
27
+ className="block w-[14rem] max-w-[18rem] space-y-1 rounded-md outline-none transition hover:opacity-80 focus-visible:ring-2 focus-visible:ring-[var(--accent)] lg:w-auto"
28
+ params={{ project: info.row.original.name }}
29
+ to="/codex/$project"
30
+ >
31
+ <p className="font-medium underline-offset-2 hover:underline">{info.getValue()}</p>
32
+ <p className="text-[var(--muted-foreground)] text-xs">
33
+ {formatNumber(info.row.original.cwdPaths.length)} cwd path
34
+ {info.row.original.cwdPaths.length === 1 ? '' : 's'}
35
+ </p>
36
+ </Link>
37
+ ),
38
+ header: 'Project',
39
+ }),
40
+ columnHelper.accessor('threadCount', {
41
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
42
+ header: 'Threads',
43
+ }),
44
+ columnHelper.accessor('totalTokens', {
45
+ cell: (info) => <span className="font-mono text-sm">{formatTokens(info.getValue())}</span>,
46
+ header: 'Tokens',
47
+ }),
48
+ columnHelper.accessor('lastUpdatedAtMs', {
49
+ cell: (info) => (
50
+ <span className="text-sm" suppressHydrationWarning>
51
+ {formatDateTime(info.getValue())}
52
+ </span>
53
+ ),
54
+ header: 'Last updated',
55
+ }),
56
+ columnHelper.display({
57
+ cell: (info) => (
58
+ <span className="text-[var(--muted-foreground)] text-sm">
59
+ {formatList(info.row.original.modelNames)}
60
+ </span>
61
+ ),
62
+ header: 'Models',
63
+ id: 'models',
64
+ }),
65
+ columnHelper.accessor('archivedThreadCount', {
66
+ cell: (info) => <span className="text-sm">{formatNumber(info.getValue())}</span>,
67
+ header: 'Archived',
68
+ }),
69
+ columnHelper.display({
70
+ cell: (info) => (
71
+ <DropdownMenu>
72
+ <DropdownMenuTrigger asChild>
73
+ <Button
74
+ className="rounded-full"
75
+ size="icon"
76
+ type="button"
77
+ variant="ghost"
78
+ onClick={(event) => event.stopPropagation()}
79
+ >
80
+ <MoreHorizontal className="size-4" />
81
+ </Button>
82
+ </DropdownMenuTrigger>
83
+ <DropdownMenuContent align="end">
84
+ <DropdownMenuItem
85
+ className="text-[var(--destructive)]"
86
+ onClick={() => onDeleteProject(info.row.original)}
87
+ >
88
+ <Trash2 className="mr-2 size-4" />
89
+ Delete project
90
+ </DropdownMenuItem>
91
+ </DropdownMenuContent>
92
+ </DropdownMenu>
93
+ ),
94
+ header: '',
95
+ id: 'actions',
96
+ }),
97
+ ] as const;
98
+
99
+ export function ProjectsTable({ projects, onDeleteProject }: ProjectsTableProps) {
100
+ return (
101
+ <DataTable
102
+ columns={columns(onDeleteProject)}
103
+ data={projects}
104
+ emptyMessage="No projects match the current search."
105
+ initialSorting={[{ desc: true, id: 'lastUpdatedAtMs' }]}
106
+ />
107
+ );
108
+ }
@@ -0,0 +1,112 @@
1
+ import type { QoderSessionSummary } from '@spiracha/lib/qoder-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 QoderSessionsTableProps = {
18
+ onExportSession: (session: QoderSessionSummary) => void;
19
+ sessions: QoderSessionSummary[];
20
+ };
21
+
22
+ const columnHelper = createColumnHelper<QoderSessionSummary>();
23
+ const defaultSorting: SortingState = [{ desc: true, id: 'lastActive' }];
24
+
25
+ const columns = (onExportSession: (session: QoderSessionSummary) => 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="/qoder-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) => <span className="whitespace-nowrap text-sm">{formatDateTime(info.getValue())}</span>,
42
+ header: 'Updated',
43
+ id: 'lastActive',
44
+ }),
45
+ columnHelper.accessor('status', {
46
+ cell: (info) => <span className="font-mono text-sm">{info.getValue() ?? 'unknown'}</span>,
47
+ header: 'Status',
48
+ }),
49
+ columnHelper.accessor('model', {
50
+ cell: (info) => <span className="font-mono 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: 'Prompts',
56
+ }),
57
+ columnHelper.accessor('fileOperationCount', {
58
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
59
+ header: 'File ops',
60
+ }),
61
+ columnHelper.accessor('snapshotFileCount', {
62
+ cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
63
+ header: 'Snapshots',
64
+ }),
65
+ columnHelper.accessor('executionMode', {
66
+ cell: (info) => <span className="font-mono text-sm">{info.getValue() ?? 'unknown'}</span>,
67
+ header: 'Mode',
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 QoderSessionsTable = ({ onExportSession, sessions }: QoderSessionsTableProps) => {
102
+ const tableColumns = useMemo(() => columns(onExportSession), [onExportSession]);
103
+
104
+ return (
105
+ <DataTable
106
+ columns={tableColumns}
107
+ data={sessions}
108
+ emptyMessage="No Qoder sessions match the current workspace filter."
109
+ initialSorting={defaultSorting}
110
+ />
111
+ );
112
+ };