@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,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": ["Bash", "Edit", "Write", "Read"]
|
|
4
|
+
},
|
|
5
|
+
"hooks": {
|
|
6
|
+
"PreToolUse": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": "",
|
|
9
|
+
"hooks": [
|
|
10
|
+
{
|
|
11
|
+
"type": "command",
|
|
12
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs PreToolUse"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"PostToolUse": [
|
|
18
|
+
{
|
|
19
|
+
"matcher": "",
|
|
20
|
+
"hooks": [
|
|
21
|
+
{
|
|
22
|
+
"type": "command",
|
|
23
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs PostToolUse"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"PostToolUseFailure": [
|
|
29
|
+
{
|
|
30
|
+
"matcher": "",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs PostToolUseFailure"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"Stop": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"type": "command",
|
|
45
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs Stop"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"SessionStart": [
|
|
51
|
+
{
|
|
52
|
+
"matcher": "",
|
|
53
|
+
"hooks": [
|
|
54
|
+
{
|
|
55
|
+
"type": "command",
|
|
56
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs SessionStart"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"SessionEnd": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs SessionEnd"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"Notification": [
|
|
73
|
+
{
|
|
74
|
+
"matcher": "",
|
|
75
|
+
"hooks": [
|
|
76
|
+
{
|
|
77
|
+
"type": "command",
|
|
78
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs Notification"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"PermissionRequest": [
|
|
84
|
+
{
|
|
85
|
+
"matcher": "",
|
|
86
|
+
"hooks": [
|
|
87
|
+
{
|
|
88
|
+
"type": "command",
|
|
89
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs PermissionRequest",
|
|
90
|
+
"timeout": 180
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"SubagentStart": [
|
|
96
|
+
{
|
|
97
|
+
"matcher": "",
|
|
98
|
+
"hooks": [
|
|
99
|
+
{
|
|
100
|
+
"type": "command",
|
|
101
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs SubagentStart"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"SubagentStop": [
|
|
107
|
+
{
|
|
108
|
+
"matcher": "",
|
|
109
|
+
"hooks": [
|
|
110
|
+
{
|
|
111
|
+
"type": "command",
|
|
112
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs SubagentStop"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"UserPromptSubmit": [
|
|
118
|
+
{
|
|
119
|
+
"matcher": "",
|
|
120
|
+
"hooks": [
|
|
121
|
+
{
|
|
122
|
+
"type": "command",
|
|
123
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs UserPromptSubmit"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"TeammateIdle": [
|
|
129
|
+
{
|
|
130
|
+
"matcher": "",
|
|
131
|
+
"hooks": [
|
|
132
|
+
{
|
|
133
|
+
"type": "command",
|
|
134
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs TeammateIdle"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"TaskCompleted": [
|
|
140
|
+
{
|
|
141
|
+
"matcher": "",
|
|
142
|
+
"hooks": [
|
|
143
|
+
{
|
|
144
|
+
"type": "command",
|
|
145
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs TaskCompleted"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"PreCompact": [
|
|
151
|
+
{
|
|
152
|
+
"matcher": "",
|
|
153
|
+
"hooks": [
|
|
154
|
+
{
|
|
155
|
+
"type": "command",
|
|
156
|
+
"command": "SHOOTER_USE_LOCAL=true SHOOTER_LOCAL_PORT=3000 API_KEY=$API_KEY node .claude/hooks/notifier.cjs PreCompact"
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# Shooter
|
|
2
|
+
|
|
3
|
+
**Mobile push notifications and remote terminal access for AI coding sessions.**
|
|
4
|
+
|
|
5
|
+
[](https://kit.svelte.dev/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
[](#websocket-channels)
|
|
9
|
+
[](#docker)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What is Shooter?
|
|
15
|
+
|
|
16
|
+
Shooter turns your phone into a remote control for AI coding sessions running on your dev machine. It delivers push notifications to iOS and Android when Claude Code or OpenCode events occur -- tool usage, permission requests, session completions -- and lets you approve or deny permission prompts directly from a notification. You can also launch remote terminal sessions, stream output in real time, and browse structured AI conversation history, all from a mobile-optimized web interface accessible anywhere through a Cloudflare Tunnel.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Push notifications** -- Real-time alerts for tool usage, permission requests, session starts/stops, errors, and task completions (iOS via APNs, Android via FCM)
|
|
21
|
+
- **Bidirectional permissions** -- Approve or deny Claude Code permission prompts from your phone; the hook blocks until you respond
|
|
22
|
+
- **Remote terminal** -- Launch shell, Claude Code, or OpenCode sessions from your phone with full xterm.js rendering
|
|
23
|
+
- **Terminal persistence** -- PTY processes run in holder processes that survive server restarts; metadata persisted in SQLite
|
|
24
|
+
- **Structured Chat view** -- AI conversations rendered as message bubbles with tool-use cards and thinking indicators, parsed live from JSONL session files
|
|
25
|
+
- **Session browser** -- Browse coding session history across all projects
|
|
26
|
+
- **QR code pairing** -- Scan a QR code from the `/config` page to connect mobile apps to the server
|
|
27
|
+
- **WebSocket streaming** -- Three multiplexed channels: terminal I/O, session updates, and global events
|
|
28
|
+
- **Quick keys** -- Mobile-optimized touch bar for Ctrl+C, Tab, arrow keys, Esc, and other special characters
|
|
29
|
+
- **Claude Code hooks** -- Lifecycle hooks for 13 event types with context-aware notification categorization
|
|
30
|
+
- **Docker support** -- Multi-stage Dockerfile with arm64 and amd64 support
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/juspay/shooter.git
|
|
38
|
+
cd shooter
|
|
39
|
+
pnpm install
|
|
40
|
+
pnpm setup # interactive wizard: generates .env, builds, runs health check
|
|
41
|
+
pnpm start # start the server on http://localhost:3000
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser. Visit `/config` to enter your API key for the web UI.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## All Setup Methods
|
|
49
|
+
|
|
50
|
+
| Method | Command | Notes |
|
|
51
|
+
|--------|---------|-------|
|
|
52
|
+
| Interactive wizard | `pnpm setup` | Recommended. Walks through env config, builds, and verifies. |
|
|
53
|
+
| CLI (npx) | `npx @juspay/shooter setup` | No clone needed -- runs the setup wizard directly from npm |
|
|
54
|
+
| One-command install | `curl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh \| sh` | Clones to `~/.shooter`, installs deps, runs wizard |
|
|
55
|
+
| Docker | `docker compose up -d` | See [Docker](#docker) |
|
|
56
|
+
| Manual | See [Manual Setup](#manual-setup) | For advanced users |
|
|
57
|
+
|
|
58
|
+
### Manual Setup
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/juspay/shooter.git
|
|
62
|
+
cd shooter
|
|
63
|
+
pnpm install
|
|
64
|
+
cp .env.example .env
|
|
65
|
+
# Edit .env with your values (at minimum, set API_KEY)
|
|
66
|
+
pnpm build
|
|
67
|
+
pnpm start
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The hook notifier reads `API_KEY` from the environment. Export it in your shell profile so hooks can authenticate with the server:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
echo 'export API_KEY="your-api-key-here"' >> ~/.zshrc
|
|
74
|
+
source ~/.zshrc
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Architecture
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
+----------------------------------------------------------+
|
|
83
|
+
| Dev Machine |
|
|
84
|
+
| |
|
|
85
|
+
| SvelteKit Server (adapter-node, port 3000) |
|
|
86
|
+
| +-- REST API (/api/terminals, /api/notify, ...) |
|
|
87
|
+
| +-- WebSocket Server (ws, noServer mode) |
|
|
88
|
+
| +-- PTY Manager (node-pty + holder processes) |
|
|
89
|
+
| +-- Terminal Store (SQLite persistence) |
|
|
90
|
+
| +-- Session Watcher (chokidar file watching) |
|
|
91
|
+
| +-- APNs Client (iOS push via @parse/node-apn) |
|
|
92
|
+
| +-- FCM Client (Android push via firebase-admin) |
|
|
93
|
+
+------------------------------+---------------------------+
|
|
94
|
+
|
|
|
95
|
+
Cloudflare Tunnel
|
|
96
|
+
shooter.yourdomain.com
|
|
97
|
+
|
|
|
98
|
+
+----------------------+----------------------+
|
|
99
|
+
| | |
|
|
100
|
+
+-------+--------+ +--------+-------+ +----------+------+
|
|
101
|
+
| Mobile Browser | | iOS App | | Android App |
|
|
102
|
+
| (web UI) | | (APNs push + | | (FCM push + |
|
|
103
|
+
| Terminal, Chat, | | permission | | WebView) |
|
|
104
|
+
| Session viewer | | responses) | | |
|
|
105
|
+
+-----------------+ +----------------+ +-----------------+
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Server entry point:** `server.ts` creates an HTTP server wrapping the SvelteKit handler, attaches a WebSocket server in `noServer` mode, and handles upgrade requests with ticket-based authentication.
|
|
109
|
+
|
|
110
|
+
**Terminal persistence:** PTY processes run inside separate holder processes (`pty-holder.cjs`) that survive server restarts. Terminal metadata (ID, PID, command, cwd) is persisted in SQLite so the server can reattach on restart.
|
|
111
|
+
|
|
112
|
+
**Three WebSocket channels:**
|
|
113
|
+
|
|
114
|
+
| Channel | Path | Purpose |
|
|
115
|
+
|---------|------|---------|
|
|
116
|
+
| Terminal I/O | `/ws/terminal/:id` | Raw PTY byte stream (xterm.js) |
|
|
117
|
+
| Session stream | `/ws/session/:id` | Structured AI conversation updates |
|
|
118
|
+
| Global events | `/ws/events` | Server broadcasts (new sessions, exits, permissions) |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Configuration
|
|
123
|
+
|
|
124
|
+
Copy `.env.example` to `.env` and fill in your values. The `pnpm setup` wizard handles this interactively.
|
|
125
|
+
|
|
126
|
+
| Variable | Required | Default | Description |
|
|
127
|
+
|----------|----------|---------|-------------|
|
|
128
|
+
| `API_KEY` | **Yes** | -- | Bearer token for authenticating all API and hook requests |
|
|
129
|
+
| `PORT` | No | `3000` | HTTP server port |
|
|
130
|
+
| `DEVICE_PLATFORM` | No | `ios` | Push notification target: `ios` or `android` |
|
|
131
|
+
| `APNS_KEY` | No | -- | APNs private key (`.p8` file contents, newlines escaped as `\n`) |
|
|
132
|
+
| `APNS_KEY_ID` | No | -- | 10-character APNs key identifier from Apple Developer portal |
|
|
133
|
+
| `APNS_TEAM_ID` | No | -- | 10-character Apple Team ID |
|
|
134
|
+
| `APNS_BUNDLE_ID` | No | -- | iOS app bundle identifier (must match Xcode project) |
|
|
135
|
+
| `APNS_PRODUCTION` | No | `false` | Set `true` for TestFlight / App Store builds |
|
|
136
|
+
| `DEVICE_TOKEN` | No | -- | Target iOS device token (64-character hex) |
|
|
137
|
+
| `FCM_PROJECT_ID` | No | -- | Firebase project ID |
|
|
138
|
+
| `FCM_CLIENT_EMAIL` | No | -- | Firebase service account email |
|
|
139
|
+
| `FCM_PRIVATE_KEY` | No | -- | Firebase service account private key (PEM format) |
|
|
140
|
+
| `ANDROID_DEVICE_TOKEN` | No | -- | Target Android FCM device token |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## iOS Setup
|
|
145
|
+
|
|
146
|
+
### Prerequisites
|
|
147
|
+
|
|
148
|
+
- macOS with Xcode installed
|
|
149
|
+
- Apple Developer account with Push Notifications capability
|
|
150
|
+
- Physical iOS device (push notifications do not work in the simulator)
|
|
151
|
+
|
|
152
|
+
### APNs Key Setup
|
|
153
|
+
|
|
154
|
+
1. Go to [Apple Developer > Keys](https://developer.apple.com/account/resources/authkeys/list) and create a new key with **Apple Push Notifications service (APNs)** enabled
|
|
155
|
+
2. Download the `.p8` file
|
|
156
|
+
3. Note the **Key ID** (10 characters) shown after creation
|
|
157
|
+
4. Find your **Team ID** in [Membership Details](https://developer.apple.com/account/#/membership)
|
|
158
|
+
|
|
159
|
+
Add these to your `.env`:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
APNS_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
163
|
+
APNS_KEY_ID=ABC123DEFG
|
|
164
|
+
APNS_TEAM_ID=XYZ789KLMN
|
|
165
|
+
APNS_BUNDLE_ID=com.yourcompany.shooter
|
|
166
|
+
DEVICE_TOKEN=<64-char-hex-from-device>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Building the iOS App
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
cd ios/Shooter
|
|
173
|
+
open Shooter.xcodeproj
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
1. Select your signing team in **Signing & Capabilities**
|
|
177
|
+
2. Ensure the **Push Notifications** capability is enabled
|
|
178
|
+
3. Build and run on a physical device
|
|
179
|
+
4. The device token is printed to the Xcode console on first launch
|
|
180
|
+
|
|
181
|
+
For TestFlight or App Store builds, set `APNS_PRODUCTION=true` in your server `.env` to route through the production APNs gateway.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Android Setup
|
|
186
|
+
|
|
187
|
+
### Prerequisites
|
|
188
|
+
|
|
189
|
+
- Android Studio
|
|
190
|
+
- Gradle 8.12+ (for generating the wrapper)
|
|
191
|
+
- Firebase project with Cloud Messaging enabled
|
|
192
|
+
|
|
193
|
+
### Firebase Setup
|
|
194
|
+
|
|
195
|
+
1. Create a project in the [Firebase Console](https://console.firebase.google.com/)
|
|
196
|
+
2. Add an Android app with application ID `com.shooter.android`
|
|
197
|
+
3. Download `google-services.json` and place it in `android/app/`
|
|
198
|
+
4. Go to **Project Settings > Service Accounts** and generate a new private key
|
|
199
|
+
5. Copy `project_id`, `client_email`, and `private_key` from the downloaded JSON into your `.env`:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
FCM_PROJECT_ID=your-firebase-project-id
|
|
203
|
+
FCM_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
|
|
204
|
+
FCM_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
|
|
205
|
+
ANDROID_DEVICE_TOKEN=<fcm-device-token>
|
|
206
|
+
DEVICE_PLATFORM=android
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Building the Android App
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd android
|
|
213
|
+
chmod +x setup.sh
|
|
214
|
+
./setup.sh # generates Gradle wrapper
|
|
215
|
+
./gradlew assembleDebug
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The app targets SDK 35 (min SDK 26) and uses a WebView that connects to your Shooter server URL.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Claude Code Hooks
|
|
223
|
+
|
|
224
|
+
Shooter integrates with Claude Code through lifecycle hooks defined in `.claude/settings.json`. A unified notifier script (`.claude/hooks/notifier.cjs`) handles all hook events.
|
|
225
|
+
|
|
226
|
+
### Captured Events
|
|
227
|
+
|
|
228
|
+
| Hook | Description |
|
|
229
|
+
|------|-------------|
|
|
230
|
+
| `PreToolUse` | Before a tool executes (file edit, bash command, etc.) |
|
|
231
|
+
| `PostToolUse` | After a tool completes successfully |
|
|
232
|
+
| `PostToolUseFailure` | After a tool fails |
|
|
233
|
+
| `PermissionRequest` | Claude Code asks for permission -- **blocks until you respond** |
|
|
234
|
+
| `SessionStart` | A new coding session begins |
|
|
235
|
+
| `SessionEnd` | A coding session ends |
|
|
236
|
+
| `Stop` | Claude Code stops execution |
|
|
237
|
+
| `Notification` | General notification from Claude Code |
|
|
238
|
+
| `SubagentStart` | A subagent is spawned |
|
|
239
|
+
| `SubagentStop` | A subagent completes |
|
|
240
|
+
| `UserPromptSubmit` | User submits a prompt |
|
|
241
|
+
| `TeammateIdle` | A teammate agent becomes idle |
|
|
242
|
+
| `TaskCompleted` | A task finishes |
|
|
243
|
+
| `PreCompact` | Before context compaction |
|
|
244
|
+
|
|
245
|
+
### Permission Flow
|
|
246
|
+
|
|
247
|
+
1. Claude Code triggers `PermissionRequest` hook
|
|
248
|
+
2. Notifier sends a push notification with the tool name and details to your phone
|
|
249
|
+
3. You tap **Allow** or **Deny** on the interactive notification (iOS) or in the app
|
|
250
|
+
4. Notifier polls `GET /api/response?requestId=...` until your decision arrives
|
|
251
|
+
5. The hook returns the decision to Claude Code, which proceeds or aborts
|
|
252
|
+
|
|
253
|
+
The `PermissionRequest` hook has a 180-second timeout in `.claude/settings.json`. The notifier's internal poll timeout is 120 seconds, providing a 60-second safety buffer.
|
|
254
|
+
|
|
255
|
+
### Hook Environment Variables
|
|
256
|
+
|
|
257
|
+
| Variable | Default | Description |
|
|
258
|
+
|----------|---------|-------------|
|
|
259
|
+
| `SHOOTER_USE_LOCAL` | -- | Set `true` to connect to local server instead of remote URL |
|
|
260
|
+
| `SHOOTER_LOCAL_PORT` | `3000` | Local server port when using `SHOOTER_USE_LOCAL` |
|
|
261
|
+
| `SHOOTER_API_URL` | -- | Remote server URL (when not using local) |
|
|
262
|
+
| `SHOOTER_PERMISSION_TIMEOUT` | `120` | Seconds to wait for a permission response |
|
|
263
|
+
| `API_KEY` | -- | Bearer token (must match the server's `API_KEY`) |
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Docker
|
|
268
|
+
|
|
269
|
+
### Quick Start
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
cp .env.example .env
|
|
273
|
+
# Edit .env with your values
|
|
274
|
+
docker compose up -d
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Manual Build and Run
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
docker build -t shooter .
|
|
281
|
+
|
|
282
|
+
docker run -d \
|
|
283
|
+
--name shooter \
|
|
284
|
+
--env-file .env \
|
|
285
|
+
-p 3000:3000 \
|
|
286
|
+
-v shooter-data:/root/.shooter \
|
|
287
|
+
--restart unless-stopped \
|
|
288
|
+
shooter
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The multi-stage Dockerfile uses `node:20-slim` and includes build tools for `node-pty` and `better-sqlite3` native addons. SQLite data is persisted in the `shooter-data` volume. The `.env` file is injected at runtime and never baked into the image.
|
|
292
|
+
|
|
293
|
+
### docker-compose.yml
|
|
294
|
+
|
|
295
|
+
```yaml
|
|
296
|
+
services:
|
|
297
|
+
shooter:
|
|
298
|
+
build: .
|
|
299
|
+
ports:
|
|
300
|
+
- "3000:3000"
|
|
301
|
+
env_file:
|
|
302
|
+
- .env
|
|
303
|
+
volumes:
|
|
304
|
+
- shooter-data:/root/.shooter
|
|
305
|
+
restart: unless-stopped
|
|
306
|
+
|
|
307
|
+
volumes:
|
|
308
|
+
shooter-data:
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## API Reference
|
|
314
|
+
|
|
315
|
+
All endpoints require the `Authorization: Bearer <API_KEY>` header.
|
|
316
|
+
|
|
317
|
+
| Method | Path | Description |
|
|
318
|
+
|--------|------|-------------|
|
|
319
|
+
| `GET` | `/api/health` | Health check with server status |
|
|
320
|
+
| `GET` | `/api/terminals` | List all active and recently exited terminals |
|
|
321
|
+
| `POST` | `/api/terminals` | Create a new terminal session |
|
|
322
|
+
| `GET` | `/api/terminals/:id` | Get details for a specific terminal |
|
|
323
|
+
| `DELETE` | `/api/terminals/:id` | Kill and remove a terminal session |
|
|
324
|
+
| `POST` | `/api/terminals/:id/resize` | Resize a terminal (cols, rows) |
|
|
325
|
+
| `POST` | `/api/ws-ticket` | Generate a short-lived WebSocket auth ticket |
|
|
326
|
+
| `GET` | `/api/ws-status` | Get connected WebSocket client count |
|
|
327
|
+
| `POST` | `/api/notify` | Send a push notification via APNs or FCM |
|
|
328
|
+
| `GET` | `/api/notify` | Check notification status and history |
|
|
329
|
+
| `POST` | `/api/response` | Submit a permission allow/deny decision |
|
|
330
|
+
| `GET` | `/api/response` | Poll for a pending permission decision |
|
|
331
|
+
| `GET` | `/api/sessions` | List sessions across all projects |
|
|
332
|
+
| `POST` | `/api/webhook` | Receive external webhook events |
|
|
333
|
+
| `GET` | `/api/qr-config` | Generate QR code for mobile app pairing |
|
|
334
|
+
| `POST` | `/api/device-token` | Register a device token (iOS or Android) |
|
|
335
|
+
| `GET` | `/api/debug` | Debug information (APNs config, device token status) |
|
|
336
|
+
|
|
337
|
+
### WebSocket Authentication
|
|
338
|
+
|
|
339
|
+
WebSocket connections use ticket-based auth. First call `POST /api/ws-ticket` with your Bearer token to receive a single-use ticket (valid 30 seconds), then connect with `?ticket=TICKET` in the query string.
|
|
340
|
+
|
|
341
|
+
### Example: Create Terminal
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
curl -X POST http://localhost:3000/api/terminals \
|
|
345
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
346
|
+
-H "Content-Type: application/json" \
|
|
347
|
+
-d '{"command": "claude", "cwd": "/Users/me/project", "cols": 80, "rows": 24}'
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Response:
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"id": "term_a1b2c3",
|
|
355
|
+
"pid": 45231,
|
|
356
|
+
"command": "claude",
|
|
357
|
+
"cwd": "/Users/me/project",
|
|
358
|
+
"ws": "/ws/terminal/term_a1b2c3",
|
|
359
|
+
"sessionWs": "/ws/session/term_a1b2c3",
|
|
360
|
+
"createdAt": "2026-03-17T10:00:00Z"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Development
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
pnpm dev # Vite dev server with hot reload (no WebSocket server)
|
|
370
|
+
pnpm build # Production build (outputs to build/)
|
|
371
|
+
pnpm start # Production server with WebSocket support (tsx server.ts)
|
|
372
|
+
pnpm preview # Preview production build via Vite
|
|
373
|
+
pnpm check # TypeScript type checking
|
|
374
|
+
pnpm run gen:types # Generate types from YAML specs (specs/types/)
|
|
375
|
+
pnpm lint # ESLint
|
|
376
|
+
pnpm lint:fix # ESLint with auto-fix
|
|
377
|
+
pnpm format # Prettier formatting
|
|
378
|
+
pnpm format:check # Check formatting without writing
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Note:** `pnpm dev` runs the Vite dev server, which does not include the WebSocket server or PTY manager. For full functionality (terminal sessions, live streaming), use `pnpm build && pnpm start`.
|
|
382
|
+
|
|
383
|
+
### Type System
|
|
384
|
+
|
|
385
|
+
Types are auto-generated from YAML specifications in `specs/types/` using [type-crafter](https://github.com/nicktaf/type-crafter). Never edit files in `src/generated/types/` directly -- edit the YAML specs and run `pnpm run gen:types`.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Project Structure
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
shooter/
|
|
393
|
+
server.ts # HTTP + WebSocket server entry point
|
|
394
|
+
package.json # Dependencies and scripts (pnpm only)
|
|
395
|
+
Dockerfile # Multi-stage Docker build
|
|
396
|
+
docker-compose.yml # Docker Compose config
|
|
397
|
+
.env.example # Environment variable template
|
|
398
|
+
svelte.config.js # SvelteKit config (adapter-node)
|
|
399
|
+
vite.config.ts # Vite config (node-pty external)
|
|
400
|
+
bin/
|
|
401
|
+
shooter.cjs # CLI entry point (shooter start|setup|help)
|
|
402
|
+
scripts/
|
|
403
|
+
setup.cjs # Interactive setup wizard
|
|
404
|
+
install.sh # One-command curl installer
|
|
405
|
+
.claude/
|
|
406
|
+
hooks/notifier.cjs # Unified hook notifier (Node.js)
|
|
407
|
+
settings.json # Hook configuration (13 event types)
|
|
408
|
+
src/
|
|
409
|
+
generated/types/ # Auto-generated TypeScript types (DO NOT EDIT)
|
|
410
|
+
lib/
|
|
411
|
+
modules/
|
|
412
|
+
server/
|
|
413
|
+
apn/ # APNs push notification service
|
|
414
|
+
auth.ts # Shared authentication helper
|
|
415
|
+
cli/ # CLI command utilities
|
|
416
|
+
terminal/
|
|
417
|
+
pty-manager.ts # PTY lifecycle, scrollback, cleanup
|
|
418
|
+
pty-holder.cjs # Standalone holder process for persistence
|
|
419
|
+
terminal-store.ts # SQLite persistence for terminal metadata
|
|
420
|
+
session-watcher.ts # JSONL file watcher (chokidar)
|
|
421
|
+
opencode-watcher.ts # OpenCode session watcher
|
|
422
|
+
ws/
|
|
423
|
+
server.ts # WebSocket upgrade routing
|
|
424
|
+
terminal-handler.ts # Terminal I/O channel
|
|
425
|
+
session-handler.ts # Session stream channel
|
|
426
|
+
events-handler.ts # Global event bus channel
|
|
427
|
+
ticket-store.ts # One-time auth ticket store
|
|
428
|
+
keepalive.ts # Ping/pong heartbeat
|
|
429
|
+
sessions/
|
|
430
|
+
jsonl-reader.ts # Parse JSONL session files
|
|
431
|
+
opencode-reader.ts # Parse OpenCode sessions
|
|
432
|
+
client/
|
|
433
|
+
common/ # Reusable UI components
|
|
434
|
+
terminal/
|
|
435
|
+
ChatView.svelte # Structured AI conversation view
|
|
436
|
+
LaunchSheet.svelte # Terminal launch dialog
|
|
437
|
+
QuickKeys.svelte # Mobile quick key bar
|
|
438
|
+
ConnectionStatus.svelte # Connection state indicator
|
|
439
|
+
xterm-wrapper.ts # Async xterm.js initialization
|
|
440
|
+
routes/
|
|
441
|
+
api/ # REST API endpoints (17 endpoints)
|
|
442
|
+
terminals/ # Terminal list and detail pages
|
|
443
|
+
project/ # Project dashboard
|
|
444
|
+
session/[id]/ # Session viewer
|
|
445
|
+
config/ # Settings page with QR pairing
|
|
446
|
+
specs/types/ # Type-crafter YAML specifications
|
|
447
|
+
ios/Shooter/ # Swift iOS app (Xcode project)
|
|
448
|
+
android/ # Kotlin Android app (Gradle project)
|
|
449
|
+
docs/ # Documentation
|
|
450
|
+
plans/ # Architecture plans and roadmap
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Troubleshooting
|
|
456
|
+
|
|
457
|
+
### Server does not start
|
|
458
|
+
|
|
459
|
+
- Verify Node.js 20+ is installed: `node --version`
|
|
460
|
+
- Ensure pnpm is used (npm and yarn are blocked): `pnpm --version`
|
|
461
|
+
- Check that `pnpm build` completed without errors before running `pnpm start`
|
|
462
|
+
- Confirm `.env` exists and `API_KEY` is set
|
|
463
|
+
|
|
464
|
+
### WebSocket connections fail
|
|
465
|
+
|
|
466
|
+
- `pnpm dev` does **not** run the WebSocket server. Use `pnpm build && pnpm start` for full functionality.
|
|
467
|
+
- Ensure you are obtaining a ticket via `POST /api/ws-ticket` before connecting
|
|
468
|
+
- Tickets expire after 30 seconds and are single-use
|
|
469
|
+
|
|
470
|
+
### Push notifications not arriving
|
|
471
|
+
|
|
472
|
+
- **iOS:** Verify `APNS_KEY`, `APNS_KEY_ID`, `APNS_TEAM_ID`, `APNS_BUNDLE_ID`, and `DEVICE_TOKEN` are all set in `.env`
|
|
473
|
+
- **iOS (TestFlight/App Store):** Set `APNS_PRODUCTION=true` -- sandbox tokens do not work with the production gateway and vice versa
|
|
474
|
+
- **Android:** Ensure `google-services.json` is in `android/app/` and FCM credentials are in `.env`
|
|
475
|
+
- Check `GET /api/debug` for APNs configuration status and device token validity
|
|
476
|
+
- Check server logs for APNs or FCM error responses
|
|
477
|
+
|
|
478
|
+
### Hooks not sending notifications
|
|
479
|
+
|
|
480
|
+
- `API_KEY` must be exported in your shell environment, not just in `.env`: `export API_KEY="..."`
|
|
481
|
+
- Verify the hooks are configured in `.claude/settings.json`
|
|
482
|
+
- Test connectivity: `curl -H "Authorization: Bearer $API_KEY" http://localhost:3000/api/health`
|
|
483
|
+
|
|
484
|
+
### Terminal sessions lost after restart
|
|
485
|
+
|
|
486
|
+
- Terminal metadata is persisted in SQLite and PTY holder processes survive restarts, so running terminals are reattached automatically
|
|
487
|
+
- In-memory state (WebSocket connections, auth tickets, pending permission requests) is lost on restart
|
|
488
|
+
|
|
489
|
+
### node-pty build errors
|
|
490
|
+
|
|
491
|
+
- Ensure Python 3, make, and a C++ compiler are installed
|
|
492
|
+
- On macOS, install Xcode Command Line Tools: `xcode-select --install`
|
|
493
|
+
- Try rebuilding: `pnpm rebuild node-pty`
|
|
494
|
+
|
|
495
|
+
### Port already in use
|
|
496
|
+
|
|
497
|
+
- Default port is 3000. Set `PORT=<number>` in `.env` to use a different port.
|
|
498
|
+
- Check what is using the port: `lsof -i :3000`
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Security
|
|
503
|
+
|
|
504
|
+
- **Command allowlist** -- Only `zsh`, `bash`, `sh`, `fish`, `claude`, and `opencode` can be launched as terminal commands
|
|
505
|
+
- **Ticket-based WebSocket auth** -- Short-lived, single-use tickets (30-second expiry) keep API keys out of WebSocket URLs
|
|
506
|
+
- **Bearer token on all REST endpoints** -- Every request requires `Authorization: Bearer <API_KEY>`
|
|
507
|
+
- **Working directory validation** -- The `cwd` parameter is validated against the user's home directory; symlink traversal is blocked
|
|
508
|
+
- **No credentials in code** -- All secrets loaded from `.env` at runtime; `.env` is gitignored
|
|
509
|
+
- **APNs JWT rotation** -- Push notification tokens are generated with short expiry and rotated automatically
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## License
|
|
514
|
+
|
|
515
|
+
MIT
|