@juspay/shooter 1.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/.claude/hooks/notifier.cjs +1431 -0
- package/.claude/settings.json +162 -0
- package/README.md +515 -0
- package/bin/shooter.cjs +141 -0
- package/build/client/_app/immutable/assets/0.CM9Hl6d-.css +1 -0
- package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.br +0 -0
- package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.CAShZ7lQ.css +1 -0
- package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.br +1 -0
- package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.C0uFg0IS.css +1 -0
- package/build/client/_app/immutable/assets/3.C0uFg0IS.css.br +0 -0
- package/build/client/_app/immutable/assets/3.C0uFg0IS.css.gz +0 -0
- package/build/client/_app/immutable/assets/4.cJuCkJKZ.css +1 -0
- package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.br +0 -0
- package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.gz +0 -0
- package/build/client/_app/immutable/assets/5.DRjApZQW.css +1 -0
- package/build/client/_app/immutable/assets/5.DRjApZQW.css.br +0 -0
- package/build/client/_app/immutable/assets/5.DRjApZQW.css.gz +0 -0
- package/build/client/_app/immutable/assets/6.AraZrY8I.css +1 -0
- package/build/client/_app/immutable/assets/6.AraZrY8I.css.br +0 -0
- package/build/client/_app/immutable/assets/6.AraZrY8I.css.gz +0 -0
- package/build/client/_app/immutable/assets/7.BCJ1IuMx.css +1 -0
- package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.br +0 -0
- package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.gz +0 -0
- package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css +1 -0
- package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.br +0 -0
- package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.gz +0 -0
- package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css +1 -0
- package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.br +0 -0
- package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.gz +0 -0
- package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css +1 -0
- package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.br +0 -0
- package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BNJphC1q.js +56 -0
- package/build/client/_app/immutable/chunks/BNJphC1q.js.br +0 -0
- package/build/client/_app/immutable/chunks/BNJphC1q.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BTGVxaYV.js +9 -0
- package/build/client/_app/immutable/chunks/BTGVxaYV.js.br +0 -0
- package/build/client/_app/immutable/chunks/BTGVxaYV.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BlxrFPDK.js +1 -0
- package/build/client/_app/immutable/chunks/BlxrFPDK.js.br +0 -0
- package/build/client/_app/immutable/chunks/BlxrFPDK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bvk7mfPM.js +1 -0
- package/build/client/_app/immutable/chunks/Bvk7mfPM.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bvk7mfPM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CAokzuPQ.js +1 -0
- package/build/client/_app/immutable/chunks/CAokzuPQ.js.br +0 -0
- package/build/client/_app/immutable/chunks/CAokzuPQ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CGLrx-H5.js +1 -0
- package/build/client/_app/immutable/chunks/CGLrx-H5.js.br +0 -0
- package/build/client/_app/immutable/chunks/CGLrx-H5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CgCpWzEA.js +1 -0
- package/build/client/_app/immutable/chunks/CgCpWzEA.js.br +0 -0
- package/build/client/_app/immutable/chunks/CgCpWzEA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cjwk_cGO.js +6 -0
- package/build/client/_app/immutable/chunks/Cjwk_cGO.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cjwk_cGO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CtQ8EED1.js +11 -0
- package/build/client/_app/immutable/chunks/CtQ8EED1.js.br +0 -0
- package/build/client/_app/immutable/chunks/CtQ8EED1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DERQCisl.js +1 -0
- package/build/client/_app/immutable/chunks/DERQCisl.js.br +0 -0
- package/build/client/_app/immutable/chunks/DERQCisl.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DKrg8TQs.js +1 -0
- package/build/client/_app/immutable/chunks/DKrg8TQs.js.br +0 -0
- package/build/client/_app/immutable/chunks/DKrg8TQs.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DLu6yJIZ.js +1 -0
- package/build/client/_app/immutable/chunks/DLu6yJIZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/DLu6yJIZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dkkpz_4D.js +126 -0
- package/build/client/_app/immutable/chunks/Dkkpz_4D.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dkkpz_4D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DoczjQhA.js +1 -0
- package/build/client/_app/immutable/chunks/DoczjQhA.js.br +0 -0
- package/build/client/_app/immutable/chunks/DoczjQhA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/PPVm8Dsz.js +1 -0
- package/build/client/_app/immutable/chunks/PPVm8Dsz.js.br +0 -0
- package/build/client/_app/immutable/chunks/PPVm8Dsz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/RpcNruLP.js +2 -0
- package/build/client/_app/immutable/chunks/RpcNruLP.js.br +0 -0
- package/build/client/_app/immutable/chunks/RpcNruLP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/a-St0Zwo.js +1 -0
- package/build/client/_app/immutable/chunks/a-St0Zwo.js.br +0 -0
- package/build/client/_app/immutable/chunks/a-St0Zwo.js.gz +0 -0
- package/build/client/_app/immutable/chunks/bo70OQUZ.js +1 -0
- package/build/client/_app/immutable/chunks/bo70OQUZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/bo70OQUZ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.QvGgdvTI.js +2 -0
- package/build/client/_app/immutable/entry/app.QvGgdvTI.js.br +0 -0
- package/build/client/_app/immutable/entry/app.QvGgdvTI.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BntDNRMC.js +1 -0
- package/build/client/_app/immutable/entry/start.BntDNRMC.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BntDNRMC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js +1 -0
- package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.MG1QhfrI.js +1 -0
- package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.B4MlOSh6.js +1 -0
- package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DIwYkjDn.js +3 -0
- package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.D-cIe70D.js +1 -0
- package/build/client/_app/immutable/nodes/4.D-cIe70D.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.D-cIe70D.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.D7zPRe3L.js +1 -0
- package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.BB7QE48r.js +2 -0
- package/build/client/_app/immutable/nodes/6.BB7QE48r.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BB7QE48r.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.D8mqsrZG.js +2 -0
- package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.gz +0 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/app-icon.png +0 -0
- package/build/client/apple-touch-icon.png +0 -0
- package/build/client/favicon.png +0 -0
- package/build/client/favicon.svg +10 -0
- package/build/client/favicon.svg.br +0 -0
- package/build/client/favicon.svg.gz +0 -0
- package/build/client/manifest.webmanifest +1 -0
- package/build/client/pwa-192x192.png +0 -0
- package/build/client/pwa-512x512.png +0 -0
- package/build/client/registerSW.js +1 -0
- package/build/client/registerSW.js.br +0 -0
- package/build/client/registerSW.js.gz +0 -0
- package/build/client/sw.js +222 -0
- package/build/client/sw.js.br +0 -0
- package/build/client/sw.js.gz +0 -0
- package/build/client/workbox-5119daf5.js +3395 -0
- package/build/client/workbox-5119daf5.js.br +0 -0
- package/build/client/workbox-5119daf5.js.gz +0 -0
- package/build/env.js +94 -0
- package/build/handler.js +1494 -0
- package/build/index.js +345 -0
- package/build/pty-holder.cjs +510 -0
- package/build/server/chunks/0-q2IUp76Y.js +9 -0
- package/build/server/chunks/0-q2IUp76Y.js.map +1 -0
- package/build/server/chunks/1-CU50G5wZ.js +9 -0
- package/build/server/chunks/1-CU50G5wZ.js.map +1 -0
- package/build/server/chunks/2-D01t9s8T.js +9 -0
- package/build/server/chunks/2-D01t9s8T.js.map +1 -0
- package/build/server/chunks/3-5PUQ04wC.js +9 -0
- package/build/server/chunks/3-5PUQ04wC.js.map +1 -0
- package/build/server/chunks/4-e7gywnSG.js +9 -0
- package/build/server/chunks/4-e7gywnSG.js.map +1 -0
- package/build/server/chunks/5-CA1SA6KZ.js +9 -0
- package/build/server/chunks/5-CA1SA6KZ.js.map +1 -0
- package/build/server/chunks/6-71H221sV.js +9 -0
- package/build/server/chunks/6-71H221sV.js.map +1 -0
- package/build/server/chunks/7-Bo-vmdyz.js +9 -0
- package/build/server/chunks/7-Bo-vmdyz.js.map +1 -0
- package/build/server/chunks/_layout.svelte-SFHOxs74.js +132 -0
- package/build/server/chunks/_layout.svelte-SFHOxs74.js.map +1 -0
- package/build/server/chunks/_page.svelte-B4w-2wD-.js +120 -0
- package/build/server/chunks/_page.svelte-B4w-2wD-.js.map +1 -0
- package/build/server/chunks/_page.svelte-B_qAXjkh.js +213 -0
- package/build/server/chunks/_page.svelte-B_qAXjkh.js.map +1 -0
- package/build/server/chunks/_page.svelte-CsF1_TRG.js +50 -0
- package/build/server/chunks/_page.svelte-CsF1_TRG.js.map +1 -0
- package/build/server/chunks/_page.svelte-DJC6U-P0.js +68 -0
- package/build/server/chunks/_page.svelte-DJC6U-P0.js.map +1 -0
- package/build/server/chunks/_page.svelte-DQ6HBtsz.js +407 -0
- package/build/server/chunks/_page.svelte-DQ6HBtsz.js.map +1 -0
- package/build/server/chunks/_page.svelte-LbhhjP21.js +148 -0
- package/build/server/chunks/_page.svelte-LbhhjP21.js.map +1 -0
- package/build/server/chunks/_server.ts-BL2FGb5Z.js +387 -0
- package/build/server/chunks/_server.ts-BL2FGb5Z.js.map +1 -0
- package/build/server/chunks/_server.ts-BgdjBZco.js +47 -0
- package/build/server/chunks/_server.ts-BgdjBZco.js.map +1 -0
- package/build/server/chunks/_server.ts-BihKSdj_.js +59 -0
- package/build/server/chunks/_server.ts-BihKSdj_.js.map +1 -0
- package/build/server/chunks/_server.ts-BjOJsoy4.js +63 -0
- package/build/server/chunks/_server.ts-BjOJsoy4.js.map +1 -0
- package/build/server/chunks/_server.ts-C29xzfaw.js +77 -0
- package/build/server/chunks/_server.ts-C29xzfaw.js.map +1 -0
- package/build/server/chunks/_server.ts-CPa6DgIt.js +71 -0
- package/build/server/chunks/_server.ts-CPa6DgIt.js.map +1 -0
- package/build/server/chunks/_server.ts-CbDRDIoP.js +36 -0
- package/build/server/chunks/_server.ts-CbDRDIoP.js.map +1 -0
- package/build/server/chunks/_server.ts-Cl1OEWL4.js +54 -0
- package/build/server/chunks/_server.ts-Cl1OEWL4.js.map +1 -0
- package/build/server/chunks/_server.ts-ColfDHW8.js +60 -0
- package/build/server/chunks/_server.ts-ColfDHW8.js.map +1 -0
- package/build/server/chunks/_server.ts-Cv_OrRuL.js +494 -0
- package/build/server/chunks/_server.ts-Cv_OrRuL.js.map +1 -0
- package/build/server/chunks/_server.ts-D4MNi4cD.js +25 -0
- package/build/server/chunks/_server.ts-D4MNi4cD.js.map +1 -0
- package/build/server/chunks/_server.ts-DRVbgm6k.js +125 -0
- package/build/server/chunks/_server.ts-DRVbgm6k.js.map +1 -0
- package/build/server/chunks/_server.ts-DfajWaqh.js +39 -0
- package/build/server/chunks/_server.ts-DfajWaqh.js.map +1 -0
- package/build/server/chunks/_server.ts-y9-WYDMa.js +35 -0
- package/build/server/chunks/_server.ts-y9-WYDMa.js.map +1 -0
- package/build/server/chunks/auth-CEgFis71.js +32 -0
- package/build/server/chunks/auth-CEgFis71.js.map +1 -0
- package/build/server/chunks/client-CxCatAKr.js +255 -0
- package/build/server/chunks/client-CxCatAKr.js.map +1 -0
- package/build/server/chunks/error.svelte-BqdwMWdK.js +26 -0
- package/build/server/chunks/error.svelte-BqdwMWdK.js.map +1 -0
- package/build/server/chunks/exports-CJ0Q5XmL.js +4081 -0
- package/build/server/chunks/exports-CJ0Q5XmL.js.map +1 -0
- package/build/server/chunks/index2-DAxIoAO-.js +36 -0
- package/build/server/chunks/index2-DAxIoAO-.js.map +1 -0
- package/build/server/chunks/jsonl-parser-dmZU_Hyu.js +137 -0
- package/build/server/chunks/jsonl-parser-dmZU_Hyu.js.map +1 -0
- package/build/server/chunks/library-apns-BHxLmuIx.js +104 -0
- package/build/server/chunks/library-apns-BHxLmuIx.js.map +1 -0
- package/build/server/chunks/markdown-Bxrl3cCF.js +1241 -0
- package/build/server/chunks/markdown-Bxrl3cCF.js.map +1 -0
- package/build/server/chunks/pending-requests-D8UiTw7L.js +44 -0
- package/build/server/chunks/pending-requests-D8UiTw7L.js.map +1 -0
- package/build/server/chunks/pty-manager-C0FhBiVq.js +1697 -0
- package/build/server/chunks/pty-manager-C0FhBiVq.js.map +1 -0
- package/build/server/chunks/shared-server-BDY8jh20.js +200 -0
- package/build/server/chunks/shared-server-BDY8jh20.js.map +1 -0
- package/build/server/chunks/stores-D0HorpgL.js +36 -0
- package/build/server/chunks/stores-D0HorpgL.js.map +1 -0
- package/build/server/index.js +6466 -0
- package/build/server/index.js.map +1 -0
- package/build/server/manifest.js +184 -0
- package/build/server/manifest.js.map +1 -0
- package/build/shims.js +32 -0
- package/package.json +94 -0
- package/scripts/clipboard-shims/wl-paste +48 -0
- package/scripts/clipboard-shims/xclip +31 -0
- package/scripts/install.sh +477 -0
- package/scripts/setup-node-pty.sh +63 -0
- package/scripts/setup.cjs +571 -0
- package/scripts/test-runner.ts +243 -0
- package/scripts/vercel-env-commands.sh +60 -0
- package/server.ts +139 -0
- package/src/app.css +1835 -0
- package/src/app.d.ts +31 -0
- package/src/app.html +24 -0
- package/src/generated/types/APN.ts +305 -0
- package/src/generated/types/CLI.ts +52 -0
- package/src/generated/types/JWT.ts +92 -0
- package/src/generated/types/Terminal.ts +2736 -0
- package/src/generated/types/index.ts +6 -0
- package/src/lib/assets/icons/alert-triangle.svg +5 -0
- package/src/lib/assets/icons/bell.svg +4 -0
- package/src/lib/assets/icons/check-circle.svg +4 -0
- package/src/lib/assets/icons/file.svg +4 -0
- package/src/lib/assets/icons/folder.svg +3 -0
- package/src/lib/assets/icons/play.svg +3 -0
- package/src/lib/assets/icons/refresh.svg +4 -0
- package/src/lib/assets/icons/settings.svg +4 -0
- package/src/lib/assets/icons/terminal.svg +1 -0
- package/src/lib/assets/icons/tool.svg +3 -0
- package/src/lib/assets/icons/x-circle.svg +5 -0
- package/src/lib/modules/client/common/Card.svelte +26 -0
- package/src/lib/modules/client/common/EmptyState.svelte +36 -0
- package/src/lib/modules/client/common/Icon.svelte +61 -0
- package/src/lib/modules/client/common/StatusBadge.svelte +38 -0
- package/src/lib/modules/client/common/cache.ts +31 -0
- package/src/lib/modules/client/common/config-guard.ts +18 -0
- package/src/lib/modules/client/common/index.ts +12 -0
- package/src/lib/modules/client/common/markdown.ts +23 -0
- package/src/lib/modules/client/common/native-bridge.ts +50 -0
- package/src/lib/modules/client/common/time.ts +22 -0
- package/src/lib/modules/client/common/tool-title.ts +28 -0
- package/src/lib/modules/client/terminal/ChatView.svelte +400 -0
- package/src/lib/modules/client/terminal/CommandPalette.svelte +60 -0
- package/src/lib/modules/client/terminal/ConnectionStatus.svelte +99 -0
- package/src/lib/modules/client/terminal/LaunchSheet.svelte +294 -0
- package/src/lib/modules/client/terminal/QuickKeys.svelte +71 -0
- package/src/lib/modules/client/terminal/ShortcutsHelp.svelte +79 -0
- package/src/lib/modules/client/terminal/keyboard-shortcuts.ts +70 -0
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +243 -0
- package/src/lib/modules/server/apn/library-apns.ts +137 -0
- package/src/lib/modules/server/apn/notification-history.ts +35 -0
- package/src/lib/modules/server/apn/notification-sessions.ts +117 -0
- package/src/lib/modules/server/apn/pending-requests.ts +65 -0
- package/src/lib/modules/server/apn/types.ts +51 -0
- package/src/lib/modules/server/auth.ts +34 -0
- package/src/lib/modules/server/cli/index.ts +79 -0
- package/src/lib/modules/server/cli/runner.ts +162 -0
- package/src/lib/modules/server/fcm/fcm-service.ts +72 -0
- package/src/lib/modules/server/sessions/jsonl-parser.ts +197 -0
- package/src/lib/modules/server/sessions/jsonl-reader.ts +301 -0
- package/src/lib/modules/server/sessions/opencode-reader.ts +264 -0
- package/src/lib/modules/server/sessions/types.ts +53 -0
- package/src/lib/modules/server/terminal/holder-client.ts +273 -0
- package/src/lib/modules/server/terminal/opencode-watcher.ts +661 -0
- package/src/lib/modules/server/terminal/pty-holder.cjs +510 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +1012 -0
- package/src/lib/modules/server/terminal/session-watcher.ts +320 -0
- package/src/lib/modules/server/terminal/terminal-store.ts +198 -0
- package/src/lib/modules/server/ws/events-handler.ts +73 -0
- package/src/lib/modules/server/ws/keepalive.ts +108 -0
- package/src/lib/modules/server/ws/server.ts +93 -0
- package/src/lib/modules/server/ws/session-handler.ts +462 -0
- package/src/lib/modules/server/ws/terminal-handler.ts +197 -0
- package/src/lib/modules/server/ws/ticket-store.ts +58 -0
- package/src/lib/theme.css +529 -0
- package/src/lib/types/config.ts +6 -0
- package/src/routes/+layout.svelte +218 -0
- package/src/routes/+page.svelte +261 -0
- package/src/routes/api/debug/+server.ts +33 -0
- package/src/routes/api/device-token/+server.ts +85 -0
- package/src/routes/api/health/+server.ts +100 -0
- package/src/routes/api/notify/+server.ts +418 -0
- package/src/routes/api/qr-config/+server.ts +45 -0
- package/src/routes/api/response/+server.ts +73 -0
- package/src/routes/api/sessions/+server.ts +120 -0
- package/src/routes/api/terminals/+server.ts +141 -0
- package/src/routes/api/terminals/[id]/+server.ts +75 -0
- package/src/routes/api/terminals/[id]/paste-image/+server.ts +61 -0
- package/src/routes/api/terminals/[id]/resize/+server.ts +60 -0
- package/src/routes/api/webhook/+server.ts +42 -0
- package/src/routes/api/ws-status/+server.ts +23 -0
- package/src/routes/api/ws-ticket/+server.ts +86 -0
- package/src/routes/config/+page.svelte +600 -0
- package/src/routes/project/+page.svelte +274 -0
- package/src/routes/session/[id]/+page.svelte +434 -0
- package/src/routes/terminals/+page.svelte +618 -0
- package/src/routes/terminals/[id]/+page.svelte +968 -0
- package/svelte.config.js +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import type { ConversationMessage, MessagePart, ProjectGroup, SessionInfo } from './types';
|
|
6
|
+
|
|
7
|
+
import { parseJsonlText } from './jsonl-parser';
|
|
8
|
+
|
|
9
|
+
// Short hash for project IDs (8 chars from SHA-256)
|
|
10
|
+
function shortHash(input: string): string {
|
|
11
|
+
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Path to Claude Code's project session data
|
|
15
|
+
const CLAUDE_PROJECTS_DIR = path.join(process.env.HOME || '', '.claude', 'projects');
|
|
16
|
+
|
|
17
|
+
export function getSessionConversation(
|
|
18
|
+
sessionId: string,
|
|
19
|
+
offset = 0,
|
|
20
|
+
limit = 100,
|
|
21
|
+
projectId?: string
|
|
22
|
+
): ConversationMessage[] {
|
|
23
|
+
const projectDir = projectId ? path.join(CLAUDE_PROJECTS_DIR, projectId) : getProjectDir();
|
|
24
|
+
const jsonlPath = path.join(projectDir, `${sessionId}.jsonl`);
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(jsonlPath)) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const raw = fs.readFileSync(jsonlPath, 'utf-8');
|
|
32
|
+
const assistantTurns = new Map<string, { parts: MessagePart[]; timestamp: string }>();
|
|
33
|
+
const messages = parseJsonlText(raw, assistantTurns, 0);
|
|
34
|
+
|
|
35
|
+
// Flush any remaining assistant turns
|
|
36
|
+
for (const [msgId, turn] of assistantTurns) {
|
|
37
|
+
if (turn.parts.length > 0) {
|
|
38
|
+
messages.push({
|
|
39
|
+
id: msgId,
|
|
40
|
+
parts: turn.parts,
|
|
41
|
+
role: 'assistant',
|
|
42
|
+
timestamp: turn.timestamp,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return messages.slice(offset, offset + limit);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('[sessions] Failed to parse session JSONL:', error);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function listProjectsWithSessions(): ProjectGroup[] {
|
|
55
|
+
if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let projectDirs: string[];
|
|
60
|
+
try {
|
|
61
|
+
projectDirs = fs.readdirSync(CLAUDE_PROJECTS_DIR).filter((d) => {
|
|
62
|
+
const fullPath = path.join(CLAUDE_PROJECTS_DIR, d);
|
|
63
|
+
try {
|
|
64
|
+
return fs.statSync(fullPath).isDirectory();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const projects: ProjectGroup[] = [];
|
|
74
|
+
|
|
75
|
+
for (const dir of projectDirs) {
|
|
76
|
+
const projectDir = path.join(CLAUDE_PROJECTS_DIR, dir);
|
|
77
|
+
// Decode project path (fallback, prefer session's projectPath)
|
|
78
|
+
const decodedPath = dir.startsWith('-') ? dir.replace(/-/g, '/') : `/${dir.replace(/-/g, '/')}`;
|
|
79
|
+
|
|
80
|
+
// Get sessions for this project
|
|
81
|
+
const sessions = listSessionsForProject(projectDir);
|
|
82
|
+
if (sessions.length === 0) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Sort sessions by modified desc
|
|
87
|
+
sessions.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
88
|
+
|
|
89
|
+
// Get real project path from session data instead of decoding directory name
|
|
90
|
+
const realPath =
|
|
91
|
+
sessions.find((s) => s.projectPath && !s.projectPath.includes('.claude/projects'))
|
|
92
|
+
?.projectPath || decodedPath;
|
|
93
|
+
const pathSegments = realPath.split('/').filter(Boolean);
|
|
94
|
+
const projectName = pathSegments.slice(-2).join('/');
|
|
95
|
+
|
|
96
|
+
projects.push({
|
|
97
|
+
fullPath: realPath,
|
|
98
|
+
id: shortHash(realPath),
|
|
99
|
+
lastModified: sessions[0]?.modified || '',
|
|
100
|
+
name: projectName,
|
|
101
|
+
sessionCount: sessions.length,
|
|
102
|
+
sessions,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return projects.sort(
|
|
107
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function listSessions(): SessionInfo[] {
|
|
112
|
+
const projectDir = getProjectDir();
|
|
113
|
+
return listSessionsForProject(projectDir);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function cleanTitle(prompt: string): string {
|
|
117
|
+
// Strip internal Claude Code XML markup tags, then remove ALL angle-bracket content.
|
|
118
|
+
// Use a loop to handle nested/reconstructed tags (satisfies CodeQL multi-char sanitization).
|
|
119
|
+
let cleaned = prompt
|
|
120
|
+
.replace(/<command-message>.*?<\/command-message>/gs, '')
|
|
121
|
+
.replace(/<command-name>.*?<\/command-name>/gs, '')
|
|
122
|
+
.replace(/<command-args>.*?<\/command-args>/gs, '')
|
|
123
|
+
.replace(/<local-command-caveat>.*?<\/local-command-caveat>/gs, '')
|
|
124
|
+
.replace(/<local-command-stdout>.*?<\/local-command-stdout>/gs, '')
|
|
125
|
+
.replace(/<system-reminder>.*?<\/system-reminder>/gs, '');
|
|
126
|
+
|
|
127
|
+
// Iteratively strip tags until none remain (prevents reconstructed tags like "<scr" + "ipt>")
|
|
128
|
+
let prev = '';
|
|
129
|
+
while (prev !== cleaned) {
|
|
130
|
+
prev = cleaned;
|
|
131
|
+
cleaned = cleaned.replace(/<[^>]*>/g, '').replace(/</g, '');
|
|
132
|
+
}
|
|
133
|
+
cleaned = cleaned.trim();
|
|
134
|
+
|
|
135
|
+
if (!cleaned) {
|
|
136
|
+
return 'Untitled Session';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Take first line, trim, cap at 80 chars
|
|
140
|
+
const firstLine = cleaned.split('\n')[0].trim();
|
|
141
|
+
if (!firstLine) {
|
|
142
|
+
return 'Untitled Session';
|
|
143
|
+
}
|
|
144
|
+
if (firstLine.length > 80) {
|
|
145
|
+
return `${firstLine.slice(0, 77)}...`;
|
|
146
|
+
}
|
|
147
|
+
return firstLine;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getProjectDir(): string {
|
|
151
|
+
// Encode project path: /Users/sachinsharma/Developer/Personal/shooter -> -Users-sachinsharma-Developer-Personal-shooter
|
|
152
|
+
const projectPath = process.cwd();
|
|
153
|
+
const encoded = projectPath.replace(/\//g, '-');
|
|
154
|
+
return path.join(CLAUDE_PROJECTS_DIR, encoded);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function listSessionsForProject(projectDir: string): SessionInfo[] {
|
|
158
|
+
const indexPath = path.join(projectDir, 'sessions-index.json');
|
|
159
|
+
|
|
160
|
+
if (!fs.existsSync(indexPath)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const raw = fs.readFileSync(indexPath, 'utf-8');
|
|
166
|
+
const index = JSON.parse(raw) as {
|
|
167
|
+
entries: {
|
|
168
|
+
created?: string;
|
|
169
|
+
firstPrompt?: string;
|
|
170
|
+
gitBranch?: string;
|
|
171
|
+
isSidechain?: boolean;
|
|
172
|
+
messageCount?: number;
|
|
173
|
+
modified?: string;
|
|
174
|
+
projectPath?: string;
|
|
175
|
+
sessionId: string;
|
|
176
|
+
summary?: string;
|
|
177
|
+
}[];
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const indexedIds = new Set<string>();
|
|
181
|
+
const sessions: SessionInfo[] = index.entries
|
|
182
|
+
.filter((e) => !e.isSidechain)
|
|
183
|
+
.filter((e) => {
|
|
184
|
+
const jsonlFile = path.join(projectDir, `${e.sessionId}.jsonl`);
|
|
185
|
+
return fs.existsSync(jsonlFile);
|
|
186
|
+
})
|
|
187
|
+
.map((entry) => {
|
|
188
|
+
indexedIds.add(entry.sessionId);
|
|
189
|
+
return {
|
|
190
|
+
created: entry.created || '',
|
|
191
|
+
gitBranch: entry.gitBranch || '',
|
|
192
|
+
id: entry.sessionId,
|
|
193
|
+
messageCount: entry.messageCount || 0,
|
|
194
|
+
modified: entry.modified || '',
|
|
195
|
+
projectPath: entry.projectPath || '',
|
|
196
|
+
source: 'claude-code' as const,
|
|
197
|
+
summary: entry.summary || '',
|
|
198
|
+
title: cleanTitle(entry.firstPrompt || entry.summary || 'Untitled Session'),
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Also scan for JSONL files not in the index (e.g., active sessions)
|
|
203
|
+
try {
|
|
204
|
+
const files = fs.readdirSync(projectDir);
|
|
205
|
+
for (const file of files) {
|
|
206
|
+
if (!file.endsWith('.jsonl')) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const sessionId = file.replace('.jsonl', '');
|
|
210
|
+
if (indexedIds.has(sessionId)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// Read first user message as title
|
|
214
|
+
const filePath = path.join(projectDir, file);
|
|
215
|
+
const stat = fs.statSync(filePath);
|
|
216
|
+
let firstPrompt = 'Active Session';
|
|
217
|
+
let messageCount = 0;
|
|
218
|
+
try {
|
|
219
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
220
|
+
// Count user + assistant messages for the message count
|
|
221
|
+
const lines = content.split('\n');
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
if (!line.trim()) { continue; }
|
|
224
|
+
try {
|
|
225
|
+
const entry = JSON.parse(line);
|
|
226
|
+
if (entry.type === 'user' || entry.type === 'assistant') {
|
|
227
|
+
messageCount++;
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// skip malformed lines
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Find the first real user message (skip system caveats, commands, tool results)
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
if (!line.trim()) { continue; }
|
|
236
|
+
try {
|
|
237
|
+
const entry = JSON.parse(line);
|
|
238
|
+
if (entry.type !== 'user') { continue; }
|
|
239
|
+
const msg = entry.message;
|
|
240
|
+
if (!msg?.content) { continue; }
|
|
241
|
+
|
|
242
|
+
// Extract text from content (can be string or array of blocks)
|
|
243
|
+
let text = '';
|
|
244
|
+
if (typeof msg.content === 'string') {
|
|
245
|
+
text = msg.content.trim();
|
|
246
|
+
} else if (Array.isArray(msg.content)) {
|
|
247
|
+
for (const b of msg.content) {
|
|
248
|
+
if (typeof b === 'string' && b.trim()) {
|
|
249
|
+
text = b.trim();
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
if (typeof b === 'object' && b !== null && b.type === 'text' && b.text?.trim()) {
|
|
253
|
+
text = b.text.trim();
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!text) { continue; }
|
|
260
|
+
|
|
261
|
+
// Skip system-injected caveats and command outputs
|
|
262
|
+
if (text.startsWith('<local-command') ||
|
|
263
|
+
text.startsWith('<command-name>') ||
|
|
264
|
+
text.startsWith('<local-command-stdout>') ||
|
|
265
|
+
text.startsWith('<system-reminder>') ||
|
|
266
|
+
text.startsWith('<task-notification>')) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
firstPrompt = text;
|
|
271
|
+
break;
|
|
272
|
+
} catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
// ignore read errors
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
sessions.push({
|
|
281
|
+
created: stat.birthtime.toISOString(),
|
|
282
|
+
gitBranch: '',
|
|
283
|
+
id: sessionId,
|
|
284
|
+
messageCount,
|
|
285
|
+
modified: stat.mtime.toISOString(),
|
|
286
|
+
projectPath: '',
|
|
287
|
+
source: 'claude-code' as const,
|
|
288
|
+
summary: '',
|
|
289
|
+
title: cleanTitle(firstPrompt),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
// ignore scan errors
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return sessions.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error('[sessions] Failed to read sessions index:', error);
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import * as crypto from 'crypto';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
function shortHash(input: string): string {
|
|
7
|
+
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
import type { ConversationMessage, MessagePart, ProjectGroup, SessionInfo } from './types';
|
|
11
|
+
|
|
12
|
+
const OPENCODE_DB_PATH = path.join(
|
|
13
|
+
process.env.HOME || '',
|
|
14
|
+
'.local',
|
|
15
|
+
'share',
|
|
16
|
+
'opencode',
|
|
17
|
+
'opencode.db'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function getOpenCodeConversation(
|
|
21
|
+
sessionId: string,
|
|
22
|
+
offset = 0,
|
|
23
|
+
limit = 100
|
|
24
|
+
): ConversationMessage[] {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
if (!db) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Get messages for this session
|
|
32
|
+
const messages = db
|
|
33
|
+
.prepare(
|
|
34
|
+
`
|
|
35
|
+
SELECT id, session_id, time_created, data
|
|
36
|
+
FROM message
|
|
37
|
+
WHERE session_id = ?
|
|
38
|
+
ORDER BY time_created ASC
|
|
39
|
+
LIMIT ? OFFSET ?
|
|
40
|
+
`
|
|
41
|
+
)
|
|
42
|
+
.all(sessionId, limit, offset) as {
|
|
43
|
+
data: string;
|
|
44
|
+
id: string;
|
|
45
|
+
session_id: string;
|
|
46
|
+
time_created: number;
|
|
47
|
+
}[];
|
|
48
|
+
|
|
49
|
+
// Get parts for these messages
|
|
50
|
+
const messageIds = messages.map((m) => m.id);
|
|
51
|
+
if (messageIds.length === 0) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const placeholders = messageIds.map(() => '?').join(',');
|
|
56
|
+
const parts = db
|
|
57
|
+
.prepare(
|
|
58
|
+
`
|
|
59
|
+
SELECT id, message_id, time_created, data
|
|
60
|
+
FROM part
|
|
61
|
+
WHERE message_id IN (${placeholders})
|
|
62
|
+
ORDER BY time_created ASC
|
|
63
|
+
`
|
|
64
|
+
)
|
|
65
|
+
.all(...messageIds) as {
|
|
66
|
+
data: string;
|
|
67
|
+
id: string;
|
|
68
|
+
message_id: string;
|
|
69
|
+
time_created: number;
|
|
70
|
+
}[];
|
|
71
|
+
|
|
72
|
+
// Group parts by message
|
|
73
|
+
const partsByMessage = new Map<string, typeof parts>();
|
|
74
|
+
for (const part of parts) {
|
|
75
|
+
if (!partsByMessage.has(part.message_id)) {
|
|
76
|
+
partsByMessage.set(part.message_id, []);
|
|
77
|
+
}
|
|
78
|
+
const bucket = partsByMessage.get(part.message_id);
|
|
79
|
+
if (bucket) {
|
|
80
|
+
bucket.push(part);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Build ConversationMessage array
|
|
85
|
+
const result: ConversationMessage[] = [];
|
|
86
|
+
|
|
87
|
+
for (const msg of messages) {
|
|
88
|
+
let msgData: { agent?: string; role?: string } = {};
|
|
89
|
+
try {
|
|
90
|
+
msgData = JSON.parse(msg.data) as typeof msgData;
|
|
91
|
+
} catch {
|
|
92
|
+
// skip unparseable message data
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const role = msgData.role === 'user' ? 'user' : 'assistant';
|
|
96
|
+
const msgParts = partsByMessage.get(msg.id) || [];
|
|
97
|
+
const convertedParts: MessagePart[] = [];
|
|
98
|
+
|
|
99
|
+
for (const part of msgParts) {
|
|
100
|
+
let partData: Record<string, unknown> = {};
|
|
101
|
+
try {
|
|
102
|
+
partData = JSON.parse(part.data) as Record<string, unknown>;
|
|
103
|
+
} catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const converted = convertOpenCodePart(partData);
|
|
108
|
+
if (converted) {
|
|
109
|
+
convertedParts.push(converted);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (convertedParts.length > 0) {
|
|
114
|
+
result.push({
|
|
115
|
+
id: msg.id,
|
|
116
|
+
parts: convertedParts,
|
|
117
|
+
role: role as 'assistant' | 'system' | 'user',
|
|
118
|
+
timestamp: new Date(msg.time_created).toISOString(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[opencode] Failed to read conversation:', error);
|
|
126
|
+
return [];
|
|
127
|
+
} finally {
|
|
128
|
+
db.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function listOpenCodeProjects(): ProjectGroup[] {
|
|
133
|
+
const db = getDb();
|
|
134
|
+
if (!db) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Get all sessions with their directories
|
|
140
|
+
const sessions = db
|
|
141
|
+
.prepare(
|
|
142
|
+
`
|
|
143
|
+
SELECT id, directory, title, slug, time_created, time_updated, parent_id
|
|
144
|
+
FROM session
|
|
145
|
+
WHERE time_archived IS NULL OR time_archived = 0
|
|
146
|
+
ORDER BY time_updated DESC
|
|
147
|
+
`
|
|
148
|
+
)
|
|
149
|
+
.all() as {
|
|
150
|
+
directory: string;
|
|
151
|
+
id: string;
|
|
152
|
+
parent_id: null | string;
|
|
153
|
+
slug: null | string;
|
|
154
|
+
time_created: number;
|
|
155
|
+
time_updated: number;
|
|
156
|
+
title: null | string;
|
|
157
|
+
}[];
|
|
158
|
+
|
|
159
|
+
// Get message counts per session
|
|
160
|
+
const msgCounts = db
|
|
161
|
+
.prepare(`SELECT session_id, COUNT(*) as count FROM message GROUP BY session_id`)
|
|
162
|
+
.all() as { count: number; session_id: string }[];
|
|
163
|
+
const msgCountMap = new Map(msgCounts.map((r) => [r.session_id, r.count]));
|
|
164
|
+
|
|
165
|
+
// Group by directory (project)
|
|
166
|
+
const projectMap = new Map<string, SessionInfo[]>();
|
|
167
|
+
|
|
168
|
+
for (const row of sessions) {
|
|
169
|
+
const dir = row.directory || 'unknown';
|
|
170
|
+
if (!projectMap.has(dir)) {
|
|
171
|
+
projectMap.set(dir, []);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const sessionInfo: SessionInfo = {
|
|
175
|
+
created: new Date(row.time_created).toISOString(),
|
|
176
|
+
gitBranch: '',
|
|
177
|
+
id: row.id,
|
|
178
|
+
messageCount: msgCountMap.get(row.id) || 0,
|
|
179
|
+
modified: new Date(row.time_updated).toISOString(),
|
|
180
|
+
projectPath: dir,
|
|
181
|
+
source: 'opencode' as const,
|
|
182
|
+
summary: '',
|
|
183
|
+
title: row.title || row.slug || 'Untitled Session',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const bucket = projectMap.get(dir);
|
|
187
|
+
if (bucket) {
|
|
188
|
+
bucket.push(sessionInfo);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Build ProjectGroup array
|
|
193
|
+
const projects: ProjectGroup[] = [];
|
|
194
|
+
|
|
195
|
+
for (const [dir, dirSessions] of projectMap) {
|
|
196
|
+
const pathSegments = dir.split('/').filter(Boolean);
|
|
197
|
+
const projectName = pathSegments.slice(-2).join('/');
|
|
198
|
+
|
|
199
|
+
// Sort sessions by modified desc
|
|
200
|
+
dirSessions.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
|
|
201
|
+
|
|
202
|
+
projects.push({
|
|
203
|
+
fullPath: dir,
|
|
204
|
+
id: shortHash(dir),
|
|
205
|
+
lastModified: dirSessions[0]?.modified || '',
|
|
206
|
+
name: `${projectName} (OpenCode)`,
|
|
207
|
+
sessionCount: dirSessions.length,
|
|
208
|
+
sessions: dirSessions,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return projects.sort(
|
|
213
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
214
|
+
);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('[opencode] Failed to read sessions:', error);
|
|
217
|
+
return [];
|
|
218
|
+
} finally {
|
|
219
|
+
db.close();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function convertOpenCodePart(data: Record<string, unknown>): MessagePart | null {
|
|
224
|
+
const type = data.type as string;
|
|
225
|
+
|
|
226
|
+
switch (type) {
|
|
227
|
+
case 'reasoning':
|
|
228
|
+
return {
|
|
229
|
+
content: (data.text as string) || '',
|
|
230
|
+
type: 'thinking',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
case 'text':
|
|
234
|
+
return {
|
|
235
|
+
content: (data.text as string) || '',
|
|
236
|
+
type: 'text',
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
case 'tool': {
|
|
240
|
+
const state = data.state as Record<string, unknown> | undefined;
|
|
241
|
+
return {
|
|
242
|
+
id: (data.callID as string) || (data.id as string) || '',
|
|
243
|
+
input: (state?.input as Record<string, unknown>) || {},
|
|
244
|
+
toolName: (data.tool as string) || 'Unknown',
|
|
245
|
+
type: 'tool_use',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
default:
|
|
250
|
+
// Skip snapshot, patch, step-start, step-finish, subtask, retry, compaction
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getDb(): Database.Database | null {
|
|
256
|
+
if (!fs.existsSync(OPENCODE_DB_PATH)) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
return new Database(OPENCODE_DB_PATH, { readonly: true });
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface ConversationMessage {
|
|
2
|
+
id: string;
|
|
3
|
+
parts: MessagePart[];
|
|
4
|
+
role: 'assistant' | 'system' | 'user';
|
|
5
|
+
timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type MessagePart = TextPart | ThinkingPart | ToolResultPart | ToolUsePart;
|
|
9
|
+
|
|
10
|
+
export interface ProjectGroup {
|
|
11
|
+
fullPath: string;
|
|
12
|
+
id: string;
|
|
13
|
+
lastModified: string;
|
|
14
|
+
name: string;
|
|
15
|
+
sessionCount: number;
|
|
16
|
+
sessions: SessionInfo[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SessionInfo {
|
|
20
|
+
created: string;
|
|
21
|
+
gitBranch: string;
|
|
22
|
+
id: string;
|
|
23
|
+
messageCount: number;
|
|
24
|
+
modified: string;
|
|
25
|
+
projectPath: string;
|
|
26
|
+
source: 'claude-code' | 'opencode';
|
|
27
|
+
summary: string;
|
|
28
|
+
title: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TextPart {
|
|
32
|
+
content: string;
|
|
33
|
+
type: 'text';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ThinkingPart {
|
|
37
|
+
content: string;
|
|
38
|
+
type: 'thinking';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ToolResultPart {
|
|
42
|
+
isError: boolean;
|
|
43
|
+
output: string;
|
|
44
|
+
toolUseId: string;
|
|
45
|
+
type: 'tool_result';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ToolUsePart {
|
|
49
|
+
id: string;
|
|
50
|
+
input: Record<string, unknown>;
|
|
51
|
+
toolName: string;
|
|
52
|
+
type: 'tool_use';
|
|
53
|
+
}
|