@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,418 @@
|
|
|
1
|
+
import { env } from '$env/dynamic/private';
|
|
2
|
+
import { LibraryAPNsService } from '$lib/modules/server/apn/library-apns';
|
|
3
|
+
import { addNotification, getNotifications } from '$lib/modules/server/apn/notification-history';
|
|
4
|
+
import { createPendingRequest } from '$lib/modules/server/apn/pending-requests';
|
|
5
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
6
|
+
import { isFCMConfigured, sendFCMNotification } from '$lib/modules/server/fcm/fcm-service.js';
|
|
7
|
+
import { json } from '@sveltejs/kit';
|
|
8
|
+
|
|
9
|
+
import type { RequestHandler } from './$types';
|
|
10
|
+
|
|
11
|
+
interface FilterResult {
|
|
12
|
+
reason: string;
|
|
13
|
+
send: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface NotificationData {
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
category?: string;
|
|
19
|
+
files?: string;
|
|
20
|
+
project?: string;
|
|
21
|
+
source?: string;
|
|
22
|
+
tool?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface NotificationRequest {
|
|
26
|
+
data?: NotificationData;
|
|
27
|
+
message: string;
|
|
28
|
+
title: string;
|
|
29
|
+
waitForResponse?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Singleton APNs client - reuses HTTP/2 connection across requests
|
|
33
|
+
let apnsSingleton: LibraryAPNsService | null = null;
|
|
34
|
+
function getAPNsClient(): LibraryAPNsService {
|
|
35
|
+
if (!apnsSingleton) {
|
|
36
|
+
apnsSingleton = new LibraryAPNsService();
|
|
37
|
+
}
|
|
38
|
+
return apnsSingleton;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// NOTIFICATION DEDUPLICATION CACHE
|
|
42
|
+
const notificationCache = new Map<string, number>();
|
|
43
|
+
const DEDUP_WINDOW = 10000; // 10 seconds deduplication window
|
|
44
|
+
|
|
45
|
+
// 🎯 INTELLIGENT NOTIFICATION FILTERING
|
|
46
|
+
function intelligentNotificationFilter(
|
|
47
|
+
title: string,
|
|
48
|
+
message: string,
|
|
49
|
+
data?: NotificationData
|
|
50
|
+
): FilterResult {
|
|
51
|
+
const source = data?.source || 'unknown';
|
|
52
|
+
|
|
53
|
+
// Check for duplicate notifications first
|
|
54
|
+
if (isDuplicateNotification(title, message, data)) {
|
|
55
|
+
return {
|
|
56
|
+
reason: 'Duplicate notification within 10-second window',
|
|
57
|
+
send: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Always allow smart completion detector notifications (both old and new naming)
|
|
62
|
+
if (source === 'smart-completion-detector' || source === 'shooter-completion-detector') {
|
|
63
|
+
return {
|
|
64
|
+
reason: 'Smart completion detector - completion or intervention needed',
|
|
65
|
+
send: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Always allow Stop hook completion notifications
|
|
70
|
+
if (source === 'stop-hook') {
|
|
71
|
+
return {
|
|
72
|
+
reason: 'Stop hook completion notification - session finished',
|
|
73
|
+
send: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter out only very specific spam patterns to be less restrictive
|
|
78
|
+
const spamPatterns = [
|
|
79
|
+
/PreToolUse/i, // Any PreToolUse notifications
|
|
80
|
+
/PostToolUse/i, // Any PostToolUse notifications
|
|
81
|
+
/^(Read|Write|Edit|Bash)\s+Starting\s*\|\s*\w+$/i, // Only basic tool starting patterns
|
|
82
|
+
/^unknown\s*\|\s*\w+$/i, // Only "unknown | projectname" exactly
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const isSpam = spamPatterns.some((pattern) => pattern.test(title) || pattern.test(message));
|
|
86
|
+
|
|
87
|
+
if (isSpam) {
|
|
88
|
+
return {
|
|
89
|
+
reason: 'Filtered spam notification from old hook system',
|
|
90
|
+
send: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Allow explicit completion/intervention notifications
|
|
95
|
+
const importantPatterns = [
|
|
96
|
+
/session complete/i,
|
|
97
|
+
/intervention needed/i,
|
|
98
|
+
/error/i,
|
|
99
|
+
/failed/i,
|
|
100
|
+
/blocked/i,
|
|
101
|
+
/attention/i,
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const isImportant = importantPatterns.some(
|
|
105
|
+
(pattern) => pattern.test(title) || pattern.test(message)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (isImportant) {
|
|
109
|
+
return {
|
|
110
|
+
reason: 'Important notification - completion or intervention',
|
|
111
|
+
send: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Default: allow all notifications unless explicitly filtered
|
|
116
|
+
return {
|
|
117
|
+
reason: 'General notification - allowing by default',
|
|
118
|
+
send: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isDuplicateNotification(title: string, message: string, data?: NotificationData): boolean {
|
|
123
|
+
const key = `${title}|${message}|${data?.category || 'unknown'}`;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
|
|
126
|
+
if (notificationCache.has(key)) {
|
|
127
|
+
const lastSent = notificationCache.get(key)!;
|
|
128
|
+
if (now - lastSent < DEDUP_WINDOW) {
|
|
129
|
+
return true; // Duplicate within time window
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clean up old entries
|
|
134
|
+
for (const [k, v] of notificationCache.entries()) {
|
|
135
|
+
if (now - v > DEDUP_WINDOW) {
|
|
136
|
+
notificationCache.delete(k);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Do NOT record here — caller must call recordNotification() after
|
|
141
|
+
// successful delivery to avoid cache poisoning on send failure.
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Record a notification key in the dedup cache after successful delivery. */
|
|
146
|
+
function recordNotification(title: string, message: string, data?: NotificationData): void {
|
|
147
|
+
const key = `${title}|${message}|${data?.category || 'unknown'}`;
|
|
148
|
+
notificationCache.set(key, Date.now());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
152
|
+
try {
|
|
153
|
+
// Use singleton APNs client to reuse HTTP/2 connection
|
|
154
|
+
const apnsClient = getAPNsClient();
|
|
155
|
+
|
|
156
|
+
// Validate API key using timing-safe comparison
|
|
157
|
+
const authError = validateAuth(request);
|
|
158
|
+
if (authError) return authError;
|
|
159
|
+
|
|
160
|
+
// Parse request body
|
|
161
|
+
const body = (await request.json()) as NotificationRequest;
|
|
162
|
+
const { data, message, title, waitForResponse } = body;
|
|
163
|
+
|
|
164
|
+
if (!title || !message) {
|
|
165
|
+
return json({ error: 'Title and message are required' }, { status: 400 });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Smart notification filtering
|
|
169
|
+
const requestId = Math.random().toString(36).substring(2, 15);
|
|
170
|
+
const dataRequestId = typeof data?.requestId === 'string' ? data.requestId : undefined;
|
|
171
|
+
const canonicalRequestId = dataRequestId || requestId;
|
|
172
|
+
const shouldSendNotification = intelligentNotificationFilter(title, message, data);
|
|
173
|
+
|
|
174
|
+
if (!shouldSendNotification.send) {
|
|
175
|
+
addNotification({
|
|
176
|
+
category: data?.category,
|
|
177
|
+
data: data as Record<string, unknown>,
|
|
178
|
+
error: shouldSendNotification.reason,
|
|
179
|
+
id: canonicalRequestId,
|
|
180
|
+
message,
|
|
181
|
+
project: data?.project,
|
|
182
|
+
source: data?.source,
|
|
183
|
+
status: 'filtered',
|
|
184
|
+
timestamp: new Date().toISOString(),
|
|
185
|
+
title,
|
|
186
|
+
tool: data?.tool,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return json({
|
|
190
|
+
message: 'Notification filtered (not sent)',
|
|
191
|
+
reason: shouldSendNotification.reason,
|
|
192
|
+
success: true,
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Build notification payload (shared between APNs and FCM)
|
|
198
|
+
const payload = {
|
|
199
|
+
badge: 1,
|
|
200
|
+
body: message,
|
|
201
|
+
category: waitForResponse ? 'CLAUDE_PERMISSION' : undefined,
|
|
202
|
+
data: {
|
|
203
|
+
...data,
|
|
204
|
+
requestId: canonicalRequestId,
|
|
205
|
+
source: 'modern-apns-api',
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
waitForResponse: waitForResponse || false,
|
|
208
|
+
},
|
|
209
|
+
message: null,
|
|
210
|
+
sound: 'default' as const,
|
|
211
|
+
title,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Platform-based routing: Android (FCM) or iOS (APNs)
|
|
215
|
+
const platform = env.DEVICE_PLATFORM || 'ios';
|
|
216
|
+
|
|
217
|
+
if (platform === 'android') {
|
|
218
|
+
// --- FCM (Android) path ---
|
|
219
|
+
if (!isFCMConfigured()) {
|
|
220
|
+
return json(
|
|
221
|
+
{
|
|
222
|
+
details: 'Missing FCM_PROJECT_ID, FCM_CLIENT_EMAIL, or FCM_PRIVATE_KEY environment variables',
|
|
223
|
+
error: 'FCM not configured',
|
|
224
|
+
},
|
|
225
|
+
{ status: 500 }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const androidToken = (env.ANDROID_DEVICE_TOKEN || env.DEVICE_TOKEN)?.trim();
|
|
230
|
+
|
|
231
|
+
if (!androidToken) {
|
|
232
|
+
return json(
|
|
233
|
+
{
|
|
234
|
+
details: 'ANDROID_DEVICE_TOKEN or DEVICE_TOKEN environment variable is missing',
|
|
235
|
+
error: 'No device token configured',
|
|
236
|
+
},
|
|
237
|
+
{ status: 500 }
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const fcmResult = await sendFCMNotification(androidToken, payload);
|
|
242
|
+
|
|
243
|
+
if (fcmResult.success) {
|
|
244
|
+
// Record in dedup cache only after successful delivery
|
|
245
|
+
recordNotification(title, message, data);
|
|
246
|
+
|
|
247
|
+
if (waitForResponse) {
|
|
248
|
+
createPendingRequest(canonicalRequestId, {
|
|
249
|
+
sessionId: (data?.sessionId as string) || '',
|
|
250
|
+
toolInput: (data?.toolInput as Record<string, unknown>) || {},
|
|
251
|
+
toolName: (data?.toolName as string) || '',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
addNotification({
|
|
256
|
+
category: data?.category,
|
|
257
|
+
data: data as Record<string, unknown>,
|
|
258
|
+
id: canonicalRequestId,
|
|
259
|
+
message,
|
|
260
|
+
project: data?.project,
|
|
261
|
+
source: data?.source,
|
|
262
|
+
status: 'sent',
|
|
263
|
+
timestamp: new Date().toISOString(),
|
|
264
|
+
title,
|
|
265
|
+
tool: data?.tool,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return json({
|
|
269
|
+
message: 'Notification sent successfully',
|
|
270
|
+
requestId: canonicalRequestId,
|
|
271
|
+
result: { messageId: fcmResult.messageId },
|
|
272
|
+
success: true,
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
console.error(`[notify] FCM delivery failed: ${fcmResult.error}`);
|
|
277
|
+
|
|
278
|
+
addNotification({
|
|
279
|
+
category: data?.category,
|
|
280
|
+
data: data as Record<string, unknown>,
|
|
281
|
+
error: fcmResult.error,
|
|
282
|
+
id: canonicalRequestId,
|
|
283
|
+
message,
|
|
284
|
+
project: data?.project,
|
|
285
|
+
source: data?.source,
|
|
286
|
+
status: 'failed',
|
|
287
|
+
timestamp: new Date().toISOString(),
|
|
288
|
+
title,
|
|
289
|
+
tool: data?.tool,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return json(
|
|
293
|
+
{
|
|
294
|
+
details: fcmResult.error,
|
|
295
|
+
error: 'Failed to send notification',
|
|
296
|
+
requestId: canonicalRequestId,
|
|
297
|
+
timestamp: new Date().toISOString(),
|
|
298
|
+
},
|
|
299
|
+
{ status: 500 }
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
// --- APNs (iOS) path ---
|
|
304
|
+
if (!apnsClient.isConfigured()) {
|
|
305
|
+
return json(
|
|
306
|
+
{
|
|
307
|
+
details: 'Missing APNS_KEY, APNS_KEY_ID, or APNS_TEAM_ID environment variables',
|
|
308
|
+
error: 'APNs client not configured',
|
|
309
|
+
},
|
|
310
|
+
{ status: 500 }
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const deviceToken = env.DEVICE_TOKEN?.trim();
|
|
315
|
+
|
|
316
|
+
if (!deviceToken) {
|
|
317
|
+
return json(
|
|
318
|
+
{
|
|
319
|
+
details: 'DEVICE_TOKEN environment variable is missing',
|
|
320
|
+
error: 'No device token configured',
|
|
321
|
+
},
|
|
322
|
+
{ status: 500 }
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = await apnsClient.sendNotification(deviceToken, payload);
|
|
328
|
+
|
|
329
|
+
// Record in dedup cache only after successful delivery
|
|
330
|
+
recordNotification(title, message, data);
|
|
331
|
+
|
|
332
|
+
// If this is a bidirectional permission request, store it for polling
|
|
333
|
+
// only after confirming APNs delivery succeeded
|
|
334
|
+
if (waitForResponse) {
|
|
335
|
+
createPendingRequest(canonicalRequestId, {
|
|
336
|
+
sessionId: (data?.sessionId as string) || '',
|
|
337
|
+
toolInput: (data?.toolInput as Record<string, unknown>) || {},
|
|
338
|
+
toolName: (data?.toolName as string) || '',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
addNotification({
|
|
343
|
+
category: data?.category,
|
|
344
|
+
data: data as Record<string, unknown>,
|
|
345
|
+
id: canonicalRequestId,
|
|
346
|
+
message,
|
|
347
|
+
project: data?.project,
|
|
348
|
+
source: data?.source,
|
|
349
|
+
status: 'sent',
|
|
350
|
+
timestamp: new Date().toISOString(),
|
|
351
|
+
title,
|
|
352
|
+
tool: data?.tool,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return json({
|
|
356
|
+
message: 'Notification sent successfully',
|
|
357
|
+
requestId: canonicalRequestId,
|
|
358
|
+
result,
|
|
359
|
+
success: true,
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
});
|
|
362
|
+
} catch (notificationError) {
|
|
363
|
+
const err = notificationError as Error;
|
|
364
|
+
console.error(`[notify] APNs delivery failed: ${err.message}`);
|
|
365
|
+
|
|
366
|
+
addNotification({
|
|
367
|
+
category: data?.category,
|
|
368
|
+
data: data as Record<string, unknown>,
|
|
369
|
+
error: err.message,
|
|
370
|
+
id: canonicalRequestId,
|
|
371
|
+
message,
|
|
372
|
+
project: data?.project,
|
|
373
|
+
source: data?.source,
|
|
374
|
+
status: 'failed',
|
|
375
|
+
timestamp: new Date().toISOString(),
|
|
376
|
+
title,
|
|
377
|
+
tool: data?.tool,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return json(
|
|
381
|
+
{
|
|
382
|
+
details: err.message,
|
|
383
|
+
error: 'Failed to send notification',
|
|
384
|
+
requestId: canonicalRequestId,
|
|
385
|
+
timestamp: new Date().toISOString(),
|
|
386
|
+
},
|
|
387
|
+
{ status: 500 }
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} catch (error) {
|
|
392
|
+
const err = error as Error;
|
|
393
|
+
console.error('Notification error:', err);
|
|
394
|
+
return json(
|
|
395
|
+
{
|
|
396
|
+
details: err.message,
|
|
397
|
+
error: 'Failed to send notification',
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
},
|
|
400
|
+
{ status: 500 }
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export const GET: RequestHandler = ({ request, url }) => {
|
|
406
|
+
// Validate API key using timing-safe comparison
|
|
407
|
+
const authError = validateAuth(request);
|
|
408
|
+
if (authError) return authError;
|
|
409
|
+
|
|
410
|
+
const limit = parseInt(url.searchParams.get('limit') || '50');
|
|
411
|
+
const notifications = getNotifications(limit);
|
|
412
|
+
|
|
413
|
+
return json({
|
|
414
|
+
count: notifications.length,
|
|
415
|
+
notifications,
|
|
416
|
+
timestamp: new Date().toISOString(),
|
|
417
|
+
});
|
|
418
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { env } from '$env/dynamic/private';
|
|
2
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
3
|
+
import { json } from '@sveltejs/kit';
|
|
4
|
+
import QRCode from 'qrcode';
|
|
5
|
+
|
|
6
|
+
import type { RequestHandler } from './$types';
|
|
7
|
+
|
|
8
|
+
export const GET: RequestHandler = async ({ request }) => {
|
|
9
|
+
const authError = validateAuth(request);
|
|
10
|
+
if (authError) {
|
|
11
|
+
return authError;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const apiKey = env.API_KEY?.trim();
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
return json({ error: 'API_KEY not configured on server' }, { status: 500 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Derive the server URL from the incoming request's Host header so the QR
|
|
20
|
+
// code always points to the right address (works behind tunnels, proxies, etc.)
|
|
21
|
+
const proto = request.headers.get('x-forwarded-proto') || 'http';
|
|
22
|
+
const host = request.headers.get('x-forwarded-host') || request.headers.get('host') || 'localhost:3000';
|
|
23
|
+
const serverUrl = `${proto}://${host}`;
|
|
24
|
+
|
|
25
|
+
const configPayload = JSON.stringify({ apiKey, serverUrl });
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const dataUrl = await QRCode.toDataURL(configPayload, {
|
|
29
|
+
color: {
|
|
30
|
+
dark: '#ededed',
|
|
31
|
+
light: '#0a0a0a',
|
|
32
|
+
},
|
|
33
|
+
errorCorrectionLevel: 'M',
|
|
34
|
+
margin: 2,
|
|
35
|
+
type: 'image/png',
|
|
36
|
+
width: 280,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return json({ dataUrl, serverUrl });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const err = error as Error;
|
|
42
|
+
console.error('[qr-config] QR generation failed:', err.message);
|
|
43
|
+
return json({ error: 'Failed to generate QR code' }, { status: 500 });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cleanup, getDecision, setDecision } from '$lib/modules/server/apn/pending-requests';
|
|
2
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
3
|
+
import { json } from '@sveltejs/kit';
|
|
4
|
+
|
|
5
|
+
import type { RequestHandler } from './$types';
|
|
6
|
+
|
|
7
|
+
// POST /api/response — iOS app sends the user's decision
|
|
8
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
9
|
+
cleanup();
|
|
10
|
+
|
|
11
|
+
const authError = validateAuth(request);
|
|
12
|
+
if (authError) {return authError;}
|
|
13
|
+
|
|
14
|
+
let body: { decision?: string; requestId?: string };
|
|
15
|
+
try {
|
|
16
|
+
body = (await request.json()) as { decision?: string; requestId?: string };
|
|
17
|
+
} catch {
|
|
18
|
+
return json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { decision, requestId } = body;
|
|
23
|
+
|
|
24
|
+
if (!requestId || !decision) {
|
|
25
|
+
return json({ error: 'requestId and decision are required' }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (decision !== 'allow' && decision !== 'deny') {
|
|
29
|
+
return json({ error: 'decision must be "allow" or "deny"' }, { status: 400 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const found = setDecision(requestId, decision);
|
|
33
|
+
if (!found) {
|
|
34
|
+
return json({ error: 'Request not found or expired' }, { status: 404 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`[response] Decision received: ${decision} for request ${requestId}`);
|
|
38
|
+
|
|
39
|
+
return json({
|
|
40
|
+
message: 'Decision recorded',
|
|
41
|
+
success: true,
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const err = error as Error;
|
|
46
|
+
return json({ details: err.message, error: 'Failed to process response' }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// GET /api/response?requestId=xxx — notifier.cjs polls for a decision
|
|
51
|
+
export const GET: RequestHandler = ({ request, url }) => {
|
|
52
|
+
cleanup();
|
|
53
|
+
|
|
54
|
+
const authErr = validateAuth(request);
|
|
55
|
+
if (authErr) {return authErr;}
|
|
56
|
+
|
|
57
|
+
const requestId = url.searchParams.get('requestId');
|
|
58
|
+
if (!requestId) {
|
|
59
|
+
return json({ error: 'requestId query parameter is required' }, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = getDecision(requestId);
|
|
63
|
+
|
|
64
|
+
if (result.status === 'not_found') {
|
|
65
|
+
return json({ error: 'Request not found or expired' }, { status: 404 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return json({
|
|
69
|
+
decision: result.decision ?? null,
|
|
70
|
+
status: result.status,
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
});
|
|
73
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { ProjectGroup } from '$lib/modules/server/sessions/types';
|
|
2
|
+
|
|
3
|
+
import { validateAuth } from '$lib/modules/server/auth';
|
|
4
|
+
import {
|
|
5
|
+
getSessionConversation,
|
|
6
|
+
listProjectsWithSessions,
|
|
7
|
+
} from '$lib/modules/server/sessions/jsonl-reader';
|
|
8
|
+
import {
|
|
9
|
+
getOpenCodeConversation,
|
|
10
|
+
listOpenCodeProjects,
|
|
11
|
+
} from '$lib/modules/server/sessions/opencode-reader';
|
|
12
|
+
import { json } from '@sveltejs/kit';
|
|
13
|
+
|
|
14
|
+
import type { RequestHandler } from './$types';
|
|
15
|
+
|
|
16
|
+
// Server-side cache for merged projects (30s TTL)
|
|
17
|
+
let cachedProjects: null | ProjectGroup[] = null;
|
|
18
|
+
let cacheTimestamp = 0;
|
|
19
|
+
const CACHE_TTL_MS = 30_000;
|
|
20
|
+
|
|
21
|
+
function getMergedProjects(): ProjectGroup[] {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
if (cachedProjects && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
24
|
+
return cachedProjects;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const claudeProjects = listProjectsWithSessions();
|
|
28
|
+
const openCodeProjects = listOpenCodeProjects();
|
|
29
|
+
const projectsByPath = new Map<string, ProjectGroup>();
|
|
30
|
+
|
|
31
|
+
for (const p of claudeProjects) {
|
|
32
|
+
projectsByPath.set(p.fullPath, { ...p });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const op of openCodeProjects) {
|
|
36
|
+
const existing = projectsByPath.get(op.fullPath);
|
|
37
|
+
if (existing) {
|
|
38
|
+
existing.sessions.push(...op.sessions);
|
|
39
|
+
existing.sessions.sort(
|
|
40
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
|
|
41
|
+
);
|
|
42
|
+
existing.sessionCount = existing.sessions.length;
|
|
43
|
+
existing.lastModified = existing.sessions[0]?.modified || existing.lastModified;
|
|
44
|
+
existing.name = existing.name.replace(' (OpenCode)', '');
|
|
45
|
+
} else {
|
|
46
|
+
op.name = op.name.replace(' (OpenCode)', '');
|
|
47
|
+
projectsByPath.set(op.fullPath, { ...op });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
cachedProjects = [...projectsByPath.values()].sort(
|
|
52
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
53
|
+
);
|
|
54
|
+
cacheTimestamp = now;
|
|
55
|
+
return cachedProjects;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const GET: RequestHandler = ({ request, url }) => {
|
|
59
|
+
const authError = validateAuth(request);
|
|
60
|
+
if (authError) {return authError;}
|
|
61
|
+
|
|
62
|
+
const sessionId = url.searchParams.get('id');
|
|
63
|
+
|
|
64
|
+
if (sessionId) {
|
|
65
|
+
const projectId = url.searchParams.get('project') || '';
|
|
66
|
+
const offset = parseInt(url.searchParams.get('offset') || '0');
|
|
67
|
+
const limit = parseInt(url.searchParams.get('limit') || '100');
|
|
68
|
+
|
|
69
|
+
// Resolve short project ID to full path for the readers
|
|
70
|
+
const allProjects = getMergedProjects();
|
|
71
|
+
const matchedProject = projectId ? allProjects.find((p) => p.id === projectId) : undefined;
|
|
72
|
+
|
|
73
|
+
// Try Claude Code first (pass the encoded dir name for filesystem lookup)
|
|
74
|
+
const claudeProjectDir = matchedProject
|
|
75
|
+
? matchedProject.fullPath.replace(/\//g, '-')
|
|
76
|
+
: undefined;
|
|
77
|
+
let messages = getSessionConversation(sessionId, offset, limit, claudeProjectDir);
|
|
78
|
+
|
|
79
|
+
// If Claude Code reader found nothing, try OpenCode
|
|
80
|
+
if (messages.length === 0) {
|
|
81
|
+
messages = getOpenCodeConversation(sessionId, offset, limit);
|
|
82
|
+
}
|
|
83
|
+
let sessionInfo = null;
|
|
84
|
+
for (const project of allProjects) {
|
|
85
|
+
if (projectId && project.id !== projectId) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const found = project.sessions.find((s) => s.id === sessionId);
|
|
89
|
+
if (found) {
|
|
90
|
+
sessionInfo = found;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!sessionInfo) {
|
|
96
|
+
return json({ error: 'Session not found' }, { status: 404 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return json({
|
|
100
|
+
messages,
|
|
101
|
+
session: sessionInfo,
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allProjects = getMergedProjects();
|
|
107
|
+
|
|
108
|
+
const limit = parseInt(url.searchParams.get('limit') || '0');
|
|
109
|
+
const offset = parseInt(url.searchParams.get('offset') || '0');
|
|
110
|
+
|
|
111
|
+
const total = allProjects.length;
|
|
112
|
+
const paginatedProjects = limit > 0 ? allProjects.slice(offset, offset + limit) : allProjects;
|
|
113
|
+
|
|
114
|
+
return json({
|
|
115
|
+
count: allProjects.reduce((sum, p) => sum + p.sessionCount, 0),
|
|
116
|
+
projects: paginatedProjects,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
total,
|
|
119
|
+
});
|
|
120
|
+
};
|