@juspay/shooter 1.0.0 → 1.2.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 (488) hide show
  1. package/.claude/hooks/notifier.cjs +117 -33
  2. package/.claude/settings.json +14 -14
  3. package/README.md +116 -84
  4. package/bin/shooter.cjs +471 -102
  5. package/build/client/_app/immutable/assets/{0.CM9Hl6d-.css → 0.BhZOCxO4.css} +1 -1
  6. package/build/client/_app/immutable/assets/0.BhZOCxO4.css.br +0 -0
  7. package/build/client/_app/immutable/assets/0.BhZOCxO4.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/1.BYutk3aU.css +1 -0
  9. package/build/client/_app/immutable/assets/1.BYutk3aU.css.br +0 -0
  10. package/build/client/_app/immutable/assets/1.BYutk3aU.css.gz +0 -0
  11. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.gz +0 -0
  12. package/build/client/_app/immutable/assets/3.DGDHCVnW.css +1 -0
  13. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.br +0 -0
  14. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.gz +0 -0
  15. package/build/client/_app/immutable/assets/4.BFUut--w.css +1 -0
  16. package/build/client/_app/immutable/assets/4.BFUut--w.css.br +0 -0
  17. package/build/client/_app/immutable/assets/4.BFUut--w.css.gz +0 -0
  18. package/build/client/_app/immutable/assets/5.BTOx7yt7.css +1 -0
  19. package/build/client/_app/immutable/assets/5.BTOx7yt7.css.br +0 -0
  20. package/build/client/_app/immutable/assets/5.BTOx7yt7.css.gz +0 -0
  21. package/build/client/_app/immutable/assets/6.eZGZN-BF.css +1 -0
  22. package/build/client/_app/immutable/assets/6.eZGZN-BF.css.br +0 -0
  23. package/build/client/_app/immutable/assets/6.eZGZN-BF.css.gz +0 -0
  24. package/build/client/_app/immutable/assets/7.DwS5ZHBh.css +1 -0
  25. package/build/client/_app/immutable/assets/7.DwS5ZHBh.css.br +0 -0
  26. package/build/client/_app/immutable/assets/7.DwS5ZHBh.css.gz +0 -0
  27. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css +1 -0
  28. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css.br +0 -0
  29. package/build/client/_app/immutable/assets/ChatView.CwWbzIL-.css.gz +0 -0
  30. package/build/client/_app/immutable/assets/Phone.FQEfwCX2.css +1 -0
  31. package/build/client/_app/immutable/assets/Phone.FQEfwCX2.css.br +0 -0
  32. package/build/client/_app/immutable/assets/Phone.FQEfwCX2.css.gz +0 -0
  33. package/build/client/_app/immutable/assets/markdown.Dc-OSJWY.css +1 -0
  34. package/build/client/_app/immutable/assets/markdown.Dc-OSJWY.css.br +0 -0
  35. package/build/client/_app/immutable/assets/markdown.Dc-OSJWY.css.gz +0 -0
  36. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.gz +0 -0
  37. package/build/client/_app/immutable/chunks/B-K5Sh65.js +1 -0
  38. package/build/client/_app/immutable/chunks/B-K5Sh65.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/B-K5Sh65.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/B5NAKyil.js +20 -0
  41. package/build/client/_app/immutable/chunks/B5NAKyil.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/B5NAKyil.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/B8XegpSE.js +1 -0
  44. package/build/client/_app/immutable/chunks/B8XegpSE.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/B8XegpSE.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/B8zoBsv3.js +6 -0
  47. package/build/client/_app/immutable/chunks/B8zoBsv3.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/B8zoBsv3.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/BLszSzTT.js +1 -0
  50. package/build/client/_app/immutable/chunks/BLszSzTT.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/BLszSzTT.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/BOYo8yTr.js +1 -0
  53. package/build/client/_app/immutable/chunks/BOYo8yTr.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/BOYo8yTr.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/BTGVxaYV.js.gz +0 -0
  56. package/build/client/_app/immutable/chunks/BYqGCrTe.js +1 -0
  57. package/build/client/_app/immutable/chunks/BYqGCrTe.js.br +0 -0
  58. package/build/client/_app/immutable/chunks/BYqGCrTe.js.gz +0 -0
  59. package/build/client/_app/immutable/chunks/BlxrFPDK.js.gz +0 -0
  60. package/build/client/_app/immutable/chunks/Bu1aqm5j.js +1 -0
  61. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.br +0 -0
  62. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.gz +0 -0
  63. package/build/client/_app/immutable/chunks/C4mLaWWx.js +1 -0
  64. package/build/client/_app/immutable/chunks/C4mLaWWx.js.br +0 -0
  65. package/build/client/_app/immutable/chunks/C4mLaWWx.js.gz +0 -0
  66. package/build/client/_app/immutable/chunks/CQjSATpv.js +61 -0
  67. package/build/client/_app/immutable/chunks/CQjSATpv.js.br +0 -0
  68. package/build/client/_app/immutable/chunks/CQjSATpv.js.gz +0 -0
  69. package/build/client/_app/immutable/chunks/CSoRdFvv.js +1 -0
  70. package/build/client/_app/immutable/chunks/CSoRdFvv.js.br +0 -0
  71. package/build/client/_app/immutable/chunks/CSoRdFvv.js.gz +0 -0
  72. package/build/client/_app/immutable/chunks/CZHsSL_X.js +1 -0
  73. package/build/client/_app/immutable/chunks/CZHsSL_X.js.br +0 -0
  74. package/build/client/_app/immutable/chunks/CZHsSL_X.js.gz +0 -0
  75. package/build/client/_app/immutable/chunks/DLu6yJIZ.js.gz +0 -0
  76. package/build/client/_app/immutable/chunks/DSU1n5N_.js +1 -0
  77. package/build/client/_app/immutable/chunks/DSU1n5N_.js.br +0 -0
  78. package/build/client/_app/immutable/chunks/DSU1n5N_.js.gz +0 -0
  79. package/build/client/_app/immutable/chunks/DVkn4r72.js +1 -0
  80. package/build/client/_app/immutable/chunks/DVkn4r72.js.br +0 -0
  81. package/build/client/_app/immutable/chunks/DVkn4r72.js.gz +0 -0
  82. package/build/client/_app/immutable/chunks/DjsDGxCa.js +41 -0
  83. package/build/client/_app/immutable/chunks/DjsDGxCa.js.br +0 -0
  84. package/build/client/_app/immutable/chunks/DjsDGxCa.js.gz +0 -0
  85. package/build/client/_app/immutable/chunks/PPVm8Dsz.js.gz +0 -0
  86. package/build/client/_app/immutable/chunks/UJOiqIYE.js +1 -0
  87. package/build/client/_app/immutable/chunks/UJOiqIYE.js.br +0 -0
  88. package/build/client/_app/immutable/chunks/UJOiqIYE.js.gz +0 -0
  89. package/build/client/_app/immutable/chunks/r0JawsZc.js +2 -0
  90. package/build/client/_app/immutable/chunks/r0JawsZc.js.br +0 -0
  91. package/build/client/_app/immutable/chunks/r0JawsZc.js.gz +0 -0
  92. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js +2 -0
  93. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js.br +0 -0
  94. package/build/client/_app/immutable/entry/app.Z3zMnuSx.js.gz +0 -0
  95. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js +1 -0
  96. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js.br +2 -0
  97. package/build/client/_app/immutable/entry/start.Dd-gIP4y.js.gz +0 -0
  98. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js +1 -0
  99. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js.br +0 -0
  100. package/build/client/_app/immutable/nodes/0.D2YR8tTD.js.gz +0 -0
  101. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js +1 -0
  102. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js.br +0 -0
  103. package/build/client/_app/immutable/nodes/1.B3m6rO4C.js.gz +0 -0
  104. package/build/client/_app/immutable/nodes/2.CyRB2euU.js +1 -0
  105. package/build/client/_app/immutable/nodes/2.CyRB2euU.js.br +0 -0
  106. package/build/client/_app/immutable/nodes/2.CyRB2euU.js.gz +0 -0
  107. package/build/client/_app/immutable/nodes/3.3yohCM25.js +3 -0
  108. package/build/client/_app/immutable/nodes/3.3yohCM25.js.br +0 -0
  109. package/build/client/_app/immutable/nodes/3.3yohCM25.js.gz +0 -0
  110. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js +1 -0
  111. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js.br +0 -0
  112. package/build/client/_app/immutable/nodes/4.DEAcwl7l.js.gz +0 -0
  113. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js +4 -0
  114. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js.br +0 -0
  115. package/build/client/_app/immutable/nodes/5.C6bLGWQR.js.gz +0 -0
  116. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js +2 -0
  117. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js.br +0 -0
  118. package/build/client/_app/immutable/nodes/6.ByTzlA2D.js.gz +0 -0
  119. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js +2 -0
  120. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js.br +0 -0
  121. package/build/client/_app/immutable/nodes/7.BPMfwzd2.js.gz +0 -0
  122. package/build/client/_app/version.json +1 -1
  123. package/build/client/_app/version.json.br +0 -0
  124. package/build/client/_app/version.json.gz +0 -0
  125. package/build/client/favicon.svg.gz +0 -0
  126. package/build/client/manifest.json +13 -0
  127. package/build/client/manifest.json.br +0 -0
  128. package/build/client/manifest.json.gz +0 -0
  129. package/build/pty-holder.cjs +37 -8
  130. package/build/server/chunks/0-Vk38tI2J.js +9 -0
  131. package/build/server/chunks/0-Vk38tI2J.js.map +1 -0
  132. package/build/server/chunks/1-BvYQX5MR.js +9 -0
  133. package/build/server/chunks/1-BvYQX5MR.js.map +1 -0
  134. package/build/server/chunks/2-Cl7R4Qk2.js +9 -0
  135. package/build/server/chunks/2-Cl7R4Qk2.js.map +1 -0
  136. package/build/server/chunks/3-Ck7ewhOX.js +9 -0
  137. package/build/server/chunks/3-Ck7ewhOX.js.map +1 -0
  138. package/build/server/chunks/4-CnDeRm2Z.js +9 -0
  139. package/build/server/chunks/4-CnDeRm2Z.js.map +1 -0
  140. package/build/server/chunks/5-IxitzEvN.js +9 -0
  141. package/build/server/chunks/5-IxitzEvN.js.map +1 -0
  142. package/build/server/chunks/6-CyZ3r1iS.js +9 -0
  143. package/build/server/chunks/6-CyZ3r1iS.js.map +1 -0
  144. package/build/server/chunks/7-BmI7du46.js +9 -0
  145. package/build/server/chunks/7-BmI7du46.js.map +1 -0
  146. package/build/server/chunks/Button-Cs1aE6ka.js +80 -0
  147. package/build/server/chunks/Button-Cs1aE6ka.js.map +1 -0
  148. package/build/server/chunks/EmptyState-DDFH1K8g.js +26 -0
  149. package/build/server/chunks/EmptyState-DDFH1K8g.js.map +1 -0
  150. package/build/server/chunks/Icon-CEUrotA6.js +36 -0
  151. package/build/server/chunks/Icon-CEUrotA6.js.map +1 -0
  152. package/build/server/chunks/Shimmer-DB8W1zt6.js +10 -0
  153. package/build/server/chunks/Shimmer-DB8W1zt6.js.map +1 -0
  154. package/build/server/chunks/_error.svelte-uCOJNxvr.js +39 -0
  155. package/build/server/chunks/_error.svelte-uCOJNxvr.js.map +1 -0
  156. package/build/server/chunks/_layout.svelte-CtWmEJwe.js +56 -0
  157. package/build/server/chunks/_layout.svelte-CtWmEJwe.js.map +1 -0
  158. package/build/server/chunks/_page.svelte-BcZaKdX9.js +45 -0
  159. package/build/server/chunks/_page.svelte-BcZaKdX9.js.map +1 -0
  160. package/build/server/chunks/_page.svelte-BdYynOck.js +85 -0
  161. package/build/server/chunks/_page.svelte-BdYynOck.js.map +1 -0
  162. package/build/server/chunks/_page.svelte-BgevQjq1.js +101 -0
  163. package/build/server/chunks/_page.svelte-BgevQjq1.js.map +1 -0
  164. package/build/server/chunks/_page.svelte-CVq6tRb3.js +550 -0
  165. package/build/server/chunks/_page.svelte-CVq6tRb3.js.map +1 -0
  166. package/build/server/chunks/_page.svelte-CxWcQ0Am.js +651 -0
  167. package/build/server/chunks/_page.svelte-CxWcQ0Am.js.map +1 -0
  168. package/build/server/chunks/_page.svelte-DO4oa_LY.js +44 -0
  169. package/build/server/chunks/_page.svelte-DO4oa_LY.js.map +1 -0
  170. package/build/server/chunks/{_server.ts-CbDRDIoP.js → _server.ts-BStnNIcq.js} +9 -11
  171. package/build/server/chunks/_server.ts-BStnNIcq.js.map +1 -0
  172. package/build/server/chunks/{_server.ts-DRVbgm6k.js → _server.ts-CAxsWKvS.js} +22 -25
  173. package/build/server/chunks/_server.ts-CAxsWKvS.js.map +1 -0
  174. package/build/server/chunks/{_server.ts-CPa6DgIt.js → _server.ts-COu0vNpd.js} +6 -6
  175. package/build/server/chunks/_server.ts-COu0vNpd.js.map +1 -0
  176. package/build/server/chunks/{_server.ts-C29xzfaw.js → _server.ts-CTpcLUH8.js} +10 -10
  177. package/build/server/chunks/_server.ts-CTpcLUH8.js.map +1 -0
  178. package/build/server/chunks/{_server.ts-D4MNi4cD.js → _server.ts-Cf84YIaW.js} +3 -3
  179. package/build/server/chunks/{_server.ts-D4MNi4cD.js.map → _server.ts-Cf84YIaW.js.map} +1 -1
  180. package/build/server/chunks/{_server.ts-BL2FGb5Z.js → _server.ts-Ch-6iOHp.js} +99 -53
  181. package/build/server/chunks/_server.ts-Ch-6iOHp.js.map +1 -0
  182. package/build/server/chunks/_server.ts-CtH0dhUp.js +71 -0
  183. package/build/server/chunks/_server.ts-CtH0dhUp.js.map +1 -0
  184. package/build/server/chunks/_server.ts-DB_Kg97c.js +73 -0
  185. package/build/server/chunks/_server.ts-DB_Kg97c.js.map +1 -0
  186. package/build/server/chunks/{_server.ts-DfajWaqh.js → _server.ts-DV8zTCF9.js} +7 -9
  187. package/build/server/chunks/_server.ts-DV8zTCF9.js.map +1 -0
  188. package/build/server/chunks/_server.ts-DYpJImqd.js +99 -0
  189. package/build/server/chunks/_server.ts-DYpJImqd.js.map +1 -0
  190. package/build/server/chunks/{_server.ts-ColfDHW8.js → _server.ts-DYvb9ijZ.js} +21 -10
  191. package/build/server/chunks/_server.ts-DYvb9ijZ.js.map +1 -0
  192. package/build/server/chunks/{_server.ts-Cv_OrRuL.js → _server.ts-Deok2y88.js} +209 -34
  193. package/build/server/chunks/_server.ts-Deok2y88.js.map +1 -0
  194. package/build/server/chunks/{_server.ts-y9-WYDMa.js → _server.ts-WhTJBEJy.js} +5 -4
  195. package/build/server/chunks/{_server.ts-y9-WYDMa.js.map → _server.ts-WhTJBEJy.js.map} +1 -1
  196. package/build/server/chunks/{_server.ts-BjOJsoy4.js → _server.ts-XzT2UHM1.js} +6 -5
  197. package/build/server/chunks/_server.ts-XzT2UHM1.js.map +1 -0
  198. package/build/server/chunks/{_server.ts-BgdjBZco.js → _server.ts-tSpgyl1D.js} +7 -5
  199. package/build/server/chunks/_server.ts-tSpgyl1D.js.map +1 -0
  200. package/build/server/chunks/{_server.ts-BihKSdj_.js → _server.ts-vekTmWAx.js} +8 -8
  201. package/build/server/chunks/_server.ts-vekTmWAx.js.map +1 -0
  202. package/build/server/chunks/{auth-CEgFis71.js → auth-DeCdZ83n.js} +2 -2
  203. package/build/server/chunks/{auth-CEgFis71.js.map → auth-DeCdZ83n.js.map} +1 -1
  204. package/build/server/chunks/client-BdGHe_hY.js +25 -0
  205. package/build/server/chunks/client-BdGHe_hY.js.map +1 -0
  206. package/build/server/chunks/client2-CCBGA-2V.js +7 -0
  207. package/build/server/chunks/client2-CCBGA-2V.js.map +1 -0
  208. package/build/server/chunks/error-DDXB3duW.js +12 -0
  209. package/build/server/chunks/error-DDXB3duW.js.map +1 -0
  210. package/build/server/chunks/{exports-CJ0Q5XmL.js → index-DwaY1cAm.js} +1111 -1634
  211. package/build/server/chunks/index-DwaY1cAm.js.map +1 -0
  212. package/build/server/chunks/index-server-CrDaL06Y.js +9 -0
  213. package/build/server/chunks/index-server-CrDaL06Y.js.map +1 -0
  214. package/build/server/chunks/index2-CgclKpUj.js +58 -0
  215. package/build/server/chunks/index2-CgclKpUj.js.map +1 -0
  216. package/build/server/chunks/{library-apns-BHxLmuIx.js → library-apns-BqJbvSKh.js} +4 -4
  217. package/build/server/chunks/library-apns-BqJbvSKh.js.map +1 -0
  218. package/build/server/chunks/markdown-W_mTBct0.js +8 -0
  219. package/build/server/chunks/markdown-W_mTBct0.js.map +1 -0
  220. package/build/server/chunks/opencode-db-path-DcfhJtJy.js +15 -0
  221. package/build/server/chunks/opencode-db-path-DcfhJtJy.js.map +1 -0
  222. package/build/server/chunks/{pty-manager-C0FhBiVq.js → pty-manager-BQVB7IVj.js} +155 -326
  223. package/build/server/chunks/pty-manager-BQVB7IVj.js.map +1 -0
  224. package/build/server/chunks/root-DDSnEAZv.js +1171 -0
  225. package/build/server/chunks/root-DDSnEAZv.js.map +1 -0
  226. package/build/server/chunks/{shared-server-BDY8jh20.js → shared-server-sSGG17Df.js} +2 -3
  227. package/build/server/chunks/{shared-server-BDY8jh20.js.map → shared-server-sSGG17Df.js.map} +1 -1
  228. package/build/server/chunks/state.svelte-hBbXlUak.js +11 -0
  229. package/build/server/chunks/state.svelte-hBbXlUak.js.map +1 -0
  230. package/build/server/chunks/stores-DHNzYNpX.js +28 -0
  231. package/build/server/chunks/stores-DHNzYNpX.js.map +1 -0
  232. package/build/server/index.js +1085 -2242
  233. package/build/server/index.js.map +1 -1
  234. package/build/server/manifest.js +39 -25
  235. package/build/server/manifest.js.map +1 -1
  236. package/package.json +32 -9
  237. package/scripts/fix-generated-types.sh +37 -0
  238. package/scripts/homebrew/shooter.rb +51 -0
  239. package/scripts/install.sh +348 -186
  240. package/scripts/setup.cjs +215 -45
  241. package/server.ts +114 -71
  242. package/src/app.css +12 -3
  243. package/src/app.d.ts +13 -20
  244. package/src/app.html +3 -2
  245. package/src/generated/types/API.ts +280 -0
  246. package/src/generated/types/APN.ts +186 -203
  247. package/src/generated/types/CLI.ts +18 -25
  248. package/src/generated/types/Client.ts +589 -0
  249. package/src/generated/types/Config.ts +53 -0
  250. package/src/generated/types/Holder.ts +638 -0
  251. package/src/generated/types/JWT.ts +39 -50
  252. package/src/generated/types/Notification.ts +426 -0
  253. package/src/generated/types/OpenCode.ts +356 -0
  254. package/src/generated/types/Sessions.ts +570 -0
  255. package/src/generated/types/Terminal.ts +2184 -2071
  256. package/src/generated/types/WsProtocol.ts +2004 -0
  257. package/src/generated/types/index.ts +9 -3
  258. package/src/lib/env.ts +29 -0
  259. package/src/lib/modules/client/common/cache.ts +10 -2
  260. package/src/lib/modules/client/common/config-guard.ts +37 -5
  261. package/src/lib/modules/client/common/error.ts +10 -0
  262. package/src/lib/modules/client/common/index.ts +6 -5
  263. package/src/lib/modules/client/common/markdown.ts +22 -1
  264. package/src/lib/modules/client/common/native-bridge.ts +28 -20
  265. package/src/lib/modules/client/common/time.ts +13 -11
  266. package/src/lib/modules/client/terminal/ChatView.svelte +354 -74
  267. package/src/lib/modules/client/terminal/CommandPalette.svelte +3 -2
  268. package/src/lib/modules/client/terminal/ConnectionStatus.svelte +7 -1
  269. package/src/lib/modules/client/terminal/LaunchSheet.svelte +147 -84
  270. package/src/lib/modules/client/terminal/QuickKeys.svelte +3 -1
  271. package/src/lib/modules/client/terminal/ShortcutsHelp.svelte +2 -5
  272. package/src/lib/modules/client/terminal/keyboard-shortcuts.ts +27 -24
  273. package/src/lib/modules/client/terminal/xterm-wrapper.ts +74 -45
  274. package/src/lib/modules/server/apn/library-apns.ts +3 -2
  275. package/src/lib/modules/server/apn/notification-history.ts +2 -13
  276. package/src/lib/modules/server/apn/notification-sessions.ts +3 -13
  277. package/src/lib/modules/server/apn/pending-requests.ts +3 -8
  278. package/src/lib/modules/server/apn/types.ts +5 -4
  279. package/src/lib/modules/server/cli/index.ts +3 -2
  280. package/src/lib/modules/server/fcm/fcm-service.ts +8 -6
  281. package/src/lib/modules/server/sessions/jsonl-parser.ts +3 -3
  282. package/src/lib/modules/server/sessions/jsonl-reader.ts +86 -26
  283. package/src/lib/modules/server/sessions/opencode-db-path.ts +26 -0
  284. package/src/lib/modules/server/sessions/opencode-reader.ts +13 -15
  285. package/src/lib/modules/server/sessions/process-detector.ts +103 -0
  286. package/src/lib/modules/server/sessions/types.ts +11 -22
  287. package/src/lib/modules/server/terminal/holder-client.ts +272 -248
  288. package/src/lib/modules/server/terminal/opencode-watcher.ts +547 -556
  289. package/src/lib/modules/server/terminal/pty-holder.cjs +37 -8
  290. package/src/lib/modules/server/terminal/pty-manager.ts +157 -115
  291. package/src/lib/modules/server/terminal/session-watcher.ts +6 -4
  292. package/src/lib/modules/server/terminal/terminal-store.ts +131 -128
  293. package/src/lib/modules/server/utils/error.ts +9 -0
  294. package/src/lib/modules/server/ws/events-handler.ts +12 -6
  295. package/src/lib/modules/server/ws/keepalive.ts +86 -69
  296. package/src/lib/modules/server/ws/server.ts +43 -37
  297. package/src/lib/modules/server/ws/session-handler.ts +332 -147
  298. package/src/lib/modules/server/ws/terminal-handler.ts +29 -17
  299. package/src/lib/modules/server/ws/ticket-store.ts +29 -26
  300. package/src/lib/theme.css +30 -0
  301. package/src/lib/types/config.ts +1 -6
  302. package/src/routes/+error.svelte +94 -0
  303. package/src/routes/+layout.svelte +66 -31
  304. package/src/routes/+page.svelte +25 -22
  305. package/src/routes/api/debug/+server.ts +3 -1
  306. package/src/routes/api/device-token/+server.ts +60 -60
  307. package/src/routes/api/health/+server.ts +81 -73
  308. package/src/routes/api/notify/+server.ts +115 -68
  309. package/src/routes/api/qr-config/+server.ts +30 -32
  310. package/src/routes/api/response/+server.ts +9 -4
  311. package/src/routes/api/sessions/+server.ts +15 -5
  312. package/src/routes/api/sessions/connect/+server.ts +125 -0
  313. package/src/routes/api/sessions/detect/+server.ts +27 -0
  314. package/src/routes/api/terminals/+server.ts +26 -24
  315. package/src/routes/api/terminals/[id]/+server.ts +13 -7
  316. package/src/routes/api/terminals/[id]/paste-image/+server.ts +54 -52
  317. package/src/routes/api/terminals/[id]/resize/+server.ts +6 -3
  318. package/src/routes/api/webhook/+server.ts +8 -10
  319. package/src/routes/api/ws-status/+server.ts +7 -5
  320. package/src/routes/api/ws-ticket/+server.ts +42 -41
  321. package/src/routes/config/+page.svelte +149 -75
  322. package/src/routes/project/+page.svelte +165 -35
  323. package/src/routes/session/[id]/+page.svelte +479 -283
  324. package/src/routes/terminals/+page.svelte +58 -45
  325. package/src/routes/terminals/[id]/+page.svelte +223 -91
  326. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.br +0 -0
  327. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.gz +0 -0
  328. package/build/client/_app/immutable/assets/3.C0uFg0IS.css +0 -1
  329. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.br +0 -0
  330. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.gz +0 -0
  331. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css +0 -1
  332. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.br +0 -0
  333. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.gz +0 -0
  334. package/build/client/_app/immutable/assets/5.DRjApZQW.css +0 -1
  335. package/build/client/_app/immutable/assets/5.DRjApZQW.css.br +0 -0
  336. package/build/client/_app/immutable/assets/5.DRjApZQW.css.gz +0 -0
  337. package/build/client/_app/immutable/assets/6.AraZrY8I.css +0 -1
  338. package/build/client/_app/immutable/assets/6.AraZrY8I.css.br +0 -0
  339. package/build/client/_app/immutable/assets/6.AraZrY8I.css.gz +0 -0
  340. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css +0 -1
  341. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.br +0 -0
  342. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.gz +0 -0
  343. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css +0 -1
  344. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.br +0 -0
  345. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.gz +0 -0
  346. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css +0 -1
  347. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.br +0 -0
  348. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.gz +0 -0
  349. package/build/client/_app/immutable/chunks/BNJphC1q.js +0 -56
  350. package/build/client/_app/immutable/chunks/BNJphC1q.js.br +0 -0
  351. package/build/client/_app/immutable/chunks/BNJphC1q.js.gz +0 -0
  352. package/build/client/_app/immutable/chunks/Bvk7mfPM.js +0 -1
  353. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.br +0 -0
  354. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.gz +0 -0
  355. package/build/client/_app/immutable/chunks/CAokzuPQ.js +0 -1
  356. package/build/client/_app/immutable/chunks/CAokzuPQ.js.br +0 -0
  357. package/build/client/_app/immutable/chunks/CAokzuPQ.js.gz +0 -0
  358. package/build/client/_app/immutable/chunks/CGLrx-H5.js +0 -1
  359. package/build/client/_app/immutable/chunks/CGLrx-H5.js.br +0 -0
  360. package/build/client/_app/immutable/chunks/CGLrx-H5.js.gz +0 -0
  361. package/build/client/_app/immutable/chunks/CgCpWzEA.js +0 -1
  362. package/build/client/_app/immutable/chunks/CgCpWzEA.js.br +0 -0
  363. package/build/client/_app/immutable/chunks/CgCpWzEA.js.gz +0 -0
  364. package/build/client/_app/immutable/chunks/Cjwk_cGO.js +0 -6
  365. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.br +0 -0
  366. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.gz +0 -0
  367. package/build/client/_app/immutable/chunks/CtQ8EED1.js +0 -11
  368. package/build/client/_app/immutable/chunks/CtQ8EED1.js.br +0 -0
  369. package/build/client/_app/immutable/chunks/CtQ8EED1.js.gz +0 -0
  370. package/build/client/_app/immutable/chunks/DERQCisl.js +0 -1
  371. package/build/client/_app/immutable/chunks/DERQCisl.js.br +0 -0
  372. package/build/client/_app/immutable/chunks/DERQCisl.js.gz +0 -0
  373. package/build/client/_app/immutable/chunks/DKrg8TQs.js +0 -1
  374. package/build/client/_app/immutable/chunks/DKrg8TQs.js.br +0 -0
  375. package/build/client/_app/immutable/chunks/DKrg8TQs.js.gz +0 -0
  376. package/build/client/_app/immutable/chunks/Dkkpz_4D.js +0 -126
  377. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.br +0 -0
  378. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.gz +0 -0
  379. package/build/client/_app/immutable/chunks/DoczjQhA.js +0 -1
  380. package/build/client/_app/immutable/chunks/DoczjQhA.js.br +0 -0
  381. package/build/client/_app/immutable/chunks/DoczjQhA.js.gz +0 -0
  382. package/build/client/_app/immutable/chunks/RpcNruLP.js +0 -2
  383. package/build/client/_app/immutable/chunks/RpcNruLP.js.br +0 -0
  384. package/build/client/_app/immutable/chunks/RpcNruLP.js.gz +0 -0
  385. package/build/client/_app/immutable/chunks/a-St0Zwo.js +0 -1
  386. package/build/client/_app/immutable/chunks/a-St0Zwo.js.br +0 -0
  387. package/build/client/_app/immutable/chunks/a-St0Zwo.js.gz +0 -0
  388. package/build/client/_app/immutable/chunks/bo70OQUZ.js +0 -1
  389. package/build/client/_app/immutable/chunks/bo70OQUZ.js.br +0 -0
  390. package/build/client/_app/immutable/chunks/bo70OQUZ.js.gz +0 -0
  391. package/build/client/_app/immutable/entry/app.QvGgdvTI.js +0 -2
  392. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.br +0 -0
  393. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.gz +0 -0
  394. package/build/client/_app/immutable/entry/start.BntDNRMC.js +0 -1
  395. package/build/client/_app/immutable/entry/start.BntDNRMC.js.br +0 -0
  396. package/build/client/_app/immutable/entry/start.BntDNRMC.js.gz +0 -0
  397. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js +0 -1
  398. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.br +0 -0
  399. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.gz +0 -0
  400. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js +0 -1
  401. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.br +0 -0
  402. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.gz +0 -0
  403. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js +0 -1
  404. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.br +0 -0
  405. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.gz +0 -0
  406. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js +0 -3
  407. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.br +0 -0
  408. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.gz +0 -0
  409. package/build/client/_app/immutable/nodes/4.D-cIe70D.js +0 -1
  410. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.br +0 -0
  411. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.gz +0 -0
  412. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js +0 -1
  413. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.br +0 -0
  414. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.gz +0 -0
  415. package/build/client/_app/immutable/nodes/6.BB7QE48r.js +0 -2
  416. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.br +0 -0
  417. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.gz +0 -0
  418. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js +0 -2
  419. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.br +0 -0
  420. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.gz +0 -0
  421. package/build/client/manifest.webmanifest +0 -1
  422. package/build/client/registerSW.js +0 -1
  423. package/build/client/registerSW.js.br +0 -0
  424. package/build/client/registerSW.js.gz +0 -0
  425. package/build/client/sw.js +0 -222
  426. package/build/client/sw.js.br +0 -0
  427. package/build/client/sw.js.gz +0 -0
  428. package/build/client/workbox-5119daf5.js +0 -3395
  429. package/build/client/workbox-5119daf5.js.br +0 -0
  430. package/build/client/workbox-5119daf5.js.gz +0 -0
  431. package/build/server/chunks/0-q2IUp76Y.js +0 -9
  432. package/build/server/chunks/0-q2IUp76Y.js.map +0 -1
  433. package/build/server/chunks/1-CU50G5wZ.js +0 -9
  434. package/build/server/chunks/1-CU50G5wZ.js.map +0 -1
  435. package/build/server/chunks/2-D01t9s8T.js +0 -9
  436. package/build/server/chunks/2-D01t9s8T.js.map +0 -1
  437. package/build/server/chunks/3-5PUQ04wC.js +0 -9
  438. package/build/server/chunks/3-5PUQ04wC.js.map +0 -1
  439. package/build/server/chunks/4-e7gywnSG.js +0 -9
  440. package/build/server/chunks/4-e7gywnSG.js.map +0 -1
  441. package/build/server/chunks/5-CA1SA6KZ.js +0 -9
  442. package/build/server/chunks/5-CA1SA6KZ.js.map +0 -1
  443. package/build/server/chunks/6-71H221sV.js +0 -9
  444. package/build/server/chunks/6-71H221sV.js.map +0 -1
  445. package/build/server/chunks/7-Bo-vmdyz.js +0 -9
  446. package/build/server/chunks/7-Bo-vmdyz.js.map +0 -1
  447. package/build/server/chunks/_layout.svelte-SFHOxs74.js +0 -132
  448. package/build/server/chunks/_layout.svelte-SFHOxs74.js.map +0 -1
  449. package/build/server/chunks/_page.svelte-B4w-2wD-.js +0 -120
  450. package/build/server/chunks/_page.svelte-B4w-2wD-.js.map +0 -1
  451. package/build/server/chunks/_page.svelte-B_qAXjkh.js +0 -213
  452. package/build/server/chunks/_page.svelte-B_qAXjkh.js.map +0 -1
  453. package/build/server/chunks/_page.svelte-CsF1_TRG.js +0 -50
  454. package/build/server/chunks/_page.svelte-CsF1_TRG.js.map +0 -1
  455. package/build/server/chunks/_page.svelte-DJC6U-P0.js +0 -68
  456. package/build/server/chunks/_page.svelte-DJC6U-P0.js.map +0 -1
  457. package/build/server/chunks/_page.svelte-DQ6HBtsz.js +0 -407
  458. package/build/server/chunks/_page.svelte-DQ6HBtsz.js.map +0 -1
  459. package/build/server/chunks/_page.svelte-LbhhjP21.js +0 -148
  460. package/build/server/chunks/_page.svelte-LbhhjP21.js.map +0 -1
  461. package/build/server/chunks/_server.ts-BL2FGb5Z.js.map +0 -1
  462. package/build/server/chunks/_server.ts-BgdjBZco.js.map +0 -1
  463. package/build/server/chunks/_server.ts-BihKSdj_.js.map +0 -1
  464. package/build/server/chunks/_server.ts-BjOJsoy4.js.map +0 -1
  465. package/build/server/chunks/_server.ts-C29xzfaw.js.map +0 -1
  466. package/build/server/chunks/_server.ts-CPa6DgIt.js.map +0 -1
  467. package/build/server/chunks/_server.ts-CbDRDIoP.js.map +0 -1
  468. package/build/server/chunks/_server.ts-Cl1OEWL4.js +0 -54
  469. package/build/server/chunks/_server.ts-Cl1OEWL4.js.map +0 -1
  470. package/build/server/chunks/_server.ts-ColfDHW8.js.map +0 -1
  471. package/build/server/chunks/_server.ts-Cv_OrRuL.js.map +0 -1
  472. package/build/server/chunks/_server.ts-DRVbgm6k.js.map +0 -1
  473. package/build/server/chunks/_server.ts-DfajWaqh.js.map +0 -1
  474. package/build/server/chunks/client-CxCatAKr.js +0 -255
  475. package/build/server/chunks/client-CxCatAKr.js.map +0 -1
  476. package/build/server/chunks/error.svelte-BqdwMWdK.js +0 -26
  477. package/build/server/chunks/error.svelte-BqdwMWdK.js.map +0 -1
  478. package/build/server/chunks/exports-CJ0Q5XmL.js.map +0 -1
  479. package/build/server/chunks/index2-DAxIoAO-.js +0 -36
  480. package/build/server/chunks/index2-DAxIoAO-.js.map +0 -1
  481. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js +0 -137
  482. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js.map +0 -1
  483. package/build/server/chunks/library-apns-BHxLmuIx.js.map +0 -1
  484. package/build/server/chunks/markdown-Bxrl3cCF.js +0 -1241
  485. package/build/server/chunks/markdown-Bxrl3cCF.js.map +0 -1
  486. package/build/server/chunks/pty-manager-C0FhBiVq.js.map +0 -1
  487. package/build/server/chunks/stores-D0HorpgL.js +0 -36
  488. package/build/server/chunks/stores-D0HorpgL.js.map +0 -1
@@ -7,79 +7,79 @@ import { join } from 'path';
7
7
 
8
8
  import type { RequestHandler } from './$types';
9
9
 
10
- interface DeviceTokens {
11
- android?: string;
12
- ios?: string;
13
- }
14
-
15
- interface DeviceTokenRequest {
16
- bundleId?: string;
17
- deviceToken?: string;
18
- platform: string;
19
- token?: string;
20
- }
21
-
22
10
  const TOKENS_DIR = join(homedir(), '.shooter');
23
11
  const TOKENS_FILE = join(TOKENS_DIR, 'device-tokens.json');
24
12
 
25
- function readTokens(): DeviceTokens {
26
- try {
27
- if (existsSync(TOKENS_FILE)) {
28
- return JSON.parse(readFileSync(TOKENS_FILE, 'utf-8'));
29
- }
30
- } catch {
31
- // Corrupt file — start fresh
32
- }
33
- return {};
13
+ function readTokens(): { android?: string; ios?: string } {
14
+ try {
15
+ if (existsSync(TOKENS_FILE)) {
16
+ const parsed: unknown = JSON.parse(readFileSync(TOKENS_FILE, 'utf-8'));
17
+ // Guard against valid-but-wrong JSON (null, array, number, string)
18
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
19
+ return {};
20
+ }
21
+ return parsed as { android?: string; ios?: string };
22
+ }
23
+ } catch {
24
+ // Corrupt file -- start fresh
25
+ }
26
+ return {};
34
27
  }
35
28
 
36
- function writeTokens(tokens: DeviceTokens): void {
37
- if (!existsSync(TOKENS_DIR)) {
38
- mkdirSync(TOKENS_DIR, { recursive: true });
39
- }
40
- writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2), 'utf-8');
29
+ function writeTokens(tokens: { android?: string; ios?: string }): void {
30
+ if (!existsSync(TOKENS_DIR)) {
31
+ mkdirSync(TOKENS_DIR, { mode: 0o700, recursive: true });
32
+ }
33
+ writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2), { encoding: 'utf-8', mode: 0o600 });
41
34
  }
42
35
 
43
36
  export const POST: RequestHandler = async ({ request }) => {
44
- const authError = validateAuth(request);
45
- if (authError) return authError;
37
+ const authError = validateAuth(request);
38
+ if (authError) {
39
+ return authError;
40
+ }
46
41
 
47
- let body: DeviceTokenRequest;
48
- try {
49
- body = await request.json();
50
- } catch {
51
- return json({ error: 'Invalid JSON body' }, { status: 400 });
52
- }
42
+ let body: { bundleId?: string; deviceToken?: string; platform: string; token?: string };
43
+ try {
44
+ const parsed: unknown = await request.json();
45
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
46
+ return json({ error: 'Invalid JSON body: expected an object' }, { status: 400 });
47
+ }
48
+ body = parsed as typeof body;
49
+ } catch {
50
+ return json({ error: 'Invalid JSON body' }, { status: 400 });
51
+ }
53
52
 
54
- const platform = body.platform;
55
- if (!platform || (platform !== 'ios' && platform !== 'android')) {
56
- return json(
57
- { error: 'Missing or invalid platform (must be "ios" or "android")' },
58
- { status: 400 },
59
- );
60
- }
53
+ const platform = body.platform;
54
+ if (!platform || (platform !== 'ios' && platform !== 'android')) {
55
+ return json(
56
+ { error: 'Missing or invalid platform (must be "ios" or "android")' },
57
+ { status: 400 }
58
+ );
59
+ }
61
60
 
62
- // iOS sends "deviceToken", Android sends "token"
63
- const token = body.deviceToken || body.token;
64
- if (!token || typeof token !== 'string' || token.trim().length === 0) {
65
- return json({ error: 'Missing device token (deviceToken or token)' }, { status: 400 });
66
- }
61
+ // iOS sends "deviceToken", Android sends "token"
62
+ const rawToken = body.deviceToken || body.token;
63
+ if (!rawToken || typeof rawToken !== 'string' || rawToken.trim().length === 0) {
64
+ return json({ error: 'Missing device token (deviceToken or token)' }, { status: 400 });
65
+ }
66
+ const token = rawToken.trim();
67
67
 
68
- // Persist to ~/.shooter/device-tokens.json
69
- const tokens = readTokens();
70
- tokens[platform] = token;
71
- writeTokens(tokens);
68
+ // Persist to ~/.shooter/device-tokens.json
69
+ const tokens = readTokens();
70
+ tokens[platform] = token;
71
+ writeTokens(tokens);
72
72
 
73
- // Update in-memory env so APNs can use it immediately (iOS is the primary APNs target)
74
- if (platform === 'ios') {
75
- (env as Record<string, string>).DEVICE_TOKEN = token;
76
- }
73
+ // Update in-memory env so APNs can use it immediately (iOS is the primary APNs target)
74
+ if (platform === 'ios') {
75
+ (env as Record<string, string>).DEVICE_TOKEN = token;
76
+ }
77
77
 
78
- console.log(`[device-token] Registered ${platform} token (length: ${token.length})`);
78
+ console.log(`[device-token] Registered ${platform} token (length: ${token.length})`);
79
79
 
80
- return json({
81
- platform,
82
- success: true,
83
- timestamp: new Date().toISOString(),
84
- });
80
+ return json({
81
+ platform,
82
+ success: true,
83
+ timestamp: new Date().toISOString(),
84
+ });
85
85
  };
@@ -1,40 +1,27 @@
1
+ import type {
2
+ FCMConfiguration,
3
+ HealthChecks,
4
+ HealthConfiguration,
5
+ HealthStatus,
6
+ } from '$generated/types';
7
+
1
8
  import { env } from '$env/dynamic/private';
2
9
  import { validateAuth } from '$lib/modules/server/auth';
3
10
  import { json } from '@sveltejs/kit';
11
+ import { readFileSync } from 'fs';
12
+ import { join } from 'path';
4
13
 
5
14
  import type { RequestHandler } from './$types';
6
15
 
7
- interface FCMConfiguration {
8
- configured: boolean;
9
- hasClientEmail: boolean;
10
- hasPrivateKey: boolean;
11
- hasProjectId: boolean;
12
- }
13
-
14
- interface HealthChecks {
15
- hasApiKey: boolean;
16
- hasAPNsConfig: boolean;
17
- hasBundleId: boolean;
18
- hasDeviceToken: boolean;
19
- hasFCMConfig: boolean;
20
- }
21
-
22
- interface HealthConfiguration {
23
- apnsKeyId: null | string;
24
- bundleId: null | string;
25
- deviceTokenLength: number;
26
- fcm: FCMConfiguration;
27
- production: boolean;
28
- }
29
-
30
- interface HealthResponse {
31
- checks: HealthChecks;
32
- configuration: HealthConfiguration;
33
- environment: string;
34
- status: 'degraded' | 'healthy';
35
- timestamp: string;
36
- version: string;
37
- }
16
+ const PKG_VERSION: string = (() => {
17
+ const root = process.env.SHOOTER_PKG_ROOT || process.cwd();
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')) as { version?: string };
20
+ return pkg.version || 'unknown';
21
+ } catch {
22
+ return 'unknown';
23
+ }
24
+ })();
38
25
 
39
26
  export const GET: RequestHandler = ({ request, url }) => {
40
27
  // Basic status check is public (used by layout status badge).
@@ -42,58 +29,79 @@ export const GET: RequestHandler = ({ request, url }) => {
42
29
  const wantsDetails = url.searchParams.get('details') === 'true';
43
30
  if (wantsDetails) {
44
31
  const authError = validateAuth(request);
45
- if (authError) return authError;
32
+ if (authError) {
33
+ return authError;
34
+ }
46
35
  }
47
36
 
48
- const hasProjectId = !!env.FCM_PROJECT_ID;
49
- const hasClientEmail = !!env.FCM_CLIENT_EMAIL;
50
- const hasPrivateKey = !!env.FCM_PRIVATE_KEY;
37
+ const hasProjectId = !!env.FCM_PROJECT_ID?.trim();
38
+ const hasClientEmail = !!env.FCM_CLIENT_EMAIL?.trim();
39
+ const hasPrivateKey = !!env.FCM_PRIVATE_KEY?.trim();
40
+
41
+ const checks: HealthChecks = {
42
+ hasApiKey: !!env.API_KEY?.trim(),
43
+ hasAPNsConfig: !!(env.APNS_KEY_ID?.trim() && env.APNS_TEAM_ID?.trim() && env.APNS_KEY?.trim()),
44
+ hasBundleId: !!env.APNS_BUNDLE_ID?.trim(),
45
+ hasDeviceToken: !!env.DEVICE_TOKEN?.trim(),
46
+ hasFCMConfig: hasProjectId && hasClientEmail && hasPrivateKey,
47
+ };
48
+
49
+ const fcm: FCMConfiguration = {
50
+ configured: hasProjectId && hasClientEmail && hasPrivateKey,
51
+ hasClientEmail,
52
+ hasPrivateKey,
53
+ hasProjectId,
54
+ };
55
+
56
+ const configuration: HealthConfiguration = {
57
+ apnsKeyId: env.APNS_KEY_ID ? `${env.APNS_KEY_ID.substring(0, 4)}...` : '',
58
+ bundleId: env.APNS_BUNDLE_ID || '',
59
+ deviceTokenLength: env.DEVICE_TOKEN ? env.DEVICE_TOKEN.length : 0,
60
+ fcm,
61
+ production: env.APNS_PRODUCTION === 'true',
62
+ };
63
+
64
+ // Collect warnings for optional features that are not configured.
65
+ // These are informational — the server is still fully functional for
66
+ // terminals and sessions without push notification support.
67
+ const warnings: string[] = [];
51
68
 
52
- const health: HealthResponse = {
53
- checks: {
54
- hasApiKey: !!env.API_KEY,
55
- hasAPNsConfig: !!(
56
- env.APNS_KEY_ID &&
57
- env.APNS_TEAM_ID &&
58
- env.APNS_KEY
59
- ),
60
- hasBundleId: !!env.APNS_BUNDLE_ID,
61
- hasDeviceToken: !!env.DEVICE_TOKEN,
62
- hasFCMConfig: hasProjectId && hasClientEmail && hasPrivateKey,
63
- },
64
- configuration: {
65
- apnsKeyId: env.APNS_KEY_ID ? `${env.APNS_KEY_ID.substring(0, 4)}...` : null,
66
- bundleId: env.APNS_BUNDLE_ID || null,
67
- deviceTokenLength: env.DEVICE_TOKEN ? env.DEVICE_TOKEN.length : 0,
68
- fcm: {
69
- configured: hasProjectId && hasClientEmail && hasPrivateKey,
70
- hasClientEmail,
71
- hasPrivateKey,
72
- hasProjectId,
73
- },
74
- production: env.APNS_PRODUCTION === 'true',
75
- },
69
+ if (!checks.hasAPNsConfig || !checks.hasBundleId) {
70
+ warnings.push('APNs not configured — iOS push notifications disabled');
71
+ }
72
+ if (!checks.hasDeviceToken) {
73
+ warnings.push('No device token set — push notifications have no target');
74
+ }
75
+ if (!checks.hasFCMConfig) {
76
+ warnings.push('FCM not configured — Android push notifications disabled');
77
+ }
78
+
79
+ const health: {
80
+ checks: HealthChecks;
81
+ configuration: HealthConfiguration;
82
+ environment: string;
83
+ status: HealthStatus;
84
+ timestamp: string;
85
+ version: string;
86
+ warnings: string[];
87
+ } = {
88
+ checks,
89
+ configuration,
76
90
  environment: env.NODE_ENV || 'development',
77
91
  status: 'healthy',
78
92
  timestamp: new Date().toISOString(),
79
- version: '1.1.0',
93
+ version: PKG_VERSION,
94
+ warnings,
80
95
  };
81
96
 
82
- // Determine overall health status
83
- // FCM is not a critical check - only APNs is required for core functionality
84
- const criticalChecks = [
85
- health.checks.hasDeviceToken,
86
- health.checks.hasAPNsConfig,
87
- health.checks.hasBundleId,
88
- ];
89
-
90
- if (criticalChecks.some((check) => !check)) {
91
- health.status = 'degraded';
92
- }
97
+ // Only mark as degraded for actual system-level problems
98
+ // (e.g., DB unreachable, critical services down).
99
+ // Missing push-notification config is NOT a degraded state — it just
100
+ // means notifications are disabled, which is fine for terminal/session use.
93
101
 
94
- // Public response: status only. Authenticated: full details.
102
+ // Public response: status + warnings. Authenticated: full details.
95
103
  if (!wantsDetails) {
96
- return json({ status: health.status, timestamp: health.timestamp });
104
+ return json({ status: health.status, timestamp: health.timestamp, version: health.version, warnings: health.warnings });
97
105
  }
98
106
 
99
107
  return json(health);
@@ -1,34 +1,16 @@
1
+ import type { NotificationData } from '$generated/types';
2
+
1
3
  import { env } from '$env/dynamic/private';
2
4
  import { LibraryAPNsService } from '$lib/modules/server/apn/library-apns';
3
5
  import { addNotification, getNotifications } from '$lib/modules/server/apn/notification-history';
4
6
  import { createPendingRequest } from '$lib/modules/server/apn/pending-requests';
5
7
  import { validateAuth } from '$lib/modules/server/auth';
6
8
  import { isFCMConfigured, sendFCMNotification } from '$lib/modules/server/fcm/fcm-service.js';
9
+ import { toErrorMessage } from '$lib/modules/server/utils/error';
7
10
  import { json } from '@sveltejs/kit';
8
11
 
9
12
  import type { RequestHandler } from './$types';
10
13
 
11
- interface FilterResult {
12
- reason: string;
13
- send: boolean;
14
- }
15
-
16
- interface NotificationData {
17
- [key: string]: unknown;
18
- category?: string;
19
- files?: string;
20
- project?: string;
21
- source?: string;
22
- tool?: string;
23
- }
24
-
25
- interface NotificationRequest {
26
- data?: NotificationData;
27
- message: string;
28
- title: string;
29
- waitForResponse?: boolean;
30
- }
31
-
32
14
  // Singleton APNs client - reuses HTTP/2 connection across requests
33
15
  let apnsSingleton: LibraryAPNsService | null = null;
34
16
  function getAPNsClient(): LibraryAPNsService {
@@ -42,12 +24,11 @@ function getAPNsClient(): LibraryAPNsService {
42
24
  const notificationCache = new Map<string, number>();
43
25
  const DEDUP_WINDOW = 10000; // 10 seconds deduplication window
44
26
 
45
- // 🎯 INTELLIGENT NOTIFICATION FILTERING
46
27
  function intelligentNotificationFilter(
47
28
  title: string,
48
29
  message: string,
49
30
  data?: NotificationData
50
- ): FilterResult {
31
+ ): { reason: string; send: boolean } {
51
32
  const source = data?.source || 'unknown';
52
33
 
53
34
  // Check for duplicate notifications first
@@ -137,7 +118,7 @@ function isDuplicateNotification(title: string, message: string, data?: Notifica
137
118
  }
138
119
  }
139
120
 
140
- // Do NOT record here caller must call recordNotification() after
121
+ // Do NOT record here -- caller must call recordNotification() after
141
122
  // successful delivery to avoid cache poisoning on send failure.
142
123
  return false;
143
124
  }
@@ -155,14 +136,35 @@ export const POST: RequestHandler = async ({ request }) => {
155
136
 
156
137
  // Validate API key using timing-safe comparison
157
138
  const authError = validateAuth(request);
158
- if (authError) return authError;
139
+ if (authError) {
140
+ return authError;
141
+ }
159
142
 
160
143
  // Parse request body
161
- const body = (await request.json()) as NotificationRequest;
162
- const { data, message, title, waitForResponse } = body;
144
+ const rawBody: unknown = await request.json();
163
145
 
164
- if (!title || !message) {
165
- return json({ error: 'Title and message are required' }, { status: 400 });
146
+ if (!rawBody || typeof rawBody !== 'object' || Array.isArray(rawBody)) {
147
+ return json({ error: 'Request body must be a JSON object' }, { status: 400 });
148
+ }
149
+
150
+ const body = rawBody as Record<string, unknown>;
151
+ const data = body.data as NotificationData | undefined;
152
+ const requestDeviceToken = body.deviceToken as string | undefined;
153
+ const message = body.message as string;
154
+ const title = body.title as string;
155
+
156
+ // Coerce skipPush and waitForResponse to booleans — string "false" would
157
+ // be truthy so we require an actual boolean, defaulting to false otherwise.
158
+ const skipPush = typeof body.skipPush === 'boolean' ? body.skipPush : false;
159
+ const waitForResponse =
160
+ typeof body.waitForResponse === 'boolean' ? body.waitForResponse : false;
161
+
162
+ if (!title || typeof title !== 'string' || !message || typeof message !== 'string') {
163
+ return json({ error: 'Title and message are required and must be strings' }, { status: 400 });
164
+ }
165
+
166
+ if (requestDeviceToken !== undefined && typeof requestDeviceToken !== 'string') {
167
+ return json({ error: 'deviceToken must be a string' }, { status: 400 });
166
168
  }
167
169
 
168
170
  // Smart notification filtering
@@ -173,17 +175,17 @@ export const POST: RequestHandler = async ({ request }) => {
173
175
 
174
176
  if (!shouldSendNotification.send) {
175
177
  addNotification({
176
- category: data?.category,
177
- data: data as Record<string, unknown>,
178
+ category: data?.category ?? null,
179
+ data: (data as Record<string, unknown>) ?? null,
178
180
  error: shouldSendNotification.reason,
179
181
  id: canonicalRequestId,
180
182
  message,
181
- project: data?.project,
182
- source: data?.source,
183
+ project: data?.project ?? null,
184
+ source: data?.source ?? null,
183
185
  status: 'filtered',
184
186
  timestamp: new Date().toISOString(),
185
187
  title,
186
- tool: data?.tool,
188
+ tool: data?.tool ?? null,
187
189
  });
188
190
 
189
191
  return json({
@@ -194,6 +196,41 @@ export const POST: RequestHandler = async ({ request }) => {
194
196
  });
195
197
  }
196
198
 
199
+ // When skipPush is true, the caller wants to register a pending request
200
+ // (for bidirectional permission polling) without actually sending a push
201
+ // notification. This happens when WebSocket clients are connected and the
202
+ // events channel will broadcast the permission-requested event instead.
203
+ if (skipPush) {
204
+ if (waitForResponse) {
205
+ createPendingRequest(canonicalRequestId, {
206
+ sessionId: (data?.sessionId as string) || '',
207
+ toolInput: (data?.toolInput as Record<string, unknown>) || {},
208
+ toolName: (data?.toolName as string) || '',
209
+ });
210
+ }
211
+
212
+ addNotification({
213
+ category: data?.category ?? null,
214
+ data: (data as Record<string, unknown>) ?? null,
215
+ error: null,
216
+ id: canonicalRequestId,
217
+ message,
218
+ project: data?.project ?? null,
219
+ source: data?.source ?? null,
220
+ status: 'skipped',
221
+ timestamp: new Date().toISOString(),
222
+ title,
223
+ tool: data?.tool ?? null,
224
+ });
225
+
226
+ return json({
227
+ message: 'Push skipped (WebSocket clients connected)',
228
+ requestId: canonicalRequestId,
229
+ success: true,
230
+ timestamp: new Date().toISOString(),
231
+ });
232
+ }
233
+
197
234
  // Build notification payload (shared between APNs and FCM)
198
235
  const payload = {
199
236
  badge: 1,
@@ -219,19 +256,23 @@ export const POST: RequestHandler = async ({ request }) => {
219
256
  if (!isFCMConfigured()) {
220
257
  return json(
221
258
  {
222
- details: 'Missing FCM_PROJECT_ID, FCM_CLIENT_EMAIL, or FCM_PRIVATE_KEY environment variables',
259
+ details:
260
+ 'Missing FCM_PROJECT_ID, FCM_CLIENT_EMAIL, or FCM_PRIVATE_KEY environment variables',
223
261
  error: 'FCM not configured',
224
262
  },
225
263
  { status: 500 }
226
264
  );
227
265
  }
228
266
 
229
- const androidToken = (env.ANDROID_DEVICE_TOKEN || env.DEVICE_TOKEN)?.trim();
267
+ // Honor request-scoped deviceToken, falling back to environment variables.
268
+ const androidToken =
269
+ requestDeviceToken?.trim() || (env.ANDROID_DEVICE_TOKEN || env.DEVICE_TOKEN)?.trim();
230
270
 
231
271
  if (!androidToken) {
232
272
  return json(
233
273
  {
234
- details: 'ANDROID_DEVICE_TOKEN or DEVICE_TOKEN environment variable is missing',
274
+ details:
275
+ 'ANDROID_DEVICE_TOKEN or DEVICE_TOKEN environment variable is missing and no deviceToken in request body',
235
276
  error: 'No device token configured',
236
277
  },
237
278
  { status: 500 }
@@ -253,16 +294,17 @@ export const POST: RequestHandler = async ({ request }) => {
253
294
  }
254
295
 
255
296
  addNotification({
256
- category: data?.category,
257
- data: data as Record<string, unknown>,
297
+ category: data?.category ?? null,
298
+ data: (data as Record<string, unknown>) ?? null,
299
+ error: null,
258
300
  id: canonicalRequestId,
259
301
  message,
260
- project: data?.project,
261
- source: data?.source,
302
+ project: data?.project ?? null,
303
+ source: data?.source ?? null,
262
304
  status: 'sent',
263
305
  timestamp: new Date().toISOString(),
264
306
  title,
265
- tool: data?.tool,
307
+ tool: data?.tool ?? null,
266
308
  });
267
309
 
268
310
  return json({
@@ -276,17 +318,17 @@ export const POST: RequestHandler = async ({ request }) => {
276
318
  console.error(`[notify] FCM delivery failed: ${fcmResult.error}`);
277
319
 
278
320
  addNotification({
279
- category: data?.category,
280
- data: data as Record<string, unknown>,
281
- error: fcmResult.error,
321
+ category: data?.category ?? null,
322
+ data: (data as Record<string, unknown>) ?? null,
323
+ error: fcmResult.error ?? null,
282
324
  id: canonicalRequestId,
283
325
  message,
284
- project: data?.project,
285
- source: data?.source,
326
+ project: data?.project ?? null,
327
+ source: data?.source ?? null,
286
328
  status: 'failed',
287
329
  timestamp: new Date().toISOString(),
288
330
  title,
289
- tool: data?.tool,
331
+ tool: data?.tool ?? null,
290
332
  });
291
333
 
292
334
  return json(
@@ -311,12 +353,15 @@ export const POST: RequestHandler = async ({ request }) => {
311
353
  );
312
354
  }
313
355
 
314
- const deviceToken = env.DEVICE_TOKEN?.trim();
356
+ // Honor request-scoped deviceToken (e.g., from config page test),
357
+ // falling back to the server-wide environment variable.
358
+ const deviceToken = requestDeviceToken?.trim() || env.DEVICE_TOKEN?.trim();
315
359
 
316
360
  if (!deviceToken) {
317
361
  return json(
318
362
  {
319
- details: 'DEVICE_TOKEN environment variable is missing',
363
+ details:
364
+ 'DEVICE_TOKEN environment variable is missing and no deviceToken in request body',
320
365
  error: 'No device token configured',
321
366
  },
322
367
  { status: 500 }
@@ -340,16 +385,17 @@ export const POST: RequestHandler = async ({ request }) => {
340
385
  }
341
386
 
342
387
  addNotification({
343
- category: data?.category,
344
- data: data as Record<string, unknown>,
388
+ category: data?.category ?? null,
389
+ data: (data as Record<string, unknown>) ?? null,
390
+ error: null,
345
391
  id: canonicalRequestId,
346
392
  message,
347
- project: data?.project,
348
- source: data?.source,
393
+ project: data?.project ?? null,
394
+ source: data?.source ?? null,
349
395
  status: 'sent',
350
396
  timestamp: new Date().toISOString(),
351
397
  title,
352
- tool: data?.tool,
398
+ tool: data?.tool ?? null,
353
399
  });
354
400
 
355
401
  return json({
@@ -360,26 +406,26 @@ export const POST: RequestHandler = async ({ request }) => {
360
406
  timestamp: new Date().toISOString(),
361
407
  });
362
408
  } catch (notificationError) {
363
- const err = notificationError as Error;
364
- console.error(`[notify] APNs delivery failed: ${err.message}`);
409
+ const notifErrMsg = toErrorMessage(notificationError);
410
+ console.error(`[notify] APNs delivery failed: ${notifErrMsg}`);
365
411
 
366
412
  addNotification({
367
- category: data?.category,
368
- data: data as Record<string, unknown>,
369
- error: err.message,
413
+ category: data?.category ?? null,
414
+ data: (data as Record<string, unknown>) ?? null,
415
+ error: notifErrMsg,
370
416
  id: canonicalRequestId,
371
417
  message,
372
- project: data?.project,
373
- source: data?.source,
418
+ project: data?.project ?? null,
419
+ source: data?.source ?? null,
374
420
  status: 'failed',
375
421
  timestamp: new Date().toISOString(),
376
422
  title,
377
- tool: data?.tool,
423
+ tool: data?.tool ?? null,
378
424
  });
379
425
 
380
426
  return json(
381
427
  {
382
- details: err.message,
428
+ details: notifErrMsg,
383
429
  error: 'Failed to send notification',
384
430
  requestId: canonicalRequestId,
385
431
  timestamp: new Date().toISOString(),
@@ -389,11 +435,10 @@ export const POST: RequestHandler = async ({ request }) => {
389
435
  }
390
436
  }
391
437
  } catch (error) {
392
- const err = error as Error;
393
- console.error('Notification error:', err);
438
+ console.error('Notification error:', error);
394
439
  return json(
395
440
  {
396
- details: err.message,
441
+ details: toErrorMessage(error),
397
442
  error: 'Failed to send notification',
398
443
  timestamp: new Date().toISOString(),
399
444
  },
@@ -405,7 +450,9 @@ export const POST: RequestHandler = async ({ request }) => {
405
450
  export const GET: RequestHandler = ({ request, url }) => {
406
451
  // Validate API key using timing-safe comparison
407
452
  const authError = validateAuth(request);
408
- if (authError) return authError;
453
+ if (authError) {
454
+ return authError;
455
+ }
409
456
 
410
457
  const limit = parseInt(url.searchParams.get('limit') || '50');
411
458
  const notifications = getNotifications(limit);