@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.
Files changed (327) hide show
  1. package/.claude/hooks/notifier.cjs +1431 -0
  2. package/.claude/settings.json +162 -0
  3. package/README.md +515 -0
  4. package/bin/shooter.cjs +141 -0
  5. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css +1 -0
  6. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.br +0 -0
  7. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css +1 -0
  9. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.br +1 -0
  10. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.gz +0 -0
  11. package/build/client/_app/immutable/assets/3.C0uFg0IS.css +1 -0
  12. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.br +0 -0
  13. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.gz +0 -0
  14. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css +1 -0
  15. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.br +0 -0
  16. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.gz +0 -0
  17. package/build/client/_app/immutable/assets/5.DRjApZQW.css +1 -0
  18. package/build/client/_app/immutable/assets/5.DRjApZQW.css.br +0 -0
  19. package/build/client/_app/immutable/assets/5.DRjApZQW.css.gz +0 -0
  20. package/build/client/_app/immutable/assets/6.AraZrY8I.css +1 -0
  21. package/build/client/_app/immutable/assets/6.AraZrY8I.css.br +0 -0
  22. package/build/client/_app/immutable/assets/6.AraZrY8I.css.gz +0 -0
  23. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css +1 -0
  24. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.br +0 -0
  25. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.gz +0 -0
  26. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css +1 -0
  27. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.br +0 -0
  28. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.gz +0 -0
  29. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css +1 -0
  30. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.br +0 -0
  31. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.gz +0 -0
  32. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css +1 -0
  33. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.br +0 -0
  34. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.gz +0 -0
  35. package/build/client/_app/immutable/chunks/BNJphC1q.js +56 -0
  36. package/build/client/_app/immutable/chunks/BNJphC1q.js.br +0 -0
  37. package/build/client/_app/immutable/chunks/BNJphC1q.js.gz +0 -0
  38. package/build/client/_app/immutable/chunks/BTGVxaYV.js +9 -0
  39. package/build/client/_app/immutable/chunks/BTGVxaYV.js.br +0 -0
  40. package/build/client/_app/immutable/chunks/BTGVxaYV.js.gz +0 -0
  41. package/build/client/_app/immutable/chunks/BlxrFPDK.js +1 -0
  42. package/build/client/_app/immutable/chunks/BlxrFPDK.js.br +0 -0
  43. package/build/client/_app/immutable/chunks/BlxrFPDK.js.gz +0 -0
  44. package/build/client/_app/immutable/chunks/Bvk7mfPM.js +1 -0
  45. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.br +0 -0
  46. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.gz +0 -0
  47. package/build/client/_app/immutable/chunks/CAokzuPQ.js +1 -0
  48. package/build/client/_app/immutable/chunks/CAokzuPQ.js.br +0 -0
  49. package/build/client/_app/immutable/chunks/CAokzuPQ.js.gz +0 -0
  50. package/build/client/_app/immutable/chunks/CGLrx-H5.js +1 -0
  51. package/build/client/_app/immutable/chunks/CGLrx-H5.js.br +0 -0
  52. package/build/client/_app/immutable/chunks/CGLrx-H5.js.gz +0 -0
  53. package/build/client/_app/immutable/chunks/CgCpWzEA.js +1 -0
  54. package/build/client/_app/immutable/chunks/CgCpWzEA.js.br +0 -0
  55. package/build/client/_app/immutable/chunks/CgCpWzEA.js.gz +0 -0
  56. package/build/client/_app/immutable/chunks/Cjwk_cGO.js +6 -0
  57. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.br +0 -0
  58. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.gz +0 -0
  59. package/build/client/_app/immutable/chunks/CtQ8EED1.js +11 -0
  60. package/build/client/_app/immutable/chunks/CtQ8EED1.js.br +0 -0
  61. package/build/client/_app/immutable/chunks/CtQ8EED1.js.gz +0 -0
  62. package/build/client/_app/immutable/chunks/DERQCisl.js +1 -0
  63. package/build/client/_app/immutable/chunks/DERQCisl.js.br +0 -0
  64. package/build/client/_app/immutable/chunks/DERQCisl.js.gz +0 -0
  65. package/build/client/_app/immutable/chunks/DKrg8TQs.js +1 -0
  66. package/build/client/_app/immutable/chunks/DKrg8TQs.js.br +0 -0
  67. package/build/client/_app/immutable/chunks/DKrg8TQs.js.gz +0 -0
  68. package/build/client/_app/immutable/chunks/DLu6yJIZ.js +1 -0
  69. package/build/client/_app/immutable/chunks/DLu6yJIZ.js.br +0 -0
  70. package/build/client/_app/immutable/chunks/DLu6yJIZ.js.gz +0 -0
  71. package/build/client/_app/immutable/chunks/Dkkpz_4D.js +126 -0
  72. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.br +0 -0
  73. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.gz +0 -0
  74. package/build/client/_app/immutable/chunks/DoczjQhA.js +1 -0
  75. package/build/client/_app/immutable/chunks/DoczjQhA.js.br +0 -0
  76. package/build/client/_app/immutable/chunks/DoczjQhA.js.gz +0 -0
  77. package/build/client/_app/immutable/chunks/PPVm8Dsz.js +1 -0
  78. package/build/client/_app/immutable/chunks/PPVm8Dsz.js.br +0 -0
  79. package/build/client/_app/immutable/chunks/PPVm8Dsz.js.gz +0 -0
  80. package/build/client/_app/immutable/chunks/RpcNruLP.js +2 -0
  81. package/build/client/_app/immutable/chunks/RpcNruLP.js.br +0 -0
  82. package/build/client/_app/immutable/chunks/RpcNruLP.js.gz +0 -0
  83. package/build/client/_app/immutable/chunks/a-St0Zwo.js +1 -0
  84. package/build/client/_app/immutable/chunks/a-St0Zwo.js.br +0 -0
  85. package/build/client/_app/immutable/chunks/a-St0Zwo.js.gz +0 -0
  86. package/build/client/_app/immutable/chunks/bo70OQUZ.js +1 -0
  87. package/build/client/_app/immutable/chunks/bo70OQUZ.js.br +0 -0
  88. package/build/client/_app/immutable/chunks/bo70OQUZ.js.gz +0 -0
  89. package/build/client/_app/immutable/entry/app.QvGgdvTI.js +2 -0
  90. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.br +0 -0
  91. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.gz +0 -0
  92. package/build/client/_app/immutable/entry/start.BntDNRMC.js +1 -0
  93. package/build/client/_app/immutable/entry/start.BntDNRMC.js.br +0 -0
  94. package/build/client/_app/immutable/entry/start.BntDNRMC.js.gz +0 -0
  95. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js +1 -0
  96. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.br +0 -0
  97. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.gz +0 -0
  98. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js +1 -0
  99. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.br +0 -0
  100. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.gz +0 -0
  101. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js +1 -0
  102. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.br +0 -0
  103. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.gz +0 -0
  104. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js +3 -0
  105. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.br +0 -0
  106. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.gz +0 -0
  107. package/build/client/_app/immutable/nodes/4.D-cIe70D.js +1 -0
  108. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.br +0 -0
  109. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.gz +0 -0
  110. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js +1 -0
  111. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.br +0 -0
  112. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.gz +0 -0
  113. package/build/client/_app/immutable/nodes/6.BB7QE48r.js +2 -0
  114. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.br +0 -0
  115. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.gz +0 -0
  116. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js +2 -0
  117. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.br +0 -0
  118. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.gz +0 -0
  119. package/build/client/_app/version.json +1 -0
  120. package/build/client/_app/version.json.br +0 -0
  121. package/build/client/_app/version.json.gz +0 -0
  122. package/build/client/app-icon.png +0 -0
  123. package/build/client/apple-touch-icon.png +0 -0
  124. package/build/client/favicon.png +0 -0
  125. package/build/client/favicon.svg +10 -0
  126. package/build/client/favicon.svg.br +0 -0
  127. package/build/client/favicon.svg.gz +0 -0
  128. package/build/client/manifest.webmanifest +1 -0
  129. package/build/client/pwa-192x192.png +0 -0
  130. package/build/client/pwa-512x512.png +0 -0
  131. package/build/client/registerSW.js +1 -0
  132. package/build/client/registerSW.js.br +0 -0
  133. package/build/client/registerSW.js.gz +0 -0
  134. package/build/client/sw.js +222 -0
  135. package/build/client/sw.js.br +0 -0
  136. package/build/client/sw.js.gz +0 -0
  137. package/build/client/workbox-5119daf5.js +3395 -0
  138. package/build/client/workbox-5119daf5.js.br +0 -0
  139. package/build/client/workbox-5119daf5.js.gz +0 -0
  140. package/build/env.js +94 -0
  141. package/build/handler.js +1494 -0
  142. package/build/index.js +345 -0
  143. package/build/pty-holder.cjs +510 -0
  144. package/build/server/chunks/0-q2IUp76Y.js +9 -0
  145. package/build/server/chunks/0-q2IUp76Y.js.map +1 -0
  146. package/build/server/chunks/1-CU50G5wZ.js +9 -0
  147. package/build/server/chunks/1-CU50G5wZ.js.map +1 -0
  148. package/build/server/chunks/2-D01t9s8T.js +9 -0
  149. package/build/server/chunks/2-D01t9s8T.js.map +1 -0
  150. package/build/server/chunks/3-5PUQ04wC.js +9 -0
  151. package/build/server/chunks/3-5PUQ04wC.js.map +1 -0
  152. package/build/server/chunks/4-e7gywnSG.js +9 -0
  153. package/build/server/chunks/4-e7gywnSG.js.map +1 -0
  154. package/build/server/chunks/5-CA1SA6KZ.js +9 -0
  155. package/build/server/chunks/5-CA1SA6KZ.js.map +1 -0
  156. package/build/server/chunks/6-71H221sV.js +9 -0
  157. package/build/server/chunks/6-71H221sV.js.map +1 -0
  158. package/build/server/chunks/7-Bo-vmdyz.js +9 -0
  159. package/build/server/chunks/7-Bo-vmdyz.js.map +1 -0
  160. package/build/server/chunks/_layout.svelte-SFHOxs74.js +132 -0
  161. package/build/server/chunks/_layout.svelte-SFHOxs74.js.map +1 -0
  162. package/build/server/chunks/_page.svelte-B4w-2wD-.js +120 -0
  163. package/build/server/chunks/_page.svelte-B4w-2wD-.js.map +1 -0
  164. package/build/server/chunks/_page.svelte-B_qAXjkh.js +213 -0
  165. package/build/server/chunks/_page.svelte-B_qAXjkh.js.map +1 -0
  166. package/build/server/chunks/_page.svelte-CsF1_TRG.js +50 -0
  167. package/build/server/chunks/_page.svelte-CsF1_TRG.js.map +1 -0
  168. package/build/server/chunks/_page.svelte-DJC6U-P0.js +68 -0
  169. package/build/server/chunks/_page.svelte-DJC6U-P0.js.map +1 -0
  170. package/build/server/chunks/_page.svelte-DQ6HBtsz.js +407 -0
  171. package/build/server/chunks/_page.svelte-DQ6HBtsz.js.map +1 -0
  172. package/build/server/chunks/_page.svelte-LbhhjP21.js +148 -0
  173. package/build/server/chunks/_page.svelte-LbhhjP21.js.map +1 -0
  174. package/build/server/chunks/_server.ts-BL2FGb5Z.js +387 -0
  175. package/build/server/chunks/_server.ts-BL2FGb5Z.js.map +1 -0
  176. package/build/server/chunks/_server.ts-BgdjBZco.js +47 -0
  177. package/build/server/chunks/_server.ts-BgdjBZco.js.map +1 -0
  178. package/build/server/chunks/_server.ts-BihKSdj_.js +59 -0
  179. package/build/server/chunks/_server.ts-BihKSdj_.js.map +1 -0
  180. package/build/server/chunks/_server.ts-BjOJsoy4.js +63 -0
  181. package/build/server/chunks/_server.ts-BjOJsoy4.js.map +1 -0
  182. package/build/server/chunks/_server.ts-C29xzfaw.js +77 -0
  183. package/build/server/chunks/_server.ts-C29xzfaw.js.map +1 -0
  184. package/build/server/chunks/_server.ts-CPa6DgIt.js +71 -0
  185. package/build/server/chunks/_server.ts-CPa6DgIt.js.map +1 -0
  186. package/build/server/chunks/_server.ts-CbDRDIoP.js +36 -0
  187. package/build/server/chunks/_server.ts-CbDRDIoP.js.map +1 -0
  188. package/build/server/chunks/_server.ts-Cl1OEWL4.js +54 -0
  189. package/build/server/chunks/_server.ts-Cl1OEWL4.js.map +1 -0
  190. package/build/server/chunks/_server.ts-ColfDHW8.js +60 -0
  191. package/build/server/chunks/_server.ts-ColfDHW8.js.map +1 -0
  192. package/build/server/chunks/_server.ts-Cv_OrRuL.js +494 -0
  193. package/build/server/chunks/_server.ts-Cv_OrRuL.js.map +1 -0
  194. package/build/server/chunks/_server.ts-D4MNi4cD.js +25 -0
  195. package/build/server/chunks/_server.ts-D4MNi4cD.js.map +1 -0
  196. package/build/server/chunks/_server.ts-DRVbgm6k.js +125 -0
  197. package/build/server/chunks/_server.ts-DRVbgm6k.js.map +1 -0
  198. package/build/server/chunks/_server.ts-DfajWaqh.js +39 -0
  199. package/build/server/chunks/_server.ts-DfajWaqh.js.map +1 -0
  200. package/build/server/chunks/_server.ts-y9-WYDMa.js +35 -0
  201. package/build/server/chunks/_server.ts-y9-WYDMa.js.map +1 -0
  202. package/build/server/chunks/auth-CEgFis71.js +32 -0
  203. package/build/server/chunks/auth-CEgFis71.js.map +1 -0
  204. package/build/server/chunks/client-CxCatAKr.js +255 -0
  205. package/build/server/chunks/client-CxCatAKr.js.map +1 -0
  206. package/build/server/chunks/error.svelte-BqdwMWdK.js +26 -0
  207. package/build/server/chunks/error.svelte-BqdwMWdK.js.map +1 -0
  208. package/build/server/chunks/exports-CJ0Q5XmL.js +4081 -0
  209. package/build/server/chunks/exports-CJ0Q5XmL.js.map +1 -0
  210. package/build/server/chunks/index2-DAxIoAO-.js +36 -0
  211. package/build/server/chunks/index2-DAxIoAO-.js.map +1 -0
  212. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js +137 -0
  213. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js.map +1 -0
  214. package/build/server/chunks/library-apns-BHxLmuIx.js +104 -0
  215. package/build/server/chunks/library-apns-BHxLmuIx.js.map +1 -0
  216. package/build/server/chunks/markdown-Bxrl3cCF.js +1241 -0
  217. package/build/server/chunks/markdown-Bxrl3cCF.js.map +1 -0
  218. package/build/server/chunks/pending-requests-D8UiTw7L.js +44 -0
  219. package/build/server/chunks/pending-requests-D8UiTw7L.js.map +1 -0
  220. package/build/server/chunks/pty-manager-C0FhBiVq.js +1697 -0
  221. package/build/server/chunks/pty-manager-C0FhBiVq.js.map +1 -0
  222. package/build/server/chunks/shared-server-BDY8jh20.js +200 -0
  223. package/build/server/chunks/shared-server-BDY8jh20.js.map +1 -0
  224. package/build/server/chunks/stores-D0HorpgL.js +36 -0
  225. package/build/server/chunks/stores-D0HorpgL.js.map +1 -0
  226. package/build/server/index.js +6466 -0
  227. package/build/server/index.js.map +1 -0
  228. package/build/server/manifest.js +184 -0
  229. package/build/server/manifest.js.map +1 -0
  230. package/build/shims.js +32 -0
  231. package/package.json +94 -0
  232. package/scripts/clipboard-shims/wl-paste +48 -0
  233. package/scripts/clipboard-shims/xclip +31 -0
  234. package/scripts/install.sh +477 -0
  235. package/scripts/setup-node-pty.sh +63 -0
  236. package/scripts/setup.cjs +571 -0
  237. package/scripts/test-runner.ts +243 -0
  238. package/scripts/vercel-env-commands.sh +60 -0
  239. package/server.ts +139 -0
  240. package/src/app.css +1835 -0
  241. package/src/app.d.ts +31 -0
  242. package/src/app.html +24 -0
  243. package/src/generated/types/APN.ts +305 -0
  244. package/src/generated/types/CLI.ts +52 -0
  245. package/src/generated/types/JWT.ts +92 -0
  246. package/src/generated/types/Terminal.ts +2736 -0
  247. package/src/generated/types/index.ts +6 -0
  248. package/src/lib/assets/icons/alert-triangle.svg +5 -0
  249. package/src/lib/assets/icons/bell.svg +4 -0
  250. package/src/lib/assets/icons/check-circle.svg +4 -0
  251. package/src/lib/assets/icons/file.svg +4 -0
  252. package/src/lib/assets/icons/folder.svg +3 -0
  253. package/src/lib/assets/icons/play.svg +3 -0
  254. package/src/lib/assets/icons/refresh.svg +4 -0
  255. package/src/lib/assets/icons/settings.svg +4 -0
  256. package/src/lib/assets/icons/terminal.svg +1 -0
  257. package/src/lib/assets/icons/tool.svg +3 -0
  258. package/src/lib/assets/icons/x-circle.svg +5 -0
  259. package/src/lib/modules/client/common/Card.svelte +26 -0
  260. package/src/lib/modules/client/common/EmptyState.svelte +36 -0
  261. package/src/lib/modules/client/common/Icon.svelte +61 -0
  262. package/src/lib/modules/client/common/StatusBadge.svelte +38 -0
  263. package/src/lib/modules/client/common/cache.ts +31 -0
  264. package/src/lib/modules/client/common/config-guard.ts +18 -0
  265. package/src/lib/modules/client/common/index.ts +12 -0
  266. package/src/lib/modules/client/common/markdown.ts +23 -0
  267. package/src/lib/modules/client/common/native-bridge.ts +50 -0
  268. package/src/lib/modules/client/common/time.ts +22 -0
  269. package/src/lib/modules/client/common/tool-title.ts +28 -0
  270. package/src/lib/modules/client/terminal/ChatView.svelte +400 -0
  271. package/src/lib/modules/client/terminal/CommandPalette.svelte +60 -0
  272. package/src/lib/modules/client/terminal/ConnectionStatus.svelte +99 -0
  273. package/src/lib/modules/client/terminal/LaunchSheet.svelte +294 -0
  274. package/src/lib/modules/client/terminal/QuickKeys.svelte +71 -0
  275. package/src/lib/modules/client/terminal/ShortcutsHelp.svelte +79 -0
  276. package/src/lib/modules/client/terminal/keyboard-shortcuts.ts +70 -0
  277. package/src/lib/modules/client/terminal/xterm-wrapper.ts +243 -0
  278. package/src/lib/modules/server/apn/library-apns.ts +137 -0
  279. package/src/lib/modules/server/apn/notification-history.ts +35 -0
  280. package/src/lib/modules/server/apn/notification-sessions.ts +117 -0
  281. package/src/lib/modules/server/apn/pending-requests.ts +65 -0
  282. package/src/lib/modules/server/apn/types.ts +51 -0
  283. package/src/lib/modules/server/auth.ts +34 -0
  284. package/src/lib/modules/server/cli/index.ts +79 -0
  285. package/src/lib/modules/server/cli/runner.ts +162 -0
  286. package/src/lib/modules/server/fcm/fcm-service.ts +72 -0
  287. package/src/lib/modules/server/sessions/jsonl-parser.ts +197 -0
  288. package/src/lib/modules/server/sessions/jsonl-reader.ts +301 -0
  289. package/src/lib/modules/server/sessions/opencode-reader.ts +264 -0
  290. package/src/lib/modules/server/sessions/types.ts +53 -0
  291. package/src/lib/modules/server/terminal/holder-client.ts +273 -0
  292. package/src/lib/modules/server/terminal/opencode-watcher.ts +661 -0
  293. package/src/lib/modules/server/terminal/pty-holder.cjs +510 -0
  294. package/src/lib/modules/server/terminal/pty-manager.ts +1012 -0
  295. package/src/lib/modules/server/terminal/session-watcher.ts +320 -0
  296. package/src/lib/modules/server/terminal/terminal-store.ts +198 -0
  297. package/src/lib/modules/server/ws/events-handler.ts +73 -0
  298. package/src/lib/modules/server/ws/keepalive.ts +108 -0
  299. package/src/lib/modules/server/ws/server.ts +93 -0
  300. package/src/lib/modules/server/ws/session-handler.ts +462 -0
  301. package/src/lib/modules/server/ws/terminal-handler.ts +197 -0
  302. package/src/lib/modules/server/ws/ticket-store.ts +58 -0
  303. package/src/lib/theme.css +529 -0
  304. package/src/lib/types/config.ts +6 -0
  305. package/src/routes/+layout.svelte +218 -0
  306. package/src/routes/+page.svelte +261 -0
  307. package/src/routes/api/debug/+server.ts +33 -0
  308. package/src/routes/api/device-token/+server.ts +85 -0
  309. package/src/routes/api/health/+server.ts +100 -0
  310. package/src/routes/api/notify/+server.ts +418 -0
  311. package/src/routes/api/qr-config/+server.ts +45 -0
  312. package/src/routes/api/response/+server.ts +73 -0
  313. package/src/routes/api/sessions/+server.ts +120 -0
  314. package/src/routes/api/terminals/+server.ts +141 -0
  315. package/src/routes/api/terminals/[id]/+server.ts +75 -0
  316. package/src/routes/api/terminals/[id]/paste-image/+server.ts +61 -0
  317. package/src/routes/api/terminals/[id]/resize/+server.ts +60 -0
  318. package/src/routes/api/webhook/+server.ts +42 -0
  319. package/src/routes/api/ws-status/+server.ts +23 -0
  320. package/src/routes/api/ws-ticket/+server.ts +86 -0
  321. package/src/routes/config/+page.svelte +600 -0
  322. package/src/routes/project/+page.svelte +274 -0
  323. package/src/routes/session/[id]/+page.svelte +434 -0
  324. package/src/routes/terminals/+page.svelte +618 -0
  325. package/src/routes/terminals/[id]/+page.svelte +968 -0
  326. package/svelte.config.js +18 -0
  327. 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
+ [![SvelteKit](https://img.shields.io/badge/SvelteKit-FF3E00?logo=svelte&logoColor=white)](https://kit.svelte.dev/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+ [![Node.js](https://img.shields.io/badge/Node.js_20+-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
8
+ [![WebSocket](https://img.shields.io/badge/WebSocket-010101?logo=socket.io&logoColor=white)](#websocket-channels)
9
+ [![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white)](#docker)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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