@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,243 @@
|
|
|
1
|
+
// IMPORTANT: This module must NEVER be imported at module top level in a Svelte component.
|
|
2
|
+
// Always call createTerminal() inside onMount() only.
|
|
3
|
+
|
|
4
|
+
import type { Terminal } from '@xterm/xterm';
|
|
5
|
+
|
|
6
|
+
interface TerminalInstance {
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
fitAddon: any; // FitAddon type
|
|
9
|
+
sendInput: (data: string) => void;
|
|
10
|
+
term: Terminal;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface TerminalOptions {
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
container: HTMLElement;
|
|
16
|
+
fontSize?: number;
|
|
17
|
+
getTicket: () => Promise<string>;
|
|
18
|
+
onActivity?: (active: boolean) => void;
|
|
19
|
+
onCwd?: (path: string) => void;
|
|
20
|
+
onDisconnect?: () => void;
|
|
21
|
+
onExit?: (code: number) => void;
|
|
22
|
+
onReconnect?: () => void;
|
|
23
|
+
terminalId?: string;
|
|
24
|
+
wsUrl: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function createTerminal(options: TerminalOptions): Promise<TerminalInstance> {
|
|
28
|
+
// Dynamic imports — only loaded client-side
|
|
29
|
+
const { Terminal } = await import('@xterm/xterm');
|
|
30
|
+
const { FitAddon } = await import('@xterm/addon-fit');
|
|
31
|
+
const { WebLinksAddon } = await import('@xterm/addon-web-links');
|
|
32
|
+
|
|
33
|
+
// Also need to import the CSS
|
|
34
|
+
await import('@xterm/xterm/css/xterm.css');
|
|
35
|
+
|
|
36
|
+
const fitAddon = new FitAddon();
|
|
37
|
+
|
|
38
|
+
const term = new Terminal({
|
|
39
|
+
allowTransparency: true,
|
|
40
|
+
cursorBlink: true,
|
|
41
|
+
cursorStyle: 'block',
|
|
42
|
+
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', 'Menlo', monospace",
|
|
43
|
+
fontSize: options.fontSize || 14,
|
|
44
|
+
theme: {
|
|
45
|
+
background: '#0a0a0f',
|
|
46
|
+
black: '#0a0a0f',
|
|
47
|
+
blue: '#3b82f6',
|
|
48
|
+
brightBlack: '#64748b',
|
|
49
|
+
brightBlue: '#60a5fa',
|
|
50
|
+
brightCyan: '#67e8f9',
|
|
51
|
+
brightGreen: '#4ade80',
|
|
52
|
+
brightMagenta: '#c4b5fd',
|
|
53
|
+
brightRed: '#f87171',
|
|
54
|
+
brightWhite: '#f8fafc',
|
|
55
|
+
brightYellow: '#fbbf24',
|
|
56
|
+
cursor: '#e2e8f0',
|
|
57
|
+
cursorAccent: '#0a0a0f',
|
|
58
|
+
cyan: '#38bdf8',
|
|
59
|
+
foreground: '#e2e8f0',
|
|
60
|
+
green: '#22c55e',
|
|
61
|
+
magenta: '#a78bfa',
|
|
62
|
+
red: '#ef4444',
|
|
63
|
+
selectionBackground: 'rgba(99, 102, 241, 0.3)',
|
|
64
|
+
white: '#e2e8f0',
|
|
65
|
+
yellow: '#f59e0b',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
term.loadAddon(fitAddon);
|
|
70
|
+
term.loadAddon(new WebLinksAddon());
|
|
71
|
+
term.open(options.container);
|
|
72
|
+
fitAddon.fit();
|
|
73
|
+
|
|
74
|
+
// Block browser-level Cmd/Ctrl shortcuts from reaching the PTY.
|
|
75
|
+
// Allow Ctrl+<letter> terminal signals (Ctrl+C/D/L/R/Z etc.) through.
|
|
76
|
+
const browserShortcuts = new Set(['s', 'w', 't', 'n', 'p', 'q', 'h', 'j', 'f', 'g', 'o', 'u']);
|
|
77
|
+
term.attachCustomKeyEventHandler((e) => {
|
|
78
|
+
// Cmd+key on Mac: block known browser shortcuts, allow the rest
|
|
79
|
+
if (e.metaKey) {
|
|
80
|
+
if (e.key === 'c' || e.key === 'v') { return true; } // allow copy/paste
|
|
81
|
+
if (browserShortcuts.has(e.key)) { return false; } // block browser shortcuts
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// Ctrl+key (non-Mac modifier): allow all through to PTY (Ctrl+C/D/L/R/Z etc.)
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Clipboard image paste interception
|
|
89
|
+
let pasteListener: ((e: ClipboardEvent) => void) | null = null;
|
|
90
|
+
if (options.terminalId && options.apiKey) {
|
|
91
|
+
const pasteTermId = options.terminalId;
|
|
92
|
+
const pasteApiKey = options.apiKey;
|
|
93
|
+
|
|
94
|
+
pasteListener = async (e: ClipboardEvent) => {
|
|
95
|
+
try {
|
|
96
|
+
if (!e.clipboardData) return;
|
|
97
|
+
const items = Array.from(e.clipboardData.items);
|
|
98
|
+
const imageItem = items.find(item => item.type.startsWith('image/'));
|
|
99
|
+
if (!imageItem) return; // No image — let normal paste proceed
|
|
100
|
+
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
const blob = imageItem.getAsFile();
|
|
103
|
+
if (!blob) return;
|
|
104
|
+
|
|
105
|
+
// Read image as base64
|
|
106
|
+
const reader = new FileReader();
|
|
107
|
+
const base64 = await new Promise<string>((resolve, reject) => {
|
|
108
|
+
reader.onload = () => resolve(reader.result as string);
|
|
109
|
+
reader.onerror = reject;
|
|
110
|
+
reader.readAsDataURL(blob);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Upload to server
|
|
114
|
+
const res = await fetch(`/api/terminals/${pasteTermId}/paste-image`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'Authorization': `Bearer ${pasteApiKey}`,
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ image: base64 }),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Send Ctrl+V (0x16) to PTY only after a successful upload
|
|
124
|
+
if (res.ok && ws?.readyState === WebSocket.OPEN) {
|
|
125
|
+
ws.send(JSON.stringify({ data: '\x16', type: 'input' }));
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Silent failure — don't break text paste
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
options.container.addEventListener('paste', pasteListener as EventListener);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// WebSocket connection
|
|
136
|
+
let ws: null | WebSocket = null;
|
|
137
|
+
let reconnectTimer: null | ReturnType<typeof setTimeout> = null;
|
|
138
|
+
let reconnectDelay = 1000;
|
|
139
|
+
let disposed = false;
|
|
140
|
+
|
|
141
|
+
async function connect() {
|
|
142
|
+
if (disposed) {return;}
|
|
143
|
+
|
|
144
|
+
let ticket: string;
|
|
145
|
+
try {
|
|
146
|
+
ticket = await options.getTicket();
|
|
147
|
+
} catch {
|
|
148
|
+
// Ticket fetch failed — schedule a retry
|
|
149
|
+
if (!disposed) {
|
|
150
|
+
options.onDisconnect?.();
|
|
151
|
+
reconnectTimer = setTimeout(() => {
|
|
152
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30_000);
|
|
153
|
+
void connect();
|
|
154
|
+
}, reconnectDelay);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (disposed) {return;}
|
|
160
|
+
|
|
161
|
+
ws = new WebSocket(`${options.wsUrl}?ticket=${ticket}`);
|
|
162
|
+
|
|
163
|
+
ws.onopen = () => {
|
|
164
|
+
reconnectDelay = 1000; // Reset backoff
|
|
165
|
+
options.onReconnect?.();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
ws.onmessage = (event) => {
|
|
169
|
+
const msg = JSON.parse(event.data);
|
|
170
|
+
if (msg.type === 'output') {
|
|
171
|
+
term.write(msg.data);
|
|
172
|
+
} else if (msg.type === 'scrollback') {
|
|
173
|
+
term.write(msg.data);
|
|
174
|
+
} else if (msg.type === 'exit') {
|
|
175
|
+
term.write(`\r\n\x1b[90m[Process exited with code ${msg.code}]\x1b[0m\r\n`);
|
|
176
|
+
// Process exited — stop reconnection and notify parent
|
|
177
|
+
disposed = true;
|
|
178
|
+
if (reconnectTimer) {clearTimeout(reconnectTimer);}
|
|
179
|
+
options.onExit?.(msg.code);
|
|
180
|
+
} else if (msg.type === 'output-dropped') {
|
|
181
|
+
term.write(`\r\n\x1b[33m[${msg.bytes} bytes dropped]\x1b[0m\r\n`);
|
|
182
|
+
} else if (msg.type === 'activity') {
|
|
183
|
+
options.onActivity?.(msg.active);
|
|
184
|
+
} else if (msg.type === 'cwd') {
|
|
185
|
+
options.onCwd?.(msg.path);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
ws.onclose = () => {
|
|
190
|
+
if (disposed) {return;}
|
|
191
|
+
options.onDisconnect?.();
|
|
192
|
+
// Exponential backoff reconnect
|
|
193
|
+
reconnectTimer = setTimeout(() => {
|
|
194
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30_000);
|
|
195
|
+
void connect();
|
|
196
|
+
}, reconnectDelay);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
void connect();
|
|
201
|
+
|
|
202
|
+
// Terminal input -> WebSocket
|
|
203
|
+
term.onData((data) => {
|
|
204
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
205
|
+
ws.send(JSON.stringify({ data, type: 'input' }));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Handle resize
|
|
210
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
211
|
+
fitAddon.fit();
|
|
212
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
213
|
+
ws.send(JSON.stringify({ cols: term.cols, rows: term.rows, type: 'resize' }));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
resizeObserver.observe(options.container);
|
|
217
|
+
|
|
218
|
+
function dispose() {
|
|
219
|
+
disposed = true;
|
|
220
|
+
if (reconnectTimer) {clearTimeout(reconnectTimer);}
|
|
221
|
+
if (pasteListener) {
|
|
222
|
+
options.container.removeEventListener('paste', pasteListener as EventListener);
|
|
223
|
+
}
|
|
224
|
+
resizeObserver.disconnect();
|
|
225
|
+
ws?.close();
|
|
226
|
+
term.dispose();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function sendInput(data: string) {
|
|
230
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
231
|
+
ws.send(JSON.stringify({ data, type: 'input' }));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { dispose, fitAddon, sendInput, term };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Helper to send signals
|
|
239
|
+
export function sendSignal(ws: WebSocket, signal: string): void {
|
|
240
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
241
|
+
ws.send(JSON.stringify({ signal, type: 'signal' }));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { env } from '$env/dynamic/private';
|
|
2
|
+
// APNs service using proven library instead of manual implementation
|
|
3
|
+
import apn from '@parse/node-apn';
|
|
4
|
+
|
|
5
|
+
import type { LibraryResult as APNsLibraryResult, NotificationPayload } from './types';
|
|
6
|
+
|
|
7
|
+
export class LibraryAPNsService {
|
|
8
|
+
private bundleId: string | undefined;
|
|
9
|
+
private configured = false;
|
|
10
|
+
private keyId: string | undefined;
|
|
11
|
+
private privateKey: string | undefined;
|
|
12
|
+
private provider: apn.Provider | null = null;
|
|
13
|
+
private teamId: string | undefined;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.keyId = env.APNS_KEY_ID;
|
|
17
|
+
this.teamId = env.APNS_TEAM_ID;
|
|
18
|
+
this.bundleId = env.APNS_BUNDLE_ID;
|
|
19
|
+
this.privateKey = env.APNS_KEY;
|
|
20
|
+
|
|
21
|
+
if (!this.keyId || !this.teamId || !this.bundleId || !this.privateKey) {
|
|
22
|
+
console.error(
|
|
23
|
+
'[apns] Missing required configuration (APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, or APNS_KEY)'
|
|
24
|
+
);
|
|
25
|
+
this.configured = false;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const production = env.APNS_PRODUCTION === 'true';
|
|
30
|
+
const options = {
|
|
31
|
+
production,
|
|
32
|
+
token: {
|
|
33
|
+
key: this.privateKey,
|
|
34
|
+
keyId: this.keyId,
|
|
35
|
+
teamId: this.teamId,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
this.provider = new apn.Provider(options);
|
|
41
|
+
this.configured = true;
|
|
42
|
+
console.log(`[apns] Provider initialized (${production ? 'production' : 'sandbox'} mode)`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const err = error as Error;
|
|
45
|
+
console.error('[apns] Failed to create provider:', err.message);
|
|
46
|
+
this.configured = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isConfigured(): boolean {
|
|
51
|
+
return this.configured;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async sendNotification(
|
|
55
|
+
deviceToken: string,
|
|
56
|
+
payload: NotificationPayload
|
|
57
|
+
): Promise<{
|
|
58
|
+
details?: unknown[];
|
|
59
|
+
error?: string;
|
|
60
|
+
failed: number;
|
|
61
|
+
sent: number;
|
|
62
|
+
success: boolean;
|
|
63
|
+
}> {
|
|
64
|
+
if (!this.configured || !this.provider) {
|
|
65
|
+
throw new Error('APNs service not configured properly');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!deviceToken || !payload) {
|
|
69
|
+
throw new Error('Device token and payload are required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const notification = new apn.Notification();
|
|
73
|
+
|
|
74
|
+
notification.alert = {
|
|
75
|
+
body: payload.body ?? payload.message ?? '',
|
|
76
|
+
title: payload.title,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
notification.badge = payload.badge ?? 1;
|
|
80
|
+
notification.sound = payload.sound ?? 'default';
|
|
81
|
+
notification.topic = this.bundleId!;
|
|
82
|
+
|
|
83
|
+
if (payload.category) {
|
|
84
|
+
notification.aps.category = payload.category;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (payload.data) {
|
|
88
|
+
notification.payload = payload.data;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = (await this.provider.send(notification, deviceToken)) as APNsLibraryResult;
|
|
92
|
+
|
|
93
|
+
if (result.failed && result.failed.length > 0) {
|
|
94
|
+
console.error(`[apns] Full failed result: ${JSON.stringify(result.failed[0])}`);
|
|
95
|
+
const failedItem = result.failed[0] as unknown as Record<string, unknown>;
|
|
96
|
+
const rawReason =
|
|
97
|
+
(failedItem?.response as Record<string, unknown>)?.reason ??
|
|
98
|
+
failedItem?.status ??
|
|
99
|
+
failedItem?.error;
|
|
100
|
+
const reason =
|
|
101
|
+
typeof rawReason === 'string' ? rawReason : JSON.stringify(rawReason ?? failedItem);
|
|
102
|
+
console.error(`[apns] Delivery failed: ${reason}`);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
error: reason,
|
|
106
|
+
failed: result.failed?.length || 0,
|
|
107
|
+
sent: result.sent?.length || 0,
|
|
108
|
+
success: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (result.sent && result.sent.length > 0) {
|
|
113
|
+
return {
|
|
114
|
+
details: result.sent,
|
|
115
|
+
failed: 0,
|
|
116
|
+
sent: result.sent.length,
|
|
117
|
+
success: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
details: [result as unknown as Record<string, unknown>],
|
|
123
|
+
error: 'Unexpected result format',
|
|
124
|
+
failed: 1,
|
|
125
|
+
sent: 0,
|
|
126
|
+
success: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
shutdown(): void {
|
|
131
|
+
if (this.provider) {
|
|
132
|
+
void this.provider.shutdown();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default LibraryAPNsService;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// In-memory notification history store.
|
|
2
|
+
// Same pattern as pending-requests.ts — suitable for single-instance deployments.
|
|
3
|
+
|
|
4
|
+
export interface NotificationRecord {
|
|
5
|
+
category?: string;
|
|
6
|
+
data?: Record<string, unknown>;
|
|
7
|
+
error?: string;
|
|
8
|
+
id: string;
|
|
9
|
+
message: string;
|
|
10
|
+
project?: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
status: 'failed' | 'filtered' | 'sent';
|
|
13
|
+
timestamp: string;
|
|
14
|
+
title: string;
|
|
15
|
+
tool?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const MAX_HISTORY = 100;
|
|
19
|
+
|
|
20
|
+
const history: NotificationRecord[] = [];
|
|
21
|
+
|
|
22
|
+
export function addNotification(record: NotificationRecord): void {
|
|
23
|
+
history.unshift(record); // newest first
|
|
24
|
+
if (history.length > MAX_HISTORY) {
|
|
25
|
+
history.length = MAX_HISTORY; // trim old entries
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function clearNotifications(): void {
|
|
30
|
+
history.length = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getNotifications(limit = 50): NotificationRecord[] {
|
|
34
|
+
return history.slice(0, limit);
|
|
35
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getNotifications, type NotificationRecord } from './notification-history';
|
|
2
|
+
|
|
3
|
+
export interface Session {
|
|
4
|
+
duration: number; // seconds
|
|
5
|
+
endTime: string;
|
|
6
|
+
eventCount: number;
|
|
7
|
+
events: NotificationRecord[];
|
|
8
|
+
filesModified: string[];
|
|
9
|
+
id: string;
|
|
10
|
+
project: string;
|
|
11
|
+
runtime: string;
|
|
12
|
+
startTime: string;
|
|
13
|
+
status: 'active' | 'complete';
|
|
14
|
+
toolsUsed: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SESSION_GAP_MS = 30 * 60 * 1000; // 30 min gap = new session
|
|
18
|
+
const ACTIVE_THRESHOLD_MS = 5 * 60 * 1000; // active if last event < 5 min ago
|
|
19
|
+
|
|
20
|
+
export function getSessionById(id: string): null | Session {
|
|
21
|
+
return getSessions().find((s) => s.id === id) || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getSessions(): Session[] {
|
|
25
|
+
const notifications = getNotifications(100);
|
|
26
|
+
if (notifications.length === 0) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Sort by timestamp ascending for grouping
|
|
31
|
+
const sorted = [...notifications].sort(
|
|
32
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const sessions: Session[] = [];
|
|
36
|
+
let currentGroup: NotificationRecord[] = [sorted[0]];
|
|
37
|
+
|
|
38
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
39
|
+
const prev = new Date(sorted[i - 1].timestamp).getTime();
|
|
40
|
+
const curr = new Date(sorted[i].timestamp).getTime();
|
|
41
|
+
|
|
42
|
+
if (curr - prev > SESSION_GAP_MS) {
|
|
43
|
+
// Gap too large — start new session
|
|
44
|
+
sessions.push(buildSession(currentGroup, sessions.length + 1));
|
|
45
|
+
currentGroup = [sorted[i]];
|
|
46
|
+
} else {
|
|
47
|
+
currentGroup.push(sorted[i]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Don't forget last group
|
|
52
|
+
if (currentGroup.length > 0) {
|
|
53
|
+
sessions.push(buildSession(currentGroup, sessions.length + 1));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Return newest first
|
|
57
|
+
return sessions.reverse();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildSession(events: NotificationRecord[], index: number): Session {
|
|
61
|
+
const startTime = events[0].timestamp;
|
|
62
|
+
const endTime = events[events.length - 1].timestamp;
|
|
63
|
+
const startMs = new Date(startTime).getTime();
|
|
64
|
+
const endMs = new Date(endTime).getTime();
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
|
|
67
|
+
// Extract tools from events
|
|
68
|
+
const tools = new Set<string>();
|
|
69
|
+
const files = new Set<string>();
|
|
70
|
+
let project = 'unknown';
|
|
71
|
+
let runtime = 'claude-code';
|
|
72
|
+
|
|
73
|
+
for (const event of events) {
|
|
74
|
+
if (event.tool) {
|
|
75
|
+
tools.add(event.tool);
|
|
76
|
+
}
|
|
77
|
+
if (event.data?.tool && typeof event.data.tool === 'string') {
|
|
78
|
+
tools.add(event.data.tool);
|
|
79
|
+
}
|
|
80
|
+
if (event.project) {
|
|
81
|
+
project = event.project;
|
|
82
|
+
}
|
|
83
|
+
if (event.data?.project && typeof event.data.project === 'string') {
|
|
84
|
+
project = event.data.project;
|
|
85
|
+
}
|
|
86
|
+
if (event.data?.runtime && typeof event.data.runtime === 'string') {
|
|
87
|
+
runtime = event.data.runtime;
|
|
88
|
+
}
|
|
89
|
+
// Extract files from data
|
|
90
|
+
if (event.data?.file && typeof event.data.file === 'string') {
|
|
91
|
+
files.add(event.data.file);
|
|
92
|
+
}
|
|
93
|
+
if (event.data?.files && typeof event.data.files === 'string') {
|
|
94
|
+
event.data.files.split(',').forEach((f: string) => {
|
|
95
|
+
if (f.trim()) {
|
|
96
|
+
files.add(f.trim());
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isActive = now - endMs < ACTIVE_THRESHOLD_MS;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
duration: Math.round((endMs - startMs) / 1000),
|
|
106
|
+
endTime,
|
|
107
|
+
eventCount: events.length,
|
|
108
|
+
events, // already sorted ascending
|
|
109
|
+
filesModified: [...files],
|
|
110
|
+
id: `session-${index}`,
|
|
111
|
+
project,
|
|
112
|
+
runtime,
|
|
113
|
+
startTime,
|
|
114
|
+
status: isActive ? 'active' : 'complete',
|
|
115
|
+
toolsUsed: [...tools],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Shared in-memory store for pending permission requests.
|
|
2
|
+
// Used by /api/notify (creates entries) and /api/response (reads/updates).
|
|
3
|
+
|
|
4
|
+
export interface PendingRequest {
|
|
5
|
+
createdAt: number;
|
|
6
|
+
decidedAt: null | number;
|
|
7
|
+
decision: 'allow' | 'deny' | null;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
toolInput: Record<string, unknown>;
|
|
10
|
+
toolName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
14
|
+
|
|
15
|
+
const pendingRequests = new Map<string, PendingRequest>();
|
|
16
|
+
|
|
17
|
+
export function cleanup(maxAgeMs: number = MAX_AGE_MS): void {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
for (const [id, entry] of pendingRequests.entries()) {
|
|
20
|
+
if (now - entry.createdAt > maxAgeMs) {
|
|
21
|
+
pendingRequests.delete(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createPendingRequest(
|
|
27
|
+
requestId: string,
|
|
28
|
+
data: { sessionId: string; toolInput: Record<string, unknown>; toolName: string }
|
|
29
|
+
): void {
|
|
30
|
+
cleanup();
|
|
31
|
+
pendingRequests.set(requestId, {
|
|
32
|
+
createdAt: Date.now(),
|
|
33
|
+
decidedAt: null,
|
|
34
|
+
decision: null,
|
|
35
|
+
sessionId: data.sessionId,
|
|
36
|
+
toolInput: data.toolInput,
|
|
37
|
+
toolName: data.toolName,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getDecision(requestId: string): {
|
|
42
|
+
decision?: 'allow' | 'deny';
|
|
43
|
+
status: 'decided' | 'not_found' | 'pending';
|
|
44
|
+
} {
|
|
45
|
+
const entry = pendingRequests.get(requestId);
|
|
46
|
+
if (!entry) {
|
|
47
|
+
return { status: 'not_found' };
|
|
48
|
+
}
|
|
49
|
+
if (entry.decision) {
|
|
50
|
+
// Clean up after reading a decided response
|
|
51
|
+
pendingRequests.delete(requestId);
|
|
52
|
+
return { decision: entry.decision, status: 'decided' };
|
|
53
|
+
}
|
|
54
|
+
return { status: 'pending' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function setDecision(requestId: string, decision: 'allow' | 'deny'): boolean {
|
|
58
|
+
const entry = pendingRequests.get(requestId);
|
|
59
|
+
if (!entry) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
entry.decision = decision;
|
|
63
|
+
entry.decidedAt = Date.now();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Error as APNsError,
|
|
3
|
+
ErrorData,
|
|
4
|
+
LibraryFailedItem,
|
|
5
|
+
LibrarySentItem,
|
|
6
|
+
RawResponse,
|
|
7
|
+
SentDetail,
|
|
8
|
+
} from '$generated/types';
|
|
9
|
+
|
|
10
|
+
// Types not yet generated by type-crafter (contain $ref/arrays/oneOf)
|
|
11
|
+
// TODO: Remove these once type-crafter generates them
|
|
12
|
+
|
|
13
|
+
export interface LibraryResult {
|
|
14
|
+
failed: LibraryFailedItem[] | null;
|
|
15
|
+
sent: LibrarySentItem[] | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NotificationPayload {
|
|
19
|
+
badge: null | number;
|
|
20
|
+
body: null | string;
|
|
21
|
+
category?: null | string;
|
|
22
|
+
data: null | Record<string, unknown>;
|
|
23
|
+
message: null | string;
|
|
24
|
+
sound: null | string;
|
|
25
|
+
title: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NotificationResult {
|
|
29
|
+
apnsId: null | string;
|
|
30
|
+
details: null | SentDetail[];
|
|
31
|
+
error: ErrorData | null | string;
|
|
32
|
+
errorData: ErrorData | null;
|
|
33
|
+
errors: APNsError[] | null;
|
|
34
|
+
failed: number;
|
|
35
|
+
headers: null | Record<string, unknown>;
|
|
36
|
+
response: null | RawResponse;
|
|
37
|
+
responseBody: null | string;
|
|
38
|
+
sent: number;
|
|
39
|
+
status: null | number;
|
|
40
|
+
statusCode: null | number;
|
|
41
|
+
success: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
APNsError as Error,
|
|
46
|
+
ErrorData,
|
|
47
|
+
LibraryFailedItem,
|
|
48
|
+
LibrarySentItem,
|
|
49
|
+
RawResponse,
|
|
50
|
+
SentDetail,
|
|
51
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { env } from '$env/dynamic/private';
|
|
2
|
+
import { timingSafeEqual } from 'crypto';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate Bearer token authentication against the API_KEY environment variable.
|
|
6
|
+
* Returns a 401 Response if auth fails, or null if auth passed.
|
|
7
|
+
*/
|
|
8
|
+
export function validateAuth(request: Request): null | Response {
|
|
9
|
+
const auth = request.headers.get('Authorization') || request.headers.get('authorization');
|
|
10
|
+
if (!auth?.startsWith('Bearer ')) {
|
|
11
|
+
return new Response(JSON.stringify({ error: 'Missing authorization' }), {
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
status: 401,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const token = auth.slice(7);
|
|
17
|
+
const expectedKey = env.API_KEY?.trim();
|
|
18
|
+
if (!expectedKey) {
|
|
19
|
+
return new Response(JSON.stringify({ error: 'Invalid API key' }), {
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
status: 401,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Timing-safe comparison to prevent timing attacks on the API key.
|
|
25
|
+
const tokenBuf = Buffer.from(token);
|
|
26
|
+
const expectedBuf = Buffer.from(expectedKey);
|
|
27
|
+
if (tokenBuf.length !== expectedBuf.length || !timingSafeEqual(tokenBuf, expectedBuf)) {
|
|
28
|
+
return new Response(JSON.stringify({ error: 'Invalid API key' }), {
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
status: 401,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return null; // auth passed
|
|
34
|
+
}
|