spiracha 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/AGENTS.md +114 -205
  2. package/README.md +89 -269
  3. package/apps/ui/AGENTS.md +17 -7
  4. package/apps/ui/README.md +34 -8
  5. package/apps/ui/components.json +21 -0
  6. package/apps/ui/package.json +65 -0
  7. package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
  8. package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
  9. package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
  10. package/apps/ui/src/components/app-shell.tsx +124 -0
  11. package/apps/ui/src/components/breadcrumbs.tsx +53 -0
  12. package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
  13. package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
  14. package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
  15. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  16. package/apps/ui/src/components/data-table.tsx +241 -0
  17. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  18. package/apps/ui/src/components/export-dialog.tsx +184 -0
  19. package/apps/ui/src/components/json-panel.tsx +17 -0
  20. package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
  21. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  22. package/apps/ui/src/components/list-search-input.tsx +18 -0
  23. package/apps/ui/src/components/loading-panel.tsx +23 -0
  24. package/apps/ui/src/components/metadata-section.tsx +31 -0
  25. package/apps/ui/src/components/metric-card.tsx +19 -0
  26. package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
  27. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  28. package/apps/ui/src/components/page-header.tsx +33 -0
  29. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  30. package/apps/ui/src/components/projects-table.tsx +108 -0
  31. package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
  32. package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
  33. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  34. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  35. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  36. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  37. package/apps/ui/src/components/threads-table.tsx +202 -0
  38. package/apps/ui/src/components/transcript-view.tsx +552 -0
  39. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  40. package/apps/ui/src/components/ui/badge.tsx +45 -0
  41. package/apps/ui/src/components/ui/button.tsx +62 -0
  42. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  43. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  44. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  45. package/apps/ui/src/components/ui/input.tsx +21 -0
  46. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  47. package/apps/ui/src/components/ui/select.tsx +163 -0
  48. package/apps/ui/src/components/ui/separator.tsx +28 -0
  49. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  50. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  51. package/apps/ui/src/components/ui/table.tsx +76 -0
  52. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  53. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  54. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  55. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  56. package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
  57. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  58. package/apps/ui/src/lib/antigravity-server.ts +128 -0
  59. package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
  60. package/apps/ui/src/lib/claude-code-queries.ts +26 -0
  61. package/apps/ui/src/lib/claude-code-server.ts +75 -0
  62. package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
  63. package/apps/ui/src/lib/codex-queries.ts +66 -0
  64. package/apps/ui/src/lib/codex-server.ts +209 -0
  65. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  66. package/apps/ui/src/lib/cursor-server.ts +272 -0
  67. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  68. package/apps/ui/src/lib/download.ts +166 -0
  69. package/apps/ui/src/lib/formatters.ts +149 -0
  70. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  71. package/apps/ui/src/lib/kiro-server.ts +73 -0
  72. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  73. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  74. package/apps/ui/src/lib/opencode-server.ts +73 -0
  75. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  76. package/apps/ui/src/lib/package-metadata.ts +16 -0
  77. package/apps/ui/src/lib/path-utils.ts +13 -0
  78. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  79. package/apps/ui/src/lib/qoder-server.ts +79 -0
  80. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  81. package/apps/ui/src/lib/route-search.ts +68 -0
  82. package/apps/ui/src/lib/settings-store.tsx +59 -0
  83. package/apps/ui/src/lib/source-session-export-server.ts +56 -0
  84. package/apps/ui/src/lib/text-filter.ts +22 -0
  85. package/apps/ui/src/lib/thread-id.ts +3 -0
  86. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  87. package/apps/ui/src/lib/utils.ts +7 -0
  88. package/apps/ui/src/routeTree.gen.ts +730 -0
  89. package/apps/ui/src/router.tsx +20 -0
  90. package/apps/ui/src/routes/$threadId.tsx +22 -0
  91. package/apps/ui/src/routes/__root.tsx +127 -0
  92. package/apps/ui/src/routes/analytics.tsx +143 -0
  93. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
  94. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
  95. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  96. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  97. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  98. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
  99. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  100. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  101. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  102. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
  103. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
  104. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  105. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  106. package/apps/ui/src/routes/codex.index.tsx +116 -0
  107. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  108. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
  109. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  110. package/apps/ui/src/routes/index.tsx +124 -0
  111. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
  112. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
  113. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  114. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
  115. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
  116. package/apps/ui/src/routes/opencode.index.tsx +48 -0
  117. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
  118. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
  119. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  120. package/apps/ui/src/routes/settings.tsx +91 -0
  121. package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
  122. package/apps/ui/src/styles.css +122 -0
  123. package/apps/ui/tsconfig.json +29 -0
  124. package/apps/ui/vite.config.ts +84 -0
  125. package/bin/spiracha.ts +32 -0
  126. package/package.json +46 -73
  127. package/src/client.ts +290 -0
  128. package/src/lib/antigravity-db.ts +213 -19
  129. package/src/lib/antigravity-exporter-types.ts +1 -0
  130. package/src/lib/codex-analytics.ts +1 -1
  131. package/src/lib/codex-browser-db.ts +410 -74
  132. package/src/lib/codex-browser-export.ts +6 -14
  133. package/src/lib/codex-browser-types.ts +1 -1
  134. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  135. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
  136. package/src/lib/conversation-api.ts +792 -0
  137. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  138. package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
  139. package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
  140. package/src/lib/conversation-data/codex-adapter.ts +245 -0
  141. package/src/lib/conversation-data/cursor-adapter.ts +171 -0
  142. package/src/lib/conversation-data/index.ts +311 -0
  143. package/src/lib/conversation-data/kiro-adapter.ts +140 -0
  144. package/src/lib/conversation-data/message-selector.ts +37 -0
  145. package/src/lib/conversation-data/opencode-adapter.ts +180 -0
  146. package/src/lib/conversation-data/path-match.ts +79 -0
  147. package/src/lib/conversation-data/qoder-adapter.ts +230 -0
  148. package/src/lib/conversation-data/types.ts +116 -0
  149. package/src/lib/cursor-db.ts +67 -21
  150. package/src/lib/cursor-exporter-types.ts +0 -19
  151. package/src/lib/qoder-acp-client.ts +268 -0
  152. package/src/lib/qoder-db.ts +1601 -0
  153. package/src/lib/qoder-exporter-types.ts +114 -0
  154. package/src/lib/qoder-transcript-phase.ts +41 -0
  155. package/src/lib/qoder-transcript.ts +112 -0
  156. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  157. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +0 -1
  158. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +0 -7
  159. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  160. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +0 -1
  161. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +0 -1
  162. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  163. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +0 -1
  164. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  165. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +0 -1
  166. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  167. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +0 -1
  168. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +0 -1
  169. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +0 -1
  170. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +0 -1
  171. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +0 -1
  172. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +0 -1
  173. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +0 -3
  174. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  175. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +0 -1
  176. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +0 -1
  177. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  178. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +0 -2
  179. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  180. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  181. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +0 -7
  182. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  183. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +0 -5
  184. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +0 -1
  185. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  186. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  187. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +0 -1
  188. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +0 -41
  189. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +0 -1
  190. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +0 -1
  191. package/apps/ui/dist/client/assets/index-C-STgIH4.js +0 -166
  192. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  193. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +0 -1
  194. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +0 -1
  195. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +0 -1
  196. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +0 -1
  197. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +0 -1
  198. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +0 -1
  199. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  200. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +0 -1
  201. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +0 -8
  202. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +0 -1
  203. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +0 -1
  204. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +0 -1
  205. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +0 -1
  206. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  207. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +0 -1
  208. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +0 -1
  209. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  210. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +0 -3
  211. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  212. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  213. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  214. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  215. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +0 -1
  216. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  217. package/apps/ui/dist/client/assets/select-CYF6pF37.js +0 -1
  218. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +0 -1
  219. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  220. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +0 -1
  221. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +0 -7
  222. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  223. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +0 -1
  224. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  225. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +0 -1
  226. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +0 -1
  227. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +0 -1
  228. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +0 -387
  229. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  230. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  231. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  232. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  233. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +0 -613
  234. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  235. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-eS87Dx7g.js +0 -20
  236. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  237. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  238. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  239. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  240. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  241. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  242. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  243. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  244. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  245. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  246. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  247. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  248. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  249. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +0 -361
  250. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +0 -37
  251. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +0 -72
  252. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +0 -18
  253. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +0 -473
  254. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +0 -11
  255. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +0 -139
  256. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +0 -10
  257. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +0 -28
  258. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +0 -11
  259. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +0 -191
  260. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +0 -92
  261. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +0 -11
  262. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  263. package/apps/ui/dist/server/assets/codex-server-CGXU4H8-.js +0 -2294
  264. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  265. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  266. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  267. package/apps/ui/dist/server/assets/cursor-db-CZnYy7aT.js +0 -830
  268. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  269. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  270. package/apps/ui/dist/server/assets/cursor-recovery-CXpO8KcD.js +0 -361
  271. package/apps/ui/dist/server/assets/cursor-server-DNu52A3K.js +0 -213
  272. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  273. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +0 -582
  274. package/apps/ui/dist/server/assets/cursor-threads._composerId-p9-ARAzc.js +0 -18
  275. package/apps/ui/dist/server/assets/cursor-transcript-B7tGnqtn.js +0 -125
  276. package/apps/ui/dist/server/assets/cursor._workspaceKey-CZuohIUL.js +0 -28
  277. package/apps/ui/dist/server/assets/cursor._workspaceKey-DPp5JSjy.js +0 -401
  278. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  279. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  280. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  281. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  282. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  283. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  284. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  285. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  286. package/apps/ui/dist/server/assets/export-dialog-D88ze9Gy.js +0 -240
  287. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  288. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +0 -552
  289. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +0 -37
  290. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +0 -72
  291. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +0 -11
  292. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +0 -449
  293. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +0 -18
  294. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +0 -112
  295. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +0 -27
  296. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +0 -11
  297. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +0 -28
  298. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +0 -193
  299. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +0 -11
  300. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +0 -93
  301. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  302. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  303. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  304. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +0 -397
  305. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +0 -50
  306. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +0 -72
  307. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +0 -518
  308. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +0 -18
  309. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +0 -11
  310. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +0 -87
  311. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +0 -141
  312. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +0 -32
  313. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +0 -11
  314. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +0 -28
  315. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +0 -207
  316. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +0 -11
  317. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +0 -93
  318. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  319. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  320. package/apps/ui/dist/server/assets/projects._project-4io5LO0E.js +0 -20
  321. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  322. package/apps/ui/dist/server/assets/projects._project-D6ZoGhJ8.js +0 -395
  323. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  324. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  325. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  326. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  327. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  328. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +0 -518
  329. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  330. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  331. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  332. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  333. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  334. package/apps/ui/dist/server/assets/shared-DyhChtHf.js +0 -137
  335. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +0 -38
  336. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  337. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +0 -4
  338. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +0 -501
  339. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  340. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +0 -45
  341. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  342. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +0 -648
  343. package/apps/ui/dist/server/assets/threads._threadId-Ci0S4mKU.js +0 -18
  344. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  345. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  346. package/apps/ui/dist/server/server.js +0 -5895
  347. package/bin/codex-chats-claude.js +0 -5
  348. package/bin/codex-chats.js +0 -5
  349. package/bin/spiracha.js +0 -5
  350. package/src/export-chats.ts +0 -120
  351. package/src/export-claude.ts +0 -36
  352. package/src/export-cursor.ts +0 -244
  353. package/src/lib/claude-exporter.ts +0 -864
  354. package/src/lib/codex-exporter-cli.ts +0 -277
  355. package/src/lib/codex-exporter-db.ts +0 -319
  356. package/src/lib/codex-exporter.ts +0 -115
  357. package/src/lib/cursor-exporter.ts +0 -266
  358. package/src/lib/interactive-cli.ts +0 -433
  359. package/src/lib/native-open.ts +0 -54
  360. package/src/mcp-server.ts +0 -137
  361. package/src/spiracha.ts +0 -116
  362. package/src/ui-cli.ts +0 -310
  363. /package/apps/ui/{dist/client → public}/icon.svg +0 -0
  364. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  365. /package/apps/ui/{dist/client → public}/robots.txt +0 -0
@@ -1,266 +0,0 @@
1
- import path from 'node:path';
2
- import {
3
- findCursorWorkspaceGroups,
4
- listCursorThreadsForGroup,
5
- listCursorWorkspaceGroups,
6
- readCursorThreadHead,
7
- readCursorThreadTranscriptWithAgentFiles,
8
- } from './cursor-db';
9
- import {
10
- type CursorCliOptions,
11
- type CursorExportedFile,
12
- type CursorExportRunResult,
13
- type CursorThreadSummary,
14
- type CursorWorkspaceGroup,
15
- getCursorGlobalDbPath,
16
- resolveCursorUserDir,
17
- } from './cursor-exporter-types';
18
- import { renderCursorTranscript } from './cursor-transcript';
19
- import { CliUsageError, type ExportFormat, expandHome, writeExportFile } from './shared';
20
-
21
- export const DEFAULT_CURSOR_OUTPUT_DIR = path.join(process.cwd(), 'exports', 'cursor');
22
-
23
- const resolveSingleGroup = (groups: CursorWorkspaceGroup[], query: string): CursorWorkspaceGroup => {
24
- const matched = findCursorWorkspaceGroups(groups, query);
25
- if (matched.length === 0) {
26
- throw new Error(`No Cursor workspace matched query: ${query}`);
27
- }
28
-
29
- if (matched.length > 1) {
30
- const keys = matched.map((group) => ` - ${group.key}`).join('\n');
31
- throw new Error(
32
- `Query "${query}" matched multiple Cursor workspaces. Refine it to a folder path or .code-workspace file:\n${keys}`,
33
- );
34
- }
35
-
36
- return matched[0]!;
37
- };
38
-
39
- const collectThreadsToExport = async (
40
- options: CursorCliOptions,
41
- ): Promise<{ threads: CursorThreadSummary[]; missingThreadIds: string[] }> => {
42
- if (options.threadIds.length > 0) {
43
- return collectThreadsById(options);
44
- }
45
-
46
- if (!options.workspaceQuery) {
47
- throw new CliUsageError('Provide a workspace (--workspace) or one or more --thread ids to export.');
48
- }
49
-
50
- const groups = await listCursorWorkspaceGroups(options.userDir);
51
- const group = resolveSingleGroup(groups, options.workspaceQuery);
52
- const threads = await listCursorThreadsForGroup(group, options.userDir);
53
- return { missingThreadIds: [], threads: threads.filter((thread) => thread.bubbleCount > 0) };
54
- };
55
-
56
- // Export by id reads the global head record directly so we avoid scanning every workspace bucket.
57
- const collectThreadsById = async (
58
- options: CursorCliOptions,
59
- ): Promise<{ threads: CursorThreadSummary[]; missingThreadIds: string[] }> => {
60
- const globalDbPath = getCursorGlobalDbPath(options.userDir);
61
- const threads: CursorThreadSummary[] = [];
62
- const missingThreadIds: string[] = [];
63
-
64
- for (const threadId of options.threadIds) {
65
- const head = readCursorThreadHead(globalDbPath, threadId);
66
- if (!head) {
67
- missingThreadIds.push(threadId);
68
- continue;
69
- }
70
-
71
- threads.push({
72
- bubbleBytes: 0,
73
- bubbleCount: head.orderedBubbleIds.length,
74
- bucketId: null,
75
- composerId: head.composerId,
76
- createdAtMs: head.createdAtMs,
77
- lastUpdatedAtMs: head.lastUpdatedAtMs,
78
- mode: head.mode,
79
- name: head.name ?? '(untitled)',
80
- transcriptDirs: [],
81
- workspaceKey: '',
82
- workspaceLabel: '',
83
- });
84
- }
85
-
86
- return { missingThreadIds, threads };
87
- };
88
-
89
- export const runCursorExport = async (options: CursorCliOptions): Promise<CursorExportRunResult> => {
90
- const globalDbPath = getCursorGlobalDbPath(options.userDir);
91
- const outputDir = options.outputDir ?? DEFAULT_CURSOR_OUTPUT_DIR;
92
- const { threads, missingThreadIds } = await collectThreadsToExport(options);
93
- const files: CursorExportedFile[] = [];
94
-
95
- for (const thread of threads) {
96
- const exported = await exportSingleThread(thread, globalDbPath, outputDir, options);
97
- if (exported) {
98
- files.push(exported);
99
- } else {
100
- missingThreadIds.push(thread.composerId);
101
- }
102
- }
103
-
104
- return {
105
- exportedCount: files.length,
106
- files,
107
- missingThreadIds,
108
- outputDir,
109
- };
110
- };
111
-
112
- const exportSingleThread = async (
113
- thread: CursorThreadSummary,
114
- globalDbPath: string,
115
- outputDir: string,
116
- options: CursorCliOptions,
117
- ): Promise<CursorExportedFile | null> => {
118
- const transcript = await readCursorThreadTranscriptWithAgentFiles(globalDbPath, thread.composerId, options.userDir);
119
- if (!transcript) {
120
- return null;
121
- }
122
-
123
- const content = renderCursorTranscript(transcript, options);
124
- if (!content) {
125
- return null;
126
- }
127
-
128
- const outputPath = path.join(outputDir, `${thread.composerId}.${options.outputFormat}`);
129
- await writeExportFile(outputPath, content);
130
-
131
- return { composerId: thread.composerId, outputPath };
132
- };
133
-
134
- const parseExportFormat = (value: string): ExportFormat => {
135
- if (value === 'md' || value === 'txt') {
136
- return value;
137
- }
138
-
139
- throw new CliUsageError(`Unsupported output format: ${value}`);
140
- };
141
-
142
- const requireValue = (value: string | undefined, flag: string): string => {
143
- if (!value || (value.startsWith('-') && value !== '-')) {
144
- throw new CliUsageError(`Missing value for ${flag}`);
145
- }
146
-
147
- return value;
148
- };
149
-
150
- export const parseCursorCliArgs = (argv: string[]): CursorCliOptions => {
151
- const state: CursorCliOptions = {
152
- includeCommentary: false,
153
- includeMetadata: true,
154
- includeTools: false,
155
- outputDir: null,
156
- outputFormat: 'md',
157
- threadIds: [],
158
- userDir: resolveCursorUserDir(),
159
- workspaceQuery: null,
160
- };
161
-
162
- for (let index = 0; index < argv.length; index += 1) {
163
- index = applyCursorCliArg(argv, index, state);
164
- }
165
-
166
- return state;
167
- };
168
-
169
- const applyCursorCliArg = (argv: string[], index: number, state: CursorCliOptions): number => {
170
- const arg = argv[index] as string;
171
- const flagIndex = applyCursorFlag(arg, state);
172
- if (flagIndex) {
173
- return index;
174
- }
175
-
176
- return applyCursorValueArg(argv, index, arg, state);
177
- };
178
-
179
- const applyCursorFlag = (arg: string, state: CursorCliOptions): boolean => {
180
- if (arg === '--tools') {
181
- state.includeTools = true;
182
- return true;
183
- }
184
-
185
- if (arg === '--commentary' || arg === '--reasoning') {
186
- state.includeCommentary = true;
187
- return true;
188
- }
189
-
190
- if (arg === '--no-metadata') {
191
- state.includeMetadata = false;
192
- return true;
193
- }
194
-
195
- return false;
196
- };
197
-
198
- const applyCursorValueArg = (argv: string[], index: number, arg: string, state: CursorCliOptions): number => {
199
- if (arg === '--workspace' || arg === '-w') {
200
- state.workspaceQuery = expandHome(requireValue(argv[index + 1], arg));
201
- return index + 1;
202
- }
203
-
204
- if (arg === '--thread' || arg === '-t') {
205
- state.threadIds.push(requireValue(argv[index + 1], arg));
206
- return index + 1;
207
- }
208
-
209
- if (arg === '--output' || arg === '-o') {
210
- state.outputDir = expandHome(requireValue(argv[index + 1], arg));
211
- return index + 1;
212
- }
213
-
214
- if (arg === '--user-dir') {
215
- state.userDir = expandHome(requireValue(argv[index + 1], arg));
216
- return index + 1;
217
- }
218
-
219
- if (arg.startsWith('--output-format=')) {
220
- state.outputFormat = parseExportFormat(arg.slice('--output-format='.length));
221
- return index;
222
- }
223
-
224
- if (arg === '--output-format') {
225
- state.outputFormat = parseExportFormat(requireValue(argv[index + 1], '--output-format'));
226
- return index + 1;
227
- }
228
-
229
- if (!arg.startsWith('-') && !state.workspaceQuery && state.threadIds.length === 0) {
230
- state.workspaceQuery = expandHome(arg);
231
- return index;
232
- }
233
-
234
- throw new CliUsageError(`Unknown argument: ${arg}`);
235
- };
236
-
237
- export const getCursorHelpText = (): string => {
238
- return [
239
- 'Export, recover, and prune local Cursor Agent/Composer threads.',
240
- '',
241
- 'Usage:',
242
- ' spiracha cursor list [query]',
243
- ' spiracha cursor export --workspace NAME [options]',
244
- ' spiracha cursor export --thread COMPOSER_ID [--thread ...] [options]',
245
- ' spiracha cursor recover <workspace> [--apply]',
246
- ' spiracha cursor prune --workspace NAME [--thread ID ...] [--apply]',
247
- '',
248
- 'Export options:',
249
- ' --workspace, -w Workspace folder name, path, or .code-workspace file',
250
- ' --thread, -t Composer/thread id (repeatable)',
251
- ' --output, -o Output directory (default: ./exports/cursor)',
252
- ' --output-format md or txt (default: md)',
253
- ' --tools Include tool calls and their results',
254
- ' --commentary Include assistant reasoning blocks',
255
- ' --no-metadata Omit the metadata header block',
256
- ' --user-dir Override the Cursor User data directory',
257
- '',
258
- 'Recover/prune:',
259
- ' --apply Perform writes (default is a dry run). Quit Cursor first.',
260
- '',
261
- 'Examples:',
262
- ' spiracha cursor list',
263
- ' spiracha cursor export --workspace gun-twizzle --tools --commentary',
264
- ' spiracha cursor recover gun-twizzle --apply',
265
- ].join('\n');
266
- };
@@ -1,433 +0,0 @@
1
- import { access, lstat } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { stdin as input, stdout as output } from 'node:process';
4
- import { createInterface, type Interface } from 'node:readline/promises';
5
- import { checkbox } from '@inquirer/prompts';
6
- import { type ClaudeCliOptions, runClaudeExport } from './claude-exporter';
7
- import { resolveCodexThreadDbPath, withReadonlyDb } from './codex-browser-db';
8
- import { type CodexCliOptions, runCodexExport } from './codex-exporter';
9
- import { DEFAULT_INPUT_DIR } from './codex-exporter-types';
10
- import { type ExportFormat, expandHome, getPortablePathBasename } from './shared';
11
-
12
- type InteractiveTargetKind =
13
- | 'codex_threads'
14
- | 'codex_project'
15
- | 'codex_projects_multi'
16
- | 'codex_cwd'
17
- | 'claude_path'
18
- | 'unknown';
19
-
20
- type InteractiveInference = {
21
- kind: InteractiveTargetKind;
22
- value: string | null;
23
- };
24
-
25
- export type InteractiveExportResult =
26
- | {
27
- mode: 'codex';
28
- outputDir: string;
29
- exportedCount: number;
30
- missingThreadIds: string[];
31
- files: { sourcePath: string; outputPath: string; threadId: string | null }[];
32
- }
33
- | {
34
- mode: 'claude';
35
- outputPath: string;
36
- sourcePath: string;
37
- };
38
-
39
- export const runInteractiveExport = async (): Promise<InteractiveExportResult> => {
40
- const rl = createPromptInterface();
41
-
42
- try {
43
- output.write('Interactive export mode\n\n');
44
-
45
- const initial = (
46
- await rl.question(
47
- 'Paste a Codex deeplink/thread id, project name, cwd path, or Claude export path.\nLeave blank to pick from a menu.\n> ',
48
- )
49
- ).trim();
50
-
51
- const inferred = await inferInteractiveTarget(initial);
52
- const selection = inferred.kind === 'unknown' ? await promptForTargetKind(rl) : inferred.kind;
53
-
54
- switch (selection) {
55
- case 'codex_threads':
56
- return await runInteractiveCodexThreads(rl, inferred);
57
- case 'codex_project':
58
- return await runInteractiveCodexProject(rl, inferred);
59
- case 'codex_projects_multi':
60
- return await runInteractiveCodexProjectsMulti(rl);
61
- case 'codex_cwd':
62
- return await runInteractiveCodexCwd(rl, inferred);
63
- case 'claude_path':
64
- return await runInteractiveClaude(rl, inferred);
65
- default:
66
- throw new Error('Unsupported interactive selection');
67
- }
68
- } finally {
69
- rl.close();
70
- }
71
- };
72
-
73
- export const inferInteractiveTarget = async (value: string): Promise<InteractiveInference> => {
74
- const trimmed = value.trim();
75
- if (!trimmed) {
76
- return { kind: 'unknown', value: null };
77
- }
78
-
79
- const expanded = expandHome(trimmed);
80
- const pathStats = await lstat(expanded).catch(() => null);
81
-
82
- if (pathStats) {
83
- return await inferInteractiveTargetFromPath(expanded, pathStats);
84
- }
85
-
86
- return inferInteractiveTargetFromText(trimmed, expanded);
87
- };
88
-
89
- const inferInteractiveTargetFromPath = async (
90
- expanded: string,
91
- pathStats: Awaited<ReturnType<typeof lstat>>,
92
- ): Promise<InteractiveInference> => {
93
- if (pathStats.isDirectory()) {
94
- const metadataExists = await access(path.join(expanded, 'metadata.json'))
95
- .then(() => true)
96
- .catch(() => false);
97
- return {
98
- kind: metadataExists ? 'claude_path' : 'codex_cwd',
99
- value: expanded,
100
- };
101
- }
102
-
103
- if (pathStats.isFile()) {
104
- return {
105
- kind: expanded.endsWith('.jsonl') ? 'claude_path' : 'unknown',
106
- value: expanded,
107
- };
108
- }
109
-
110
- return { kind: 'unknown', value: expanded };
111
- };
112
-
113
- const inferInteractiveTargetFromText = (trimmed: string, expanded: string): InteractiveInference => {
114
- if (trimmed.startsWith('codex://threads/') || isRawThreadId(trimmed)) {
115
- return { kind: 'codex_threads', value: trimmed };
116
- }
117
-
118
- if (trimmed.includes(path.sep) || trimmed.startsWith('~')) {
119
- return { kind: 'codex_cwd', value: expanded };
120
- }
121
-
122
- return { kind: 'codex_project', value: trimmed };
123
- };
124
-
125
- const promptForTargetKind = async (rl: Interface): Promise<Exclude<InteractiveTargetKind, 'unknown'>> => {
126
- output.write(
127
- [
128
- '',
129
- 'What do you want to export?',
130
- '1. Specific Codex thread(s)',
131
- '2. Codex project name',
132
- '3. Exact Codex cwd path',
133
- '4. Claude transcript file or export directory',
134
- '5. Select one or more Codex projects',
135
- '',
136
- ].join('\n'),
137
- );
138
-
139
- while (true) {
140
- const choice = (await rl.question('Choose 1-5: ')).trim();
141
- if (choice === '1') {
142
- return 'codex_threads';
143
- }
144
- if (choice === '2') {
145
- return 'codex_project';
146
- }
147
- if (choice === '3') {
148
- return 'codex_cwd';
149
- }
150
- if (choice === '4') {
151
- return 'claude_path';
152
- }
153
- if (choice === '5') {
154
- return 'codex_projects_multi';
155
- }
156
-
157
- output.write('Please enter 1, 2, 3, 4, or 5.\n');
158
- }
159
- };
160
-
161
- const runInteractiveCodexThreads = async (
162
- rl: Interface,
163
- inferred: InteractiveInference,
164
- ): Promise<InteractiveExportResult> => {
165
- const dbPath = resolveInteractiveDbPath();
166
- const raw =
167
- inferred.kind === 'codex_threads' && inferred.value
168
- ? inferred.value
169
- : (
170
- await rl.question(
171
- 'Enter one or more Codex deeplinks or raw thread ids, separated by commas or spaces:\n> ',
172
- )
173
- ).trim();
174
-
175
- const threadIds = normalizeInteractiveThreadSelections(raw);
176
- if (threadIds.length === 0) {
177
- throw new Error('At least one Codex thread id or deeplink is required.');
178
- }
179
-
180
- const options = await promptForCommonCodexOptions(rl, dbPath, {
181
- cwdFilter: null,
182
- projectFilter: null,
183
- threadIds,
184
- });
185
- const result = await runCodexExport(options);
186
- return { mode: 'codex', ...result };
187
- };
188
-
189
- const runInteractiveCodexProject = async (
190
- rl: Interface,
191
- inferred: InteractiveInference,
192
- ): Promise<InteractiveExportResult> => {
193
- const dbPath = resolveInteractiveDbPath();
194
- const project = (
195
- inferred.kind === 'codex_project' && inferred.value
196
- ? inferred.value
197
- : (await rl.question('Enter the Codex project name (cwd basename):\n> ')).trim()
198
- ).trim();
199
-
200
- if (!project) {
201
- throw new Error('A project name is required.');
202
- }
203
-
204
- const options = await promptForCommonCodexOptions(rl, dbPath, {
205
- cwdFilter: null,
206
- projectFilter: project,
207
- threadIds: [],
208
- });
209
- const result = await runCodexExport(options);
210
- return { mode: 'codex', ...result };
211
- };
212
-
213
- const runInteractiveCodexProjectsMulti = async (rl: Interface): Promise<InteractiveExportResult> => {
214
- const dbPath = resolveInteractiveDbPath();
215
- const projects = listCodexProjects(dbPath);
216
- if (projects.length === 0) {
217
- throw new Error(`No Codex projects found in ${dbPath}.`);
218
- }
219
-
220
- output.write('Use Space to toggle projects, and Enter to confirm.\n');
221
- // Inquirer manages the TTY directly; reopen readline afterwards for follow-up prompts.
222
- rl.close();
223
- const selectedProjects = await checkbox({
224
- choices: projects.map((project) => ({ name: project, value: project })),
225
- message: 'Select Codex project(s) to export:',
226
- pageSize: 15,
227
- });
228
-
229
- if (selectedProjects.length === 0) {
230
- throw new Error('At least one project must be selected.');
231
- }
232
-
233
- const threadIds = listThreadIdsForProjects(dbPath, selectedProjects);
234
- if (threadIds.length === 0) {
235
- throw new Error('No threads found for the selected projects.');
236
- }
237
-
238
- const followupRl = createPromptInterface();
239
- try {
240
- const options = await promptForCommonCodexOptions(followupRl, dbPath, {
241
- cwdFilter: null,
242
- projectFilter: null,
243
- threadIds,
244
- });
245
- const result = await runCodexExport(options);
246
- return { mode: 'codex', ...result };
247
- } finally {
248
- followupRl.close();
249
- }
250
- };
251
-
252
- const runInteractiveCodexCwd = async (
253
- rl: Interface,
254
- inferred: InteractiveInference,
255
- ): Promise<InteractiveExportResult> => {
256
- const dbPath = resolveInteractiveDbPath();
257
- const cwdInput =
258
- inferred.kind === 'codex_cwd' && inferred.value
259
- ? inferred.value
260
- : (await rl.question('Enter the exact Codex cwd path:\n> ')).trim();
261
- const cwdFilter = expandHome(cwdInput);
262
-
263
- if (!cwdFilter) {
264
- throw new Error('A cwd path is required.');
265
- }
266
-
267
- const options = await promptForCommonCodexOptions(rl, dbPath, {
268
- cwdFilter,
269
- projectFilter: null,
270
- threadIds: [],
271
- });
272
- const result = await runCodexExport(options);
273
- return { mode: 'codex', ...result };
274
- };
275
-
276
- const runInteractiveClaude = async (
277
- rl: Interface,
278
- inferred: InteractiveInference,
279
- ): Promise<InteractiveExportResult> => {
280
- const inputPath = expandHome(
281
- inferred.kind === 'claude_path' && inferred.value
282
- ? inferred.value
283
- : (await rl.question('Enter the Claude transcript .jsonl file or export directory:\n> ')).trim(),
284
- );
285
-
286
- if (!inputPath) {
287
- throw new Error('A Claude transcript path is required.');
288
- }
289
-
290
- const outputFormat = await promptForOutputFormat(rl);
291
- const includeTools = await promptYesNo(rl, 'Include tool output? [y/N]: ', false);
292
- const outputPath = await promptOptionalPath(rl, 'Optional output path or directory (leave blank for default):\n> ');
293
-
294
- const result = await runClaudeExport({
295
- includeTools,
296
- inputPath,
297
- outputFormat,
298
- outputPath,
299
- } satisfies ClaudeCliOptions);
300
-
301
- return { mode: 'claude', ...result };
302
- };
303
-
304
- const promptForCommonCodexOptions = async (
305
- rl: Interface,
306
- dbPath: string,
307
- target: Pick<CodexCliOptions, 'threadIds' | 'cwdFilter' | 'projectFilter'>,
308
- ): Promise<CodexCliOptions> => {
309
- const outputFormat = await promptForOutputFormat(rl);
310
- const includeMetadata = await promptYesNo(rl, 'Include metadata? [Y/n]: ', true);
311
- const includeCommentary = await promptYesNo(rl, 'Include commentary messages? [y/N]: ', false);
312
- const includeTools = await promptYesNo(rl, 'Include tool logs? [y/N]: ', false);
313
- const flat = await promptYesNo(rl, 'Write to a flat output folder? [y/N]: ', false);
314
- const outputDir = await promptOptionalPath(rl, 'Optional output directory (leave blank for default):\n> ');
315
-
316
- return {
317
- cwdFilter: target.cwdFilter,
318
- dbPath,
319
- flat,
320
- includeCommentary,
321
- includeMetadata,
322
- includeTools,
323
- inputDir: DEFAULT_INPUT_DIR,
324
- outputDir: outputDir ?? resolveInteractiveOutputDir(target.cwdFilter),
325
- outputFormat,
326
- projectFilter: target.projectFilter,
327
- threadIds: target.threadIds,
328
- };
329
- };
330
-
331
- const resolveInteractiveOutputDir = (cwdFilter: string | null) => {
332
- if (cwdFilter) {
333
- const basename = getPortablePathBasename(cwdFilter);
334
- if (basename) {
335
- return path.join(process.cwd(), basename);
336
- }
337
- }
338
-
339
- return path.join(process.cwd(), 'exports');
340
- };
341
-
342
- const promptForOutputFormat = async (rl: Interface): Promise<ExportFormat> => {
343
- output.write(['', 'Output format:', '1. Markdown (.md)', '2. Plain text (.txt)', ''].join('\n'));
344
-
345
- while (true) {
346
- const choice = (await rl.question('Choose 1-2 [1]: ')).trim();
347
- if (!choice || choice === '1') {
348
- return 'md';
349
- }
350
- if (choice === '2') {
351
- return 'txt';
352
- }
353
-
354
- output.write('Please enter 1 or 2.\n');
355
- }
356
- };
357
-
358
- const promptYesNo = async (rl: Interface, prompt: string, defaultValue: boolean): Promise<boolean> => {
359
- while (true) {
360
- const answer = (await rl.question(prompt)).trim().toLowerCase();
361
- if (!answer) {
362
- return defaultValue;
363
- }
364
- if (answer === 'y' || answer === 'yes') {
365
- return true;
366
- }
367
- if (answer === 'n' || answer === 'no') {
368
- return false;
369
- }
370
-
371
- output.write('Please answer y or n.\n');
372
- }
373
- };
374
-
375
- const promptOptionalPath = async (rl: Interface, prompt: string): Promise<string | null> => {
376
- const answer = (await rl.question(prompt)).trim();
377
- return answer ? expandHome(answer) : null;
378
- };
379
-
380
- const normalizeInteractiveThreadSelections = (value: string): string[] => {
381
- const rawTokens = value
382
- .split(/[,\s]+/)
383
- .map((token) => token.trim())
384
- .filter(Boolean);
385
-
386
- const threadIds = rawTokens.map((token) => {
387
- if (token.startsWith('codex://threads/')) {
388
- return token.replace(/^codex:\/\/threads\//u, '');
389
- }
390
- if (isRawThreadId(token)) {
391
- return token;
392
- }
393
-
394
- throw new Error(`Unsupported thread selection: ${token}`);
395
- });
396
-
397
- return [...new Set(threadIds)];
398
- };
399
-
400
- const listCodexProjects = (dbPath: string): string[] => {
401
- return withReadonlyDb(dbPath, (db) => {
402
- const rows = db.query("SELECT DISTINCT cwd FROM threads WHERE cwd IS NOT NULL AND cwd != ''").all() as Array<{
403
- cwd: string;
404
- }>;
405
- return [...new Set(rows.map((row) => getPortablePathBasename(row.cwd)).filter(Boolean))].sort();
406
- });
407
- };
408
-
409
- const resolveInteractiveDbPath = (): string => {
410
- return resolveCodexThreadDbPath();
411
- };
412
-
413
- const listThreadIdsForProjects = (dbPath: string, projectNames: string[]): string[] => {
414
- if (projectNames.length === 0) {
415
- return [];
416
- }
417
-
418
- return withReadonlyDb(dbPath, (db) => {
419
- const projectNameSet = new Set(projectNames);
420
- const rows = db
421
- .query("SELECT id, cwd FROM threads WHERE cwd IS NOT NULL AND cwd != '' ORDER BY updated_at DESC")
422
- .all() as Array<{ id: string; cwd: string }>;
423
- return rows.filter((row) => projectNameSet.has(getPortablePathBasename(row.cwd))).map((row) => row.id);
424
- });
425
- };
426
-
427
- const createPromptInterface = (): Interface => {
428
- return createInterface({ input, output });
429
- };
430
-
431
- const isRawThreadId = (value: string): boolean => {
432
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value);
433
- };