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.
- package/AGENTS.md +116 -164
- package/README.md +92 -237
- package/apps/ui/AGENTS.md +36 -7
- package/apps/ui/README.md +79 -6
- package/apps/ui/components.json +21 -0
- package/apps/ui/package.json +65 -0
- package/apps/ui/{dist/client → public}/icon.svg +2 -1
- package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
- package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
- package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
- package/apps/ui/src/components/app-shell.tsx +124 -0
- package/apps/ui/src/components/breadcrumbs.tsx +53 -0
- package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
- package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
- package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
- package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
- package/apps/ui/src/components/data-table.tsx +241 -0
- package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
- package/apps/ui/src/components/export-dialog.tsx +184 -0
- package/apps/ui/src/components/json-panel.tsx +17 -0
- package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
- package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/list-search-input.tsx +18 -0
- package/apps/ui/src/components/loading-panel.tsx +23 -0
- package/apps/ui/src/components/metadata-section.tsx +31 -0
- package/apps/ui/src/components/metric-card.tsx +19 -0
- package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
- package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
- package/apps/ui/src/components/page-header.tsx +33 -0
- package/apps/ui/src/components/projects-loading-state.tsx +74 -0
- package/apps/ui/src/components/projects-table.tsx +108 -0
- package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
- package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/recent-threads-list.tsx +52 -0
- package/apps/ui/src/components/reload-error-panel.tsx +20 -0
- package/apps/ui/src/components/text-document-panel.tsx +19 -0
- package/apps/ui/src/components/theme-toggle.tsx +48 -0
- package/apps/ui/src/components/threads-table.tsx +202 -0
- package/apps/ui/src/components/transcript-view.tsx +552 -0
- package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
- package/apps/ui/src/components/ui/badge.tsx +45 -0
- package/apps/ui/src/components/ui/button.tsx +62 -0
- package/apps/ui/src/components/ui/checkbox.tsx +29 -0
- package/apps/ui/src/components/ui/dialog.tsx +137 -0
- package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
- package/apps/ui/src/components/ui/input.tsx +21 -0
- package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
- package/apps/ui/src/components/ui/select.tsx +163 -0
- package/apps/ui/src/components/ui/separator.tsx +28 -0
- package/apps/ui/src/components/ui/sheet.tsx +107 -0
- package/apps/ui/src/components/ui/skeleton.tsx +7 -0
- package/apps/ui/src/components/ui/table.tsx +76 -0
- package/apps/ui/src/components/ui/tabs.tsx +71 -0
- package/apps/ui/src/components/ui/tooltip.tsx +44 -0
- package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
- package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
- package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
- package/apps/ui/src/lib/antigravity-queries.ts +33 -0
- package/apps/ui/src/lib/antigravity-server.ts +128 -0
- package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
- package/apps/ui/src/lib/claude-code-queries.ts +26 -0
- package/apps/ui/src/lib/claude-code-server.ts +75 -0
- package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
- package/apps/ui/src/lib/codex-queries.ts +66 -0
- package/apps/ui/src/lib/codex-server.ts +209 -0
- package/apps/ui/src/lib/cursor-queries.ts +37 -0
- package/apps/ui/src/lib/cursor-server.ts +272 -0
- package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
- package/apps/ui/src/lib/download.ts +166 -0
- package/apps/ui/src/lib/formatters.ts +149 -0
- package/apps/ui/src/lib/kiro-queries.ts +22 -0
- package/apps/ui/src/lib/kiro-server.ts +73 -0
- package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
- package/apps/ui/src/lib/opencode-queries.ts +37 -0
- package/apps/ui/src/lib/opencode-server.ts +73 -0
- package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
- package/apps/ui/src/lib/package-metadata.ts +16 -0
- package/apps/ui/src/lib/path-utils.ts +13 -0
- package/apps/ui/src/lib/qoder-queries.ts +22 -0
- package/apps/ui/src/lib/qoder-server.ts +79 -0
- package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
- package/apps/ui/src/lib/route-search.ts +68 -0
- package/apps/ui/src/lib/settings-store.tsx +59 -0
- package/apps/ui/src/lib/source-session-export-server.ts +56 -0
- package/apps/ui/src/lib/text-filter.ts +22 -0
- package/apps/ui/src/lib/thread-id.ts +3 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
- package/apps/ui/src/lib/utils.ts +7 -0
- package/apps/ui/src/routeTree.gen.ts +730 -0
- package/apps/ui/src/router.tsx +20 -0
- package/apps/ui/src/routes/$threadId.tsx +22 -0
- package/apps/ui/src/routes/__root.tsx +127 -0
- package/apps/ui/src/routes/analytics.tsx +143 -0
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
- package/apps/ui/src/routes/antigravity.index.tsx +51 -0
- package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
- package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
- package/apps/ui/src/routes/api.v1.sources.ts +12 -0
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
- package/apps/ui/src/routes/claude-code.index.tsx +48 -0
- package/apps/ui/src/routes/codex.$project.tsx +390 -0
- package/apps/ui/src/routes/codex.index.tsx +116 -0
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
- package/apps/ui/src/routes/cursor.index.tsx +120 -0
- package/apps/ui/src/routes/index.tsx +124 -0
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
- package/apps/ui/src/routes/kiro.index.tsx +48 -0
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
- package/apps/ui/src/routes/opencode.index.tsx +48 -0
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
- package/apps/ui/src/routes/qoder.index.tsx +48 -0
- package/apps/ui/src/routes/settings.tsx +91 -0
- package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
- package/apps/ui/src/styles.css +122 -0
- package/apps/ui/tsconfig.json +29 -0
- package/apps/ui/vite.config.ts +84 -0
- package/bin/spiracha.ts +32 -0
- package/package.json +50 -60
- package/src/client.ts +290 -0
- package/src/lib/antigravity-db.ts +213 -19
- package/src/lib/antigravity-exporter-types.ts +1 -0
- package/src/lib/claude-code-db.ts +655 -0
- package/src/lib/claude-code-exporter-types.ts +110 -0
- package/src/lib/claude-code-transcript-phase.ts +25 -0
- package/src/lib/claude-code-transcript.ts +180 -0
- package/src/lib/codex-analytics.ts +1 -1
- package/src/lib/codex-browser-db.ts +856 -61
- package/src/lib/codex-browser-export.ts +24 -16
- package/src/lib/codex-browser-types.ts +1 -1
- package/src/lib/codex-thread-cache.ts +17 -1
- package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
- package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
- package/src/lib/concurrency.ts +25 -0
- package/src/lib/conversation-api.ts +792 -0
- package/src/lib/conversation-data/adapter-helpers.ts +110 -0
- package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
- package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
- package/src/lib/conversation-data/codex-adapter.ts +245 -0
- package/src/lib/conversation-data/cursor-adapter.ts +171 -0
- package/src/lib/conversation-data/index.ts +311 -0
- package/src/lib/conversation-data/kiro-adapter.ts +140 -0
- package/src/lib/conversation-data/message-selector.ts +37 -0
- package/src/lib/conversation-data/opencode-adapter.ts +180 -0
- package/src/lib/conversation-data/path-match.ts +79 -0
- package/src/lib/conversation-data/qoder-adapter.ts +230 -0
- package/src/lib/conversation-data/types.ts +116 -0
- package/src/lib/cursor-db.ts +67 -21
- package/src/lib/cursor-exporter-types.ts +0 -19
- package/src/lib/kiro-db.ts +949 -0
- package/src/lib/kiro-exporter-types.ts +100 -0
- package/src/lib/kiro-transcript-phase.ts +41 -0
- package/src/lib/kiro-transcript.ts +115 -0
- package/src/lib/opencode-db.ts +610 -0
- package/src/lib/opencode-exporter-types.ts +129 -0
- package/src/lib/opencode-think-tags.ts +126 -0
- package/src/lib/opencode-transcript-phase.ts +50 -0
- package/src/lib/opencode-transcript.ts +194 -0
- package/src/lib/qoder-acp-client.ts +268 -0
- package/src/lib/qoder-db.ts +1601 -0
- package/src/lib/qoder-exporter-types.ts +114 -0
- package/src/lib/qoder-transcript-phase.ts +41 -0
- package/src/lib/qoder-transcript.ts +112 -0
- package/src/lib/shared.ts +25 -0
- package/src/lib/ui-export-archive.ts +61 -0
- package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
- package/apps/ui/dist/client/assets/analytics-DK2jdvmI.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DmD0sN8X.js +0 -7
- package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
- package/apps/ui/dist/client/assets/antigravity-keychain-panel-BRHVjFAM.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
- package/apps/ui/dist/client/assets/antigravity._workspaceKey-CGV7amas.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-DEEkjlZa.js +0 -1
- package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
- package/apps/ui/dist/client/assets/badge-DfWvPfKF.js +0 -1
- package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
- package/apps/ui/dist/client/assets/createServerFn-CgRRVpBH.js +0 -3
- package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
- package/apps/ui/dist/client/assets/cursor-threads._composerId-Ti2gTm63.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
- package/apps/ui/dist/client/assets/cursor._workspaceKey-DzD_c6j9.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
- package/apps/ui/dist/client/assets/cursor.index-PkCFVcl4.js +0 -2
- package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
- package/apps/ui/dist/client/assets/delete-confirm-dialog-BHRmyLHr.js +0 -11
- package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
- package/apps/ui/dist/client/assets/dist-BhZOxAPP.js +0 -1
- package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
- package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
- package/apps/ui/dist/client/assets/dropdown-menu-Dy5cLzTy.js +0 -1
- package/apps/ui/dist/client/assets/es2015-BkCWttyM.js +0 -41
- package/apps/ui/dist/client/assets/export-dialog-C86SYHJn.js +0 -1
- package/apps/ui/dist/client/assets/formatters-BM9kB7ed.js +0 -1
- package/apps/ui/dist/client/assets/index-CPvAP-jk.js +0 -149
- package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
- package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
- package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-Ckf6muWZ.js +0 -1
- package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
- package/apps/ui/dist/client/assets/projects.index-V5ibNZII.js +0 -3
- package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
- package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
- package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
- package/apps/ui/dist/client/assets/routes-Df2tgpyd.js +0 -1
- package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
- package/apps/ui/dist/client/assets/select-Bf2GwfPR.js +0 -1
- package/apps/ui/dist/client/assets/settings-5VQIzME0.js +0 -1
- package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
- package/apps/ui/dist/client/assets/styles-BhRkXgwB.css +0 -1
- package/apps/ui/dist/client/assets/tabs-OO4VU5KR.js +0 -7
- package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
- package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
- package/apps/ui/dist/client/assets/threads._threadId-BgDhFj2I.js +0 -1
- package/apps/ui/dist/client/assets/useMutation-CC_B7uy5.js +0 -1
- package/apps/ui/dist/client/assets/useQuery-C5By6WKU.js +0 -1
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CRW2kqj0.js +0 -232
- package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
- package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
- package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
- package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BtexWY-K.js +0 -613
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
- package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-zMrR6v6R.js +0 -20
- package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
- package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
- package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
- package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
- package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
- package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
- package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
- package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
- package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
- package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
- package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
- package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
- package/apps/ui/dist/server/assets/codex-server-CNXSJuc2.js +0 -2004
- package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
- package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
- package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
- package/apps/ui/dist/server/assets/cursor-db-cYZEU3WQ.js +0 -830
- package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
- package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
- package/apps/ui/dist/server/assets/cursor-recovery-nq-kR62j.js +0 -361
- package/apps/ui/dist/server/assets/cursor-server-C3q7hrp-.js +0 -213
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BPJFWfJj.js +0 -18
- package/apps/ui/dist/server/assets/cursor-threads._composerId-BkMMTQ3v.js +0 -582
- package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +0 -125
- package/apps/ui/dist/server/assets/cursor._workspaceKey-C5PdTaKB.js +0 -28
- package/apps/ui/dist/server/assets/cursor._workspaceKey-Ckj7apKI.js +0 -401
- package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
- package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
- package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
- package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
- package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
- package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
- package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
- package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
- package/apps/ui/dist/server/assets/export-dialog-BItjWgkZ.js +0 -240
- package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
- package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
- package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
- package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
- package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
- package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
- package/apps/ui/dist/server/assets/projects._project-Bpbc3C-L.js +0 -20
- package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
- package/apps/ui/dist/server/assets/projects._project-CvZho6EQ.js +0 -395
- package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
- package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
- package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
- package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
- package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
- package/apps/ui/dist/server/assets/router-6m-ihwqA.js +0 -410
- package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
- package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
- package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
- package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
- package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
- package/apps/ui/dist/server/assets/shared-CPRNYIql.js +0 -134
- package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
- package/apps/ui/dist/server/assets/start-CI_0bSiY.js +0 -4
- package/apps/ui/dist/server/assets/tabs-CGA13IZM.js +0 -502
- package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
- package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
- package/apps/ui/dist/server/assets/threads._threadId-Bs0yuqO9.js +0 -18
- package/apps/ui/dist/server/assets/threads._threadId-D2GYkVn6.js +0 -639
- package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
- package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
- package/apps/ui/dist/server/server.js +0 -5847
- package/bin/codex-chats-claude.js +0 -5
- package/bin/codex-chats.js +0 -5
- package/bin/spiracha.js +0 -5
- package/src/export-chats.ts +0 -120
- package/src/export-claude.ts +0 -36
- package/src/export-cursor.ts +0 -244
- package/src/lib/claude-exporter.ts +0 -864
- package/src/lib/codex-exporter-cli.ts +0 -277
- package/src/lib/codex-exporter-db.ts +0 -319
- package/src/lib/codex-exporter.ts +0 -115
- package/src/lib/cursor-exporter.ts +0 -266
- package/src/lib/interactive-cli.ts +0 -433
- package/src/lib/native-open.ts +0 -54
- package/src/mcp-server.ts +0 -137
- package/src/spiracha.ts +0 -116
- package/src/ui-cli.ts +0 -310
- /package/apps/ui/{dist/client → public}/manifest.json +0 -0
- /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
|
-
};
|