@juspay/shooter 1.4.0 → 1.6.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 (480) hide show
  1. package/.claude/hooks/notifier.cjs +15 -1
  2. package/README.md +132 -36
  3. package/bin/shooter.cjs +98 -29
  4. package/build/client/_app/immutable/assets/2.Dk9NfqnS.css +1 -0
  5. package/build/client/_app/immutable/assets/2.Dk9NfqnS.css.br +0 -0
  6. package/build/client/_app/immutable/assets/2.Dk9NfqnS.css.gz +0 -0
  7. package/build/client/_app/immutable/assets/3.DHxQoulp.css +1 -0
  8. package/build/client/_app/immutable/assets/3.DHxQoulp.css.br +0 -0
  9. package/build/client/_app/immutable/assets/3.DHxQoulp.css.gz +0 -0
  10. package/build/client/_app/immutable/assets/{3.DGDHCVnW.css → 4.D5l1JxgO.css} +1 -1
  11. package/build/client/_app/immutable/assets/4.D5l1JxgO.css.br +0 -0
  12. package/build/client/_app/immutable/assets/4.D5l1JxgO.css.gz +0 -0
  13. package/build/client/_app/immutable/assets/5.C5qz-NeI.css +1 -0
  14. package/build/client/_app/immutable/assets/5.C5qz-NeI.css.br +0 -0
  15. package/build/client/_app/immutable/assets/5.C5qz-NeI.css.gz +0 -0
  16. package/build/client/_app/immutable/chunks/1mEchsPO.js +1 -0
  17. package/build/client/_app/immutable/chunks/1mEchsPO.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/1mEchsPO.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/B7X-vhXI.js +1 -0
  20. package/build/client/_app/immutable/chunks/B7X-vhXI.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/B7X-vhXI.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/BRkqKgVG.js +1 -0
  23. package/build/client/_app/immutable/chunks/BRkqKgVG.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/BRkqKgVG.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/BfJ-f-Tu.js +1 -0
  26. package/build/client/_app/immutable/chunks/BfJ-f-Tu.js.br +2 -0
  27. package/build/client/_app/immutable/chunks/BfJ-f-Tu.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/CGMJxf7r.js +1 -0
  29. package/build/client/_app/immutable/chunks/CGMJxf7r.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/CGMJxf7r.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/{CZHsSL_X.js → CJFjKwJ7.js} +1 -1
  32. package/build/client/_app/immutable/chunks/CJFjKwJ7.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/CJFjKwJ7.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/CNH2HlKj.js +20 -0
  35. package/build/client/_app/immutable/chunks/CNH2HlKj.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/CNH2HlKj.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/CR6bkGJW.js +6 -0
  38. package/build/client/_app/immutable/chunks/CR6bkGJW.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/CR6bkGJW.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/CVtJ6yRM.js +1 -0
  41. package/build/client/_app/immutable/chunks/CVtJ6yRM.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/CVtJ6yRM.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/{CSoRdFvv.js → CaiJSUi3.js} +1 -1
  44. package/build/client/_app/immutable/chunks/CaiJSUi3.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/CaiJSUi3.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/{DjsDGxCa.js → CmczWE_d.js} +4 -4
  47. package/build/client/_app/immutable/chunks/CmczWE_d.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/CmczWE_d.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/{CDVSripB.js → CqfYvnci.js} +1 -1
  50. package/build/client/_app/immutable/chunks/CqfYvnci.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/CqfYvnci.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/CsgHjHGZ.js +1 -0
  53. package/build/client/_app/immutable/chunks/CsgHjHGZ.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/CsgHjHGZ.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/{UJOiqIYE.js → CtrCjGZT.js} +1 -1
  56. package/build/client/_app/immutable/chunks/CtrCjGZT.js.br +0 -0
  57. package/build/client/_app/immutable/chunks/CtrCjGZT.js.gz +0 -0
  58. package/build/client/_app/immutable/chunks/DDiOVAd8.js +61 -0
  59. package/build/client/_app/immutable/chunks/DDiOVAd8.js.br +0 -0
  60. package/build/client/_app/immutable/chunks/DDiOVAd8.js.gz +0 -0
  61. package/build/client/_app/immutable/chunks/DVl0sebP.js +2 -0
  62. package/build/client/_app/immutable/chunks/DVl0sebP.js.br +0 -0
  63. package/build/client/_app/immutable/chunks/DVl0sebP.js.gz +0 -0
  64. package/build/client/_app/immutable/chunks/EhLZwqfu.js +3 -0
  65. package/build/client/_app/immutable/chunks/EhLZwqfu.js.br +0 -0
  66. package/build/client/_app/immutable/chunks/EhLZwqfu.js.gz +0 -0
  67. package/build/client/_app/immutable/chunks/gQJcRhou.js +1 -0
  68. package/build/client/_app/immutable/chunks/gQJcRhou.js.br +0 -0
  69. package/build/client/_app/immutable/chunks/gQJcRhou.js.gz +0 -0
  70. package/build/client/_app/immutable/chunks/pRcLbE0d.js +1 -0
  71. package/build/client/_app/immutable/chunks/pRcLbE0d.js.br +0 -0
  72. package/build/client/_app/immutable/chunks/pRcLbE0d.js.gz +0 -0
  73. package/build/client/_app/immutable/chunks/{CF4lQ45j.js → y_g-KC7l.js} +1 -1
  74. package/build/client/_app/immutable/chunks/y_g-KC7l.js.br +0 -0
  75. package/build/client/_app/immutable/chunks/y_g-KC7l.js.gz +0 -0
  76. package/build/client/_app/immutable/entry/app.zJvbFXsj.js +2 -0
  77. package/build/client/_app/immutable/entry/app.zJvbFXsj.js.br +0 -0
  78. package/build/client/_app/immutable/entry/app.zJvbFXsj.js.gz +0 -0
  79. package/build/client/_app/immutable/entry/start.B2Jf5iFd.js +1 -0
  80. package/build/client/_app/immutable/entry/start.B2Jf5iFd.js.br +2 -0
  81. package/build/client/_app/immutable/entry/start.B2Jf5iFd.js.gz +0 -0
  82. package/build/client/_app/immutable/nodes/0.B_E4j3MX.js +1 -0
  83. package/build/client/_app/immutable/nodes/0.B_E4j3MX.js.br +0 -0
  84. package/build/client/_app/immutable/nodes/0.B_E4j3MX.js.gz +0 -0
  85. package/build/client/_app/immutable/nodes/1.B0oFqb8X.js +1 -0
  86. package/build/client/_app/immutable/nodes/1.B0oFqb8X.js.br +0 -0
  87. package/build/client/_app/immutable/nodes/1.B0oFqb8X.js.gz +0 -0
  88. package/build/client/_app/immutable/nodes/2.Bqul0XyM.js +13 -0
  89. package/build/client/_app/immutable/nodes/2.Bqul0XyM.js.br +0 -0
  90. package/build/client/_app/immutable/nodes/2.Bqul0XyM.js.gz +0 -0
  91. package/build/client/_app/immutable/nodes/3.CTqUQKSN.js +9 -0
  92. package/build/client/_app/immutable/nodes/3.CTqUQKSN.js.br +0 -0
  93. package/build/client/_app/immutable/nodes/3.CTqUQKSN.js.gz +0 -0
  94. package/build/client/_app/immutable/nodes/4.B_pbOZoD.js +4 -0
  95. package/build/client/_app/immutable/nodes/4.B_pbOZoD.js.br +0 -0
  96. package/build/client/_app/immutable/nodes/4.B_pbOZoD.js.gz +0 -0
  97. package/build/client/_app/immutable/nodes/5.CdLPNo5-.js +1 -0
  98. package/build/client/_app/immutable/nodes/5.CdLPNo5-.js.br +0 -0
  99. package/build/client/_app/immutable/nodes/5.CdLPNo5-.js.gz +0 -0
  100. package/build/client/_app/immutable/nodes/6.BvjUfHnH.js +1 -0
  101. package/build/client/_app/immutable/nodes/6.BvjUfHnH.js.br +0 -0
  102. package/build/client/_app/immutable/nodes/6.BvjUfHnH.js.gz +0 -0
  103. package/build/client/_app/immutable/nodes/{5.g3R-QfIW.js → 7.5K7Od8ba.js} +3 -3
  104. package/build/client/_app/immutable/nodes/7.5K7Od8ba.js.br +0 -0
  105. package/build/client/_app/immutable/nodes/7.5K7Od8ba.js.gz +0 -0
  106. package/build/client/_app/immutable/nodes/8.Bs1DrW0_.js +2 -0
  107. package/build/client/_app/immutable/nodes/8.Bs1DrW0_.js.br +0 -0
  108. package/build/client/_app/immutable/nodes/8.Bs1DrW0_.js.gz +0 -0
  109. package/build/client/_app/immutable/nodes/9.1fMlGdqv.js +2 -0
  110. package/build/client/_app/immutable/nodes/9.1fMlGdqv.js.br +0 -0
  111. package/build/client/_app/immutable/nodes/9.1fMlGdqv.js.gz +0 -0
  112. package/build/client/_app/version.json +1 -1
  113. package/build/client/_app/version.json.br +0 -0
  114. package/build/client/_app/version.json.gz +0 -0
  115. package/build/server/chunks/0-e1fgD9Mi.js +23 -0
  116. package/build/server/chunks/0-e1fgD9Mi.js.map +1 -0
  117. package/build/server/chunks/1-D2_XVthm.js +9 -0
  118. package/build/server/chunks/{1-BV7u1xGo.js.map → 1-D2_XVthm.js.map} +1 -1
  119. package/build/server/chunks/2-DADX86JZ.js +21 -0
  120. package/build/server/chunks/2-DADX86JZ.js.map +1 -0
  121. package/build/server/chunks/3-CHacdiCg.js +21 -0
  122. package/build/server/chunks/3-CHacdiCg.js.map +1 -0
  123. package/build/server/chunks/4-0UE_6Ep-.js +23 -0
  124. package/build/server/chunks/4-0UE_6Ep-.js.map +1 -0
  125. package/build/server/chunks/5-BBIP1PzX.js +24 -0
  126. package/build/server/chunks/5-BBIP1PzX.js.map +1 -0
  127. package/build/server/chunks/6-CWLNQu6F.js +9 -0
  128. package/build/server/chunks/6-CWLNQu6F.js.map +1 -0
  129. package/build/server/chunks/7-DmQ3B8uy.js +9 -0
  130. package/build/server/chunks/7-DmQ3B8uy.js.map +1 -0
  131. package/build/server/chunks/8-CnFVjQtZ.js +9 -0
  132. package/build/server/chunks/8-CnFVjQtZ.js.map +1 -0
  133. package/build/server/chunks/9-Dw7-P6aF.js +9 -0
  134. package/build/server/chunks/9-Dw7-P6aF.js.map +1 -0
  135. package/build/server/chunks/{Button-Cs1aE6ka.js → Button-WKgiLWZI.js} +4 -9
  136. package/build/server/chunks/Button-WKgiLWZI.js.map +1 -0
  137. package/build/server/chunks/{EmptyState-DDFH1K8g.js → EmptyState-BUBqASsp.js} +3 -3
  138. package/build/server/chunks/{EmptyState-DDFH1K8g.js.map → EmptyState-BUBqASsp.js.map} +1 -1
  139. package/build/server/chunks/{Icon-CEUrotA6.js → Icon-BNBAg85a.js} +3 -3
  140. package/build/server/chunks/Icon-BNBAg85a.js.map +1 -0
  141. package/build/server/chunks/{Shimmer-DB8W1zt6.js → Shimmer-C4uBVwxz.js} +2 -2
  142. package/build/server/chunks/{Shimmer-DB8W1zt6.js.map → Shimmer-C4uBVwxz.js.map} +1 -1
  143. package/build/server/chunks/{_error.svelte-uCOJNxvr.js → _error.svelte-DkIwmECt.js} +5 -5
  144. package/build/server/chunks/{_error.svelte-uCOJNxvr.js.map → _error.svelte-DkIwmECt.js.map} +1 -1
  145. package/build/server/chunks/{_layout.svelte-CtWmEJwe.js → _layout.svelte-DllETxmJ.js} +13 -7
  146. package/build/server/chunks/_layout.svelte-DllETxmJ.js.map +1 -0
  147. package/build/server/chunks/_page.svelte-BZSdLKE_.js +118 -0
  148. package/build/server/chunks/_page.svelte-BZSdLKE_.js.map +1 -0
  149. package/build/server/chunks/{_page.svelte-CxWcQ0Am.js → _page.svelte-Cmuco1mC.js} +84 -199
  150. package/build/server/chunks/_page.svelte-Cmuco1mC.js.map +1 -0
  151. package/build/server/chunks/{_page.svelte-BgevQjq1.js → _page.svelte-Co5sF7W-.js} +12 -11
  152. package/build/server/chunks/{_page.svelte-BgevQjq1.js.map → _page.svelte-Co5sF7W-.js.map} +1 -1
  153. package/build/server/chunks/{_page.svelte-DO4oa_LY.js → _page.svelte-CpL3R-VI.js} +8 -8
  154. package/build/server/chunks/{_page.svelte-DO4oa_LY.js.map → _page.svelte-CpL3R-VI.js.map} +1 -1
  155. package/build/server/chunks/_page.svelte-DDSzYLUs.js +137 -0
  156. package/build/server/chunks/_page.svelte-DDSzYLUs.js.map +1 -0
  157. package/build/server/chunks/_page.svelte-JIkgFUFf.js +26 -0
  158. package/build/server/chunks/_page.svelte-JIkgFUFf.js.map +1 -0
  159. package/build/server/chunks/{_page.svelte-CVq6tRb3.js → _page.svelte-Y9-O5a5w.js} +10 -9
  160. package/build/server/chunks/_page.svelte-Y9-O5a5w.js.map +1 -0
  161. package/build/server/chunks/{_page.svelte-BcZaKdX9.js → _page.svelte-fcX09N4d.js} +9 -9
  162. package/build/server/chunks/{_page.svelte-BcZaKdX9.js.map → _page.svelte-fcX09N4d.js.map} +1 -1
  163. package/build/server/chunks/{_server.ts-DYpJImqd.js → _server.ts-0Xr2fWaq.js} +9 -5
  164. package/build/server/chunks/_server.ts-0Xr2fWaq.js.map +1 -0
  165. package/build/server/chunks/{_server.ts-CTpcLUH8.js → _server.ts-2ixC-X3K.js} +20 -5
  166. package/build/server/chunks/_server.ts-2ixC-X3K.js.map +1 -0
  167. package/build/server/chunks/{_server.ts-CAxsWKvS.js → _server.ts-40c_epk8.js} +20 -4
  168. package/build/server/chunks/_server.ts-40c_epk8.js.map +1 -0
  169. package/build/server/chunks/{_server.ts-WhTJBEJy.js → _server.ts-A9_tRR-K.js} +5 -4
  170. package/build/server/chunks/{_server.ts-WhTJBEJy.js.map → _server.ts-A9_tRR-K.js.map} +1 -1
  171. package/build/server/chunks/_server.ts-BRAzC6W1.js +98 -0
  172. package/build/server/chunks/_server.ts-BRAzC6W1.js.map +1 -0
  173. package/build/server/chunks/{_server.ts-DB_Kg97c.js → _server.ts-BScvgttw.js} +24 -4
  174. package/build/server/chunks/_server.ts-BScvgttw.js.map +1 -0
  175. package/build/server/chunks/{_server.ts-Ch-6iOHp.js → _server.ts-B__YN2kX.js} +121 -87
  176. package/build/server/chunks/_server.ts-B__YN2kX.js.map +1 -0
  177. package/build/server/chunks/{_server.ts-tSpgyl1D.js → _server.ts-Bjbr7glm.js} +4 -3
  178. package/build/server/chunks/_server.ts-Bjbr7glm.js.map +1 -0
  179. package/build/server/chunks/{_server.ts-COu0vNpd.js → _server.ts-BrqaMMAa.js} +7 -6
  180. package/build/server/chunks/_server.ts-BrqaMMAa.js.map +1 -0
  181. package/build/server/chunks/{_server.ts-vekTmWAx.js → _server.ts-BuYyCrnF.js} +6 -4
  182. package/build/server/chunks/_server.ts-BuYyCrnF.js.map +1 -0
  183. package/build/server/chunks/{_server.ts-Deok2y88.js → _server.ts-ByIrRtCx.js} +132 -76
  184. package/build/server/chunks/_server.ts-ByIrRtCx.js.map +1 -0
  185. package/build/server/chunks/{_server.ts-DYvb9ijZ.js → _server.ts-ByPExYfO.js} +4 -3
  186. package/build/server/chunks/{_server.ts-DYvb9ijZ.js.map → _server.ts-ByPExYfO.js.map} +1 -1
  187. package/build/server/chunks/_server.ts-CjpQ10xh.js +123 -0
  188. package/build/server/chunks/_server.ts-CjpQ10xh.js.map +1 -0
  189. package/build/server/chunks/_server.ts-CyjDrcZN.js +21 -0
  190. package/build/server/chunks/_server.ts-CyjDrcZN.js.map +1 -0
  191. package/build/server/chunks/{_server.ts-DV8zTCF9.js → _server.ts-DOGUMzPx.js} +4 -3
  192. package/build/server/chunks/{_server.ts-DV8zTCF9.js.map → _server.ts-DOGUMzPx.js.map} +1 -1
  193. package/build/server/chunks/_server.ts-DZvfyuNj.js +15 -0
  194. package/build/server/chunks/_server.ts-DZvfyuNj.js.map +1 -0
  195. package/build/server/chunks/{_server.ts-XzT2UHM1.js → _server.ts-DkPPTUPo.js} +4 -3
  196. package/build/server/chunks/{_server.ts-XzT2UHM1.js.map → _server.ts-DkPPTUPo.js.map} +1 -1
  197. package/build/server/chunks/{auth-DeCdZ83n.js → auth-DuunT7Cg.js} +2 -2
  198. package/build/server/chunks/{auth-DeCdZ83n.js.map → auth-DuunT7Cg.js.map} +1 -1
  199. package/build/server/chunks/{client-BdGHe_hY.js → client-DRtPDkMh.js} +4 -4
  200. package/build/server/chunks/{client-BdGHe_hY.js.map → client-DRtPDkMh.js.map} +1 -1
  201. package/build/server/chunks/client2-bqqmu0b7.js +7 -0
  202. package/build/server/chunks/{client2-CCBGA-2V.js.map → client2-bqqmu0b7.js.map} +1 -1
  203. package/build/server/chunks/close-BGlLztTb.js +192 -0
  204. package/build/server/chunks/close-BGlLztTb.js.map +1 -0
  205. package/build/server/chunks/events-handler-Dm1mNPQP.js +20 -0
  206. package/build/server/chunks/events-handler-Dm1mNPQP.js.map +1 -0
  207. package/build/server/chunks/html-FW6Ia4bL.js +8 -0
  208. package/build/server/chunks/html-FW6Ia4bL.js.map +1 -0
  209. package/build/server/chunks/{shared-server-sSGG17Df.js → index-CoD1IJuy.js} +2 -11
  210. package/build/server/chunks/index-CoD1IJuy.js.map +1 -0
  211. package/build/server/chunks/{index-DwaY1cAm.js → index-DP9bWJrR.js} +2 -2
  212. package/build/server/chunks/{index-DwaY1cAm.js.map → index-DP9bWJrR.js.map} +1 -1
  213. package/build/server/chunks/{index-server-CrDaL06Y.js → index-server-BUmV4MIG.js} +2 -2
  214. package/build/server/chunks/index-server-BUmV4MIG.js.map +1 -0
  215. package/build/server/chunks/index-server2-BJrT0wnA.js +5 -0
  216. package/build/server/chunks/index-server2-BJrT0wnA.js.map +1 -0
  217. package/build/server/chunks/{index2-CgclKpUj.js → index2-D5Y19GKR.js} +2 -2
  218. package/build/server/chunks/index2-D5Y19GKR.js.map +1 -0
  219. package/build/server/chunks/{library-apns-BqJbvSKh.js → library-apns-Cf-E-DhM.js} +5 -2
  220. package/build/server/chunks/library-apns-Cf-E-DhM.js.map +1 -0
  221. package/build/server/chunks/providers-DtstoHQ0.js +17 -0
  222. package/build/server/chunks/providers-DtstoHQ0.js.map +1 -0
  223. package/build/server/chunks/{pty-manager-BQVB7IVj.js → pty-manager-TyMUpDA9.js} +41 -9
  224. package/build/server/chunks/pty-manager-TyMUpDA9.js.map +1 -0
  225. package/build/server/chunks/{root-DDSnEAZv.js → root-CATOR_0t.js} +2 -2
  226. package/build/server/chunks/root-CATOR_0t.js.map +1 -0
  227. package/build/server/chunks/shared-server-DaWdgxVh.js +11 -0
  228. package/build/server/chunks/shared-server-DaWdgxVh.js.map +1 -0
  229. package/build/server/chunks/{state.svelte-hBbXlUak.js → state.svelte-CftllyvC.js} +3 -3
  230. package/build/server/chunks/state.svelte-CftllyvC.js.map +1 -0
  231. package/build/server/chunks/{stores-DHNzYNpX.js → stores-BjL57aOK.js} +4 -4
  232. package/build/server/chunks/{stores-DHNzYNpX.js.map → stores-BjL57aOK.js.map} +1 -1
  233. package/build/server/index.js +173 -6
  234. package/build/server/index.js.map +1 -1
  235. package/build/server/manifest.js +53 -30
  236. package/build/server/manifest.js.map +1 -1
  237. package/package.json +28 -7
  238. package/scripts/dev.mjs +361 -0
  239. package/scripts/install.sh +133 -73
  240. package/scripts/{fix-generated-types.sh → postgen-types.sh} +2 -2
  241. package/scripts/setup.cjs +440 -244
  242. package/scripts/vercel-env-commands.sh +3 -3
  243. package/server.ts +3 -3
  244. package/src/app.html +163 -0
  245. package/src/lib/modules/client/activity/ActivityFeed.svelte +279 -0
  246. package/src/lib/modules/client/activity/index.ts +8 -0
  247. package/src/lib/modules/client/activity/store.svelte.ts +478 -0
  248. package/src/lib/modules/client/activity/summarizer.ts +224 -0
  249. package/src/lib/modules/client/common/Card.svelte +2 -9
  250. package/src/lib/modules/client/common/EmptyState.svelte +2 -20
  251. package/src/lib/modules/client/common/Icon.svelte +3 -7
  252. package/src/lib/modules/client/common/StatusBadge.svelte +2 -4
  253. package/src/lib/modules/client/common/config-guard.ts +22 -2
  254. package/src/lib/modules/client/common/index.ts +1 -1
  255. package/src/lib/modules/client/common/time.ts +27 -9
  256. package/src/lib/modules/client/dashboard/DashboardCard.svelte +374 -0
  257. package/src/lib/modules/client/dashboard/DashboardView.svelte +66 -0
  258. package/src/lib/modules/client/dashboard/index.ts +12 -0
  259. package/src/lib/modules/client/dashboard/store.svelte.ts +663 -0
  260. package/src/lib/modules/client/dashboard/summarizer.ts +205 -0
  261. package/src/lib/modules/client/neurolink/fetch-proxy.ts +70 -0
  262. package/src/lib/modules/client/neurolink/provider-config.ts +111 -0
  263. package/src/lib/modules/client/terminal/ChatView.svelte +46 -43
  264. package/src/lib/modules/client/terminal/CommandPalette.svelte +3 -12
  265. package/src/lib/modules/client/terminal/ConnectionStatus.svelte +3 -6
  266. package/src/lib/modules/client/terminal/LaunchSheet.svelte +10 -21
  267. package/src/lib/modules/client/terminal/QuickKeys.svelte +4 -11
  268. package/src/lib/modules/client/terminal/ShortcutsHelp.svelte +3 -6
  269. package/src/lib/modules/client/terminal/keyboard-shortcuts.ts +5 -7
  270. package/src/lib/modules/client/terminal/xterm-wrapper.ts +27 -47
  271. package/src/lib/modules/server/apn/library-apns.ts +6 -3
  272. package/src/lib/modules/server/apn/notification-history.ts +2 -2
  273. package/src/lib/modules/server/apn/notification-sessions.ts +1 -3
  274. package/src/lib/modules/server/apn/pending-requests.ts +1 -1
  275. package/src/lib/modules/server/apn/types.ts +2 -52
  276. package/src/lib/modules/server/cli/index.ts +1 -30
  277. package/src/lib/modules/server/cli/runner.ts +7 -15
  278. package/src/lib/modules/server/fcm/fcm-service.ts +2 -2
  279. package/src/lib/modules/server/sessions/jsonl-parser.ts +97 -42
  280. package/src/lib/modules/server/sessions/jsonl-reader.ts +69 -60
  281. package/src/lib/modules/server/sessions/opencode-reader.ts +1 -1
  282. package/src/lib/modules/server/sessions/process-detector.ts +72 -31
  283. package/src/lib/modules/server/sessions/types.ts +2 -42
  284. package/src/lib/modules/server/terminal/holder-client.ts +11 -35
  285. package/src/lib/modules/server/terminal/opencode-watcher.ts +16 -24
  286. package/src/lib/modules/server/terminal/pty-manager.ts +40 -45
  287. package/src/lib/modules/server/terminal/session-watcher.ts +15 -17
  288. package/src/lib/modules/server/terminal/terminal-store.ts +1 -1
  289. package/src/lib/modules/server/ws/events-handler.ts +1 -16
  290. package/src/lib/modules/server/ws/keepalive.ts +1 -5
  291. package/src/lib/modules/server/ws/server.ts +1 -1
  292. package/src/lib/modules/server/ws/session-handler.ts +20 -86
  293. package/src/lib/modules/server/ws/terminal-handler.ts +28 -51
  294. package/src/lib/modules/server/ws/ticket-store.ts +1 -1
  295. package/src/lib/modules/shared/providers.ts +21 -0
  296. package/src/lib/types/activity.ts +18 -0
  297. package/src/lib/types/apn.ts +43 -0
  298. package/src/lib/types/cli.ts +39 -0
  299. package/src/lib/types/common.ts +39 -0
  300. package/src/lib/types/dashboard.ts +4 -0
  301. package/src/lib/types/generated/Client.ts +1656 -0
  302. package/src/{generated/types → lib/types/generated}/WsProtocol.ts +344 -2
  303. package/src/lib/types/index.ts +28 -0
  304. package/src/lib/types/neurolink.ts +4 -0
  305. package/src/lib/types/server.ts +93 -0
  306. package/src/lib/types/sessions.ts +59 -0
  307. package/src/lib/types/terminal-client.ts +132 -0
  308. package/src/lib/types/ws.ts +161 -0
  309. package/src/routes/+error.svelte +7 -2
  310. package/src/routes/+layout.server.ts +9 -0
  311. package/src/routes/+layout.svelte +36 -7
  312. package/src/routes/+page.server.ts +7 -0
  313. package/src/routes/+page.svelte +85 -35
  314. package/src/routes/activity/+page.server.ts +7 -0
  315. package/src/routes/activity/+page.svelte +58 -0
  316. package/src/routes/api/health/+server.ts +32 -19
  317. package/src/routes/api/neurolink-proxy/+server.ts +136 -0
  318. package/src/routes/api/notify/+server.ts +159 -84
  319. package/src/routes/api/sessions/+server.ts +1 -1
  320. package/src/routes/api/sessions/connect/+server.ts +10 -6
  321. package/src/routes/api/terminals/+server.ts +20 -1
  322. package/src/routes/api/terminals/[id]/+server.ts +16 -2
  323. package/src/routes/api/webhook/+server.ts +5 -33
  324. package/src/routes/api/ws-ticket/+server.ts +4 -4
  325. package/src/routes/config/+page.server.ts +9 -0
  326. package/src/routes/config/+page.svelte +118 -25
  327. package/src/routes/neurolink/+page.server.ts +10 -0
  328. package/src/routes/neurolink/+page.svelte +331 -0
  329. package/src/routes/project/+page.svelte +17 -12
  330. package/src/routes/session/[id]/+page.svelte +146 -62
  331. package/src/routes/terminals/+page.svelte +2 -2
  332. package/src/routes/terminals/[id]/+page.svelte +99 -88
  333. package/svelte.config.js +1 -3
  334. package/tsconfig.json +1 -0
  335. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css +0 -1
  336. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.br +0 -1
  337. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.gz +0 -0
  338. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.br +0 -0
  339. package/build/client/_app/immutable/assets/3.DGDHCVnW.css.gz +0 -0
  340. package/build/client/_app/immutable/chunks/B5NAKyil.js +0 -20
  341. package/build/client/_app/immutable/chunks/B5NAKyil.js.br +0 -0
  342. package/build/client/_app/immutable/chunks/B5NAKyil.js.gz +0 -0
  343. package/build/client/_app/immutable/chunks/B8XegpSE.js +0 -1
  344. package/build/client/_app/immutable/chunks/B8XegpSE.js.br +0 -0
  345. package/build/client/_app/immutable/chunks/B8XegpSE.js.gz +0 -0
  346. package/build/client/_app/immutable/chunks/B8zoBsv3.js +0 -6
  347. package/build/client/_app/immutable/chunks/B8zoBsv3.js.br +0 -0
  348. package/build/client/_app/immutable/chunks/B8zoBsv3.js.gz +0 -0
  349. package/build/client/_app/immutable/chunks/BN1NjBrw.js +0 -1
  350. package/build/client/_app/immutable/chunks/BN1NjBrw.js.br +0 -0
  351. package/build/client/_app/immutable/chunks/BN1NjBrw.js.gz +0 -0
  352. package/build/client/_app/immutable/chunks/BOYo8yTr.js +0 -1
  353. package/build/client/_app/immutable/chunks/BOYo8yTr.js.br +0 -0
  354. package/build/client/_app/immutable/chunks/BOYo8yTr.js.gz +0 -0
  355. package/build/client/_app/immutable/chunks/Bu1aqm5j.js +0 -1
  356. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.br +0 -0
  357. package/build/client/_app/immutable/chunks/Bu1aqm5j.js.gz +0 -0
  358. package/build/client/_app/immutable/chunks/CDVSripB.js.br +0 -0
  359. package/build/client/_app/immutable/chunks/CDVSripB.js.gz +0 -0
  360. package/build/client/_app/immutable/chunks/CF4lQ45j.js.br +0 -0
  361. package/build/client/_app/immutable/chunks/CF4lQ45j.js.gz +0 -0
  362. package/build/client/_app/immutable/chunks/CQjSATpv.js +0 -61
  363. package/build/client/_app/immutable/chunks/CQjSATpv.js.br +0 -0
  364. package/build/client/_app/immutable/chunks/CQjSATpv.js.gz +0 -0
  365. package/build/client/_app/immutable/chunks/CSoRdFvv.js.br +0 -0
  366. package/build/client/_app/immutable/chunks/CSoRdFvv.js.gz +0 -0
  367. package/build/client/_app/immutable/chunks/CZHsSL_X.js.br +0 -0
  368. package/build/client/_app/immutable/chunks/CZHsSL_X.js.gz +0 -0
  369. package/build/client/_app/immutable/chunks/DSU1n5N_.js +0 -1
  370. package/build/client/_app/immutable/chunks/DSU1n5N_.js.br +0 -0
  371. package/build/client/_app/immutable/chunks/DSU1n5N_.js.gz +0 -0
  372. package/build/client/_app/immutable/chunks/DVkn4r72.js +0 -1
  373. package/build/client/_app/immutable/chunks/DVkn4r72.js.br +0 -0
  374. package/build/client/_app/immutable/chunks/DVkn4r72.js.gz +0 -0
  375. package/build/client/_app/immutable/chunks/DjsDGxCa.js.br +0 -0
  376. package/build/client/_app/immutable/chunks/DjsDGxCa.js.gz +0 -0
  377. package/build/client/_app/immutable/chunks/UJOiqIYE.js.br +0 -0
  378. package/build/client/_app/immutable/chunks/UJOiqIYE.js.gz +0 -0
  379. package/build/client/_app/immutable/chunks/r0JawsZc.js +0 -2
  380. package/build/client/_app/immutable/chunks/r0JawsZc.js.br +0 -0
  381. package/build/client/_app/immutable/chunks/r0JawsZc.js.gz +0 -0
  382. package/build/client/_app/immutable/entry/app.DwWiuoEC.js +0 -2
  383. package/build/client/_app/immutable/entry/app.DwWiuoEC.js.br +0 -0
  384. package/build/client/_app/immutable/entry/app.DwWiuoEC.js.gz +0 -0
  385. package/build/client/_app/immutable/entry/start.DG8BMhrh.js +0 -1
  386. package/build/client/_app/immutable/entry/start.DG8BMhrh.js.br +0 -0
  387. package/build/client/_app/immutable/entry/start.DG8BMhrh.js.gz +0 -0
  388. package/build/client/_app/immutable/nodes/0.ejabgzDQ.js +0 -1
  389. package/build/client/_app/immutable/nodes/0.ejabgzDQ.js.br +0 -0
  390. package/build/client/_app/immutable/nodes/0.ejabgzDQ.js.gz +0 -0
  391. package/build/client/_app/immutable/nodes/1.BFK7Ubrr.js +0 -1
  392. package/build/client/_app/immutable/nodes/1.BFK7Ubrr.js.br +0 -0
  393. package/build/client/_app/immutable/nodes/1.BFK7Ubrr.js.gz +0 -0
  394. package/build/client/_app/immutable/nodes/2.DV3saFiY.js +0 -1
  395. package/build/client/_app/immutable/nodes/2.DV3saFiY.js.br +0 -0
  396. package/build/client/_app/immutable/nodes/2.DV3saFiY.js.gz +0 -0
  397. package/build/client/_app/immutable/nodes/3.3yohCM25.js +0 -3
  398. package/build/client/_app/immutable/nodes/3.3yohCM25.js.br +0 -0
  399. package/build/client/_app/immutable/nodes/3.3yohCM25.js.gz +0 -0
  400. package/build/client/_app/immutable/nodes/4.D6NIf10D.js +0 -1
  401. package/build/client/_app/immutable/nodes/4.D6NIf10D.js.br +0 -0
  402. package/build/client/_app/immutable/nodes/4.D6NIf10D.js.gz +0 -0
  403. package/build/client/_app/immutable/nodes/5.g3R-QfIW.js.br +0 -0
  404. package/build/client/_app/immutable/nodes/5.g3R-QfIW.js.gz +0 -0
  405. package/build/client/_app/immutable/nodes/6.DSpd_nYK.js +0 -2
  406. package/build/client/_app/immutable/nodes/6.DSpd_nYK.js.br +0 -0
  407. package/build/client/_app/immutable/nodes/6.DSpd_nYK.js.gz +0 -0
  408. package/build/client/_app/immutable/nodes/7.F9WBFTz2.js +0 -2
  409. package/build/client/_app/immutable/nodes/7.F9WBFTz2.js.br +0 -0
  410. package/build/client/_app/immutable/nodes/7.F9WBFTz2.js.gz +0 -0
  411. package/build/server/chunks/0-ePgrkfG9.js +0 -9
  412. package/build/server/chunks/0-ePgrkfG9.js.map +0 -1
  413. package/build/server/chunks/1-BV7u1xGo.js +0 -9
  414. package/build/server/chunks/2-3p1kyvjQ.js +0 -9
  415. package/build/server/chunks/2-3p1kyvjQ.js.map +0 -1
  416. package/build/server/chunks/3-Ck7ewhOX.js +0 -9
  417. package/build/server/chunks/3-Ck7ewhOX.js.map +0 -1
  418. package/build/server/chunks/4-ChFYfo_S.js +0 -9
  419. package/build/server/chunks/4-ChFYfo_S.js.map +0 -1
  420. package/build/server/chunks/5-q-tQLBBu.js +0 -9
  421. package/build/server/chunks/5-q-tQLBBu.js.map +0 -1
  422. package/build/server/chunks/6-BIaAbm8b.js +0 -9
  423. package/build/server/chunks/6-BIaAbm8b.js.map +0 -1
  424. package/build/server/chunks/7--TmbCgrH.js +0 -9
  425. package/build/server/chunks/7--TmbCgrH.js.map +0 -1
  426. package/build/server/chunks/Button-Cs1aE6ka.js.map +0 -1
  427. package/build/server/chunks/Icon-CEUrotA6.js.map +0 -1
  428. package/build/server/chunks/_layout.svelte-CtWmEJwe.js.map +0 -1
  429. package/build/server/chunks/_page.svelte-BdYynOck.js +0 -85
  430. package/build/server/chunks/_page.svelte-BdYynOck.js.map +0 -1
  431. package/build/server/chunks/_page.svelte-CVq6tRb3.js.map +0 -1
  432. package/build/server/chunks/_page.svelte-CxWcQ0Am.js.map +0 -1
  433. package/build/server/chunks/_server.ts-BStnNIcq.js +0 -34
  434. package/build/server/chunks/_server.ts-BStnNIcq.js.map +0 -1
  435. package/build/server/chunks/_server.ts-CAxsWKvS.js.map +0 -1
  436. package/build/server/chunks/_server.ts-COu0vNpd.js.map +0 -1
  437. package/build/server/chunks/_server.ts-CTpcLUH8.js.map +0 -1
  438. package/build/server/chunks/_server.ts-Cf84YIaW.js +0 -25
  439. package/build/server/chunks/_server.ts-Cf84YIaW.js.map +0 -1
  440. package/build/server/chunks/_server.ts-Ch-6iOHp.js.map +0 -1
  441. package/build/server/chunks/_server.ts-CtH0dhUp.js +0 -71
  442. package/build/server/chunks/_server.ts-CtH0dhUp.js.map +0 -1
  443. package/build/server/chunks/_server.ts-DB_Kg97c.js.map +0 -1
  444. package/build/server/chunks/_server.ts-DYpJImqd.js.map +0 -1
  445. package/build/server/chunks/_server.ts-Deok2y88.js.map +0 -1
  446. package/build/server/chunks/_server.ts-tSpgyl1D.js.map +0 -1
  447. package/build/server/chunks/_server.ts-vekTmWAx.js.map +0 -1
  448. package/build/server/chunks/client2-CCBGA-2V.js +0 -7
  449. package/build/server/chunks/index-server-CrDaL06Y.js.map +0 -1
  450. package/build/server/chunks/index2-CgclKpUj.js.map +0 -1
  451. package/build/server/chunks/library-apns-BqJbvSKh.js.map +0 -1
  452. package/build/server/chunks/pty-manager-BQVB7IVj.js.map +0 -1
  453. package/build/server/chunks/root-DDSnEAZv.js.map +0 -1
  454. package/build/server/chunks/shared-server-sSGG17Df.js.map +0 -1
  455. package/build/server/chunks/state.svelte-hBbXlUak.js.map +0 -1
  456. package/src/generated/types/Client.ts +0 -589
  457. package/src/lib/types/config.ts +0 -1
  458. /package/build/client/_app/immutable/assets/{4.BFUut--w.css → 6.BFUut--w.css} +0 -0
  459. /package/build/client/_app/immutable/assets/{4.BFUut--w.css.br → 6.BFUut--w.css.br} +0 -0
  460. /package/build/client/_app/immutable/assets/{4.BFUut--w.css.gz → 6.BFUut--w.css.gz} +0 -0
  461. /package/build/client/_app/immutable/assets/{5.BTOx7yt7.css → 7.BTOx7yt7.css} +0 -0
  462. /package/build/client/_app/immutable/assets/{5.BTOx7yt7.css.br → 7.BTOx7yt7.css.br} +0 -0
  463. /package/build/client/_app/immutable/assets/{5.BTOx7yt7.css.gz → 7.BTOx7yt7.css.gz} +0 -0
  464. /package/build/client/_app/immutable/assets/{6.eZGZN-BF.css → 8.eZGZN-BF.css} +0 -0
  465. /package/build/client/_app/immutable/assets/{6.eZGZN-BF.css.br → 8.eZGZN-BF.css.br} +0 -0
  466. /package/build/client/_app/immutable/assets/{6.eZGZN-BF.css.gz → 8.eZGZN-BF.css.gz} +0 -0
  467. /package/build/client/_app/immutable/assets/{7.DwS5ZHBh.css → 9.DwS5ZHBh.css} +0 -0
  468. /package/build/client/_app/immutable/assets/{7.DwS5ZHBh.css.br → 9.DwS5ZHBh.css.br} +0 -0
  469. /package/build/client/_app/immutable/assets/{7.DwS5ZHBh.css.gz → 9.DwS5ZHBh.css.gz} +0 -0
  470. /package/src/{generated/types → lib/types/generated}/API.ts +0 -0
  471. /package/src/{generated/types → lib/types/generated}/APN.ts +0 -0
  472. /package/src/{generated/types → lib/types/generated}/CLI.ts +0 -0
  473. /package/src/{generated/types → lib/types/generated}/Config.ts +0 -0
  474. /package/src/{generated/types → lib/types/generated}/Holder.ts +0 -0
  475. /package/src/{generated/types → lib/types/generated}/JWT.ts +0 -0
  476. /package/src/{generated/types → lib/types/generated}/Notification.ts +0 -0
  477. /package/src/{generated/types → lib/types/generated}/OpenCode.ts +0 -0
  478. /package/src/{generated/types → lib/types/generated}/Sessions.ts +0 -0
  479. /package/src/{generated/types → lib/types/generated}/Terminal.ts +0 -0
  480. /package/src/{generated/types → lib/types/generated}/index.ts +0 -0
package/scripts/setup.cjs CHANGED
@@ -9,7 +9,6 @@ const { execSync, spawn } = require('child_process');
9
9
  const crypto = require('crypto');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
- const http = require('http');
13
12
 
14
13
  // ── ANSI helpers ─────────────────────────────────────────────────────
15
14
  const C = {
@@ -52,10 +51,28 @@ function escapeForDoubleQuotedShell(s) {
52
51
  return s.replace(/[\\"$`]/g, '\\$&');
53
52
  }
54
53
 
55
- // ── Secure key generator ─────────────────────────────────────────────
56
- // Produces a 256-bit random hex key (64 characters).
57
- function generateSecureKey() {
58
- return crypto.randomBytes(32).toString('hex');
54
+ // ── API key generator ────────────────────────────────────────────────
55
+ // Produces a readable key: <machinename><4 random hex chars>
56
+ // e.g. "sachinsharma567f"
57
+ // Easy to recognise, type, and remember — still unique per install.
58
+ function generateApiKey() {
59
+ const os = require('os');
60
+ // Take only the letters-only leading portion of the hostname — strips
61
+ // device serial suffixes like HCW39CV9MH which always contain digits.
62
+ const name =
63
+ os
64
+ .hostname()
65
+ .toLowerCase()
66
+ .replace(/\.local$/, '') // strip macOS .local suffix
67
+ .replace(/[^a-z0-9]/g, ' ') // turn separators into spaces
68
+ .trim()
69
+ .split(' ')
70
+ .filter((p) => /^[a-z]+$/.test(p)) // keep only pure-letter segments
71
+ .join('')
72
+ .slice(0, 20) || 'shooter';
73
+
74
+ const suffix = crypto.randomBytes(16).toString('hex'); // 128 bits of entropy
75
+ return `${name}-${suffix}`;
59
76
  }
60
77
 
61
78
  // ── Globals ──────────────────────────────────────────────────────────
@@ -63,9 +80,64 @@ const ROOT = process.env.SHOOTER_PKG_ROOT || path.resolve(__dirname, '..');
63
80
  const SHOOTER_HOME = process.env.SHOOTER_HOME || path.join(require('os').homedir(), '.shooter');
64
81
  const DOT_ENV_PATH = path.join(SHOOTER_HOME, '.env');
65
82
  const AUTO_MODE = process.argv.includes('--auto');
83
+ const PUSH_MODE = process.argv.includes('--push');
66
84
 
67
85
  let rl; // readline interface — created in main()
68
86
 
87
+ // ── AI Provider Registry ─────────────────────────────────────────────
88
+ const PROVIDER_REGISTRY = [
89
+ {
90
+ envKeys: ['GOOGLE_AI_API_KEY'],
91
+ hint: 'Get free at https://aistudio.google.com/apikey',
92
+ id: 'google-ai',
93
+ label: 'Google AI (Gemini)',
94
+ },
95
+ {
96
+ envKeys: ['ANTHROPIC_API_KEY'],
97
+ hint: 'Get at https://console.anthropic.com',
98
+ id: 'anthropic',
99
+ label: 'Anthropic (Claude)',
100
+ },
101
+ {
102
+ envKeys: ['OPENAI_API_KEY'],
103
+ hint: 'Get at https://platform.openai.com/api-keys',
104
+ id: 'openai',
105
+ label: 'OpenAI (GPT)',
106
+ },
107
+ {
108
+ envKeys: ['MISTRAL_API_KEY'],
109
+ hint: 'Get at https://console.mistral.ai',
110
+ id: 'mistral',
111
+ label: 'Mistral',
112
+ },
113
+ {
114
+ extraKeys: ['LITELLM_MODEL'],
115
+ envKeys: ['LITELLM_API_KEY', 'LITELLM_BASE_URL'],
116
+ hint: 'Local OpenAI-compatible proxy',
117
+ id: 'litellm',
118
+ label: 'LiteLLM (local)',
119
+ },
120
+ ];
121
+
122
+ const AI_ENV_KEYS = [
123
+ 'ANTHROPIC_API_KEY',
124
+ 'GOOGLE_AI_API_KEY',
125
+ 'LITELLM_API_KEY',
126
+ 'LITELLM_BASE_URL',
127
+ 'LITELLM_MODEL',
128
+ 'MISTRAL_API_KEY',
129
+ 'NEUROLINK_PROVIDER',
130
+ 'OPENAI_API_KEY',
131
+ ];
132
+
133
+ // ── Auto-incrementing step counter ──────────────────────────────────
134
+ let _stepNum = 0;
135
+ let _totalSteps = 5;
136
+ function step(label) {
137
+ _stepNum++;
138
+ console.log(`\n${C.blue}[${_stepNum}/${_totalSteps}] ${label}${C.reset}\n`);
139
+ }
140
+
69
141
  // ── Readline helpers ─────────────────────────────────────────────────
70
142
 
71
143
  function ask(question) {
@@ -97,18 +169,6 @@ async function askRequired(question, validator) {
97
169
  }
98
170
  }
99
171
 
100
- async function askMultiline(prompt) {
101
- console.log(cyan(prompt));
102
- console.log(dim(' Paste the content, then press Enter on an empty line to finish:'));
103
- const lines = [];
104
- while (true) {
105
- const line = await ask('');
106
- if (line === '') break;
107
- lines.push(line);
108
- }
109
- return lines.join('\n');
110
- }
111
-
112
172
  // ── Banner ───────────────────────────────────────────────────────────
113
173
 
114
174
  function printBanner() {
@@ -120,7 +180,7 @@ function printBanner() {
120
180
  console.log(`${C.cyan}${C.bold} |____/|_| |_|\\___/ \\___/ \\__\\___|_| ${C.reset}`);
121
181
  console.log('');
122
182
  console.log(bold(' Interactive Setup Wizard'));
123
- console.log(dim(' Push notifications for your coding sessions'));
183
+ console.log(dim(' Remote terminals, session viewer & push notifications'));
124
184
  console.log('');
125
185
  console.log(dim(' ─────────────────────────────────────────'));
126
186
  console.log('');
@@ -129,7 +189,7 @@ function printBanner() {
129
189
  // ── Prerequisite checks ─────────────────────────────────────────────
130
190
 
131
191
  function checkPrerequisites() {
132
- console.log(bold('1. Checking prerequisites...\n'));
192
+ step('Checking prerequisites...');
133
193
 
134
194
  // Node.js version
135
195
  const nodeVersion = process.versions.node;
@@ -203,77 +263,147 @@ function validateEmail(email) {
203
263
  return null;
204
264
  }
205
265
 
206
- // ── Collect configuration ────────────────────────────────────────────
207
-
208
- async function collectConfig() {
209
- const config = {
210
- apiKey: '',
211
- // iOS APNs
212
- wantIos: false,
213
- apnsKey: '',
214
- apnsKeyId: '',
215
- apnsTeamId: '',
216
- apnsBundleId: '',
217
- apnsProduction: false,
218
- deviceToken: '',
219
- // Android FCM
220
- wantAndroid: false,
221
- fcmProjectId: '',
222
- fcmClientEmail: '',
223
- fcmPrivateKey: '',
224
- androidDeviceToken: '',
225
- };
266
+ // ── Collect AI Provider Config ───────────────────────────────────────
267
+ async function collectAIConfig(config) {
268
+ step('AI-Powered Features (Optional)');
269
+ console.log('');
270
+ console.log(' Shooter uses NeuroLink to generate AI summaries of your coding sessions.');
271
+ console.log(' You can configure one or more AI providers.');
272
+ console.log('');
226
273
 
227
- // ── Auto mode: reuse existing key or generate new, skip push config ──
228
274
  if (AUTO_MODE) {
229
- console.log(bold('2. Auto-configuring...\n'));
275
+ console.log(dim(' Skipping AI provider configuration in --auto mode.'));
276
+ console.log('');
277
+ return;
278
+ }
230
279
 
231
- // Preserve existing API_KEY if .env already exists
232
- if (fs.existsSync(DOT_ENV_PATH)) {
233
- const existing = fs.readFileSync(DOT_ENV_PATH, 'utf-8');
234
- const match = existing.match(/^API_KEY=(.+)$/m);
235
- if (match && match[1]) {
236
- config.apiKey = match[1];
237
- const maskedKey = mask(config.apiKey);
238
- console.log(green(` Existing API key preserved: ${maskedKey}`));
239
- console.log(dim(' Push notifications skipped (run "shooter setup" to configure later)'));
240
- console.log('');
241
- return config;
280
+ const wantAI = await confirm('Configure AI providers?');
281
+ if (!wantAI) {
282
+ console.log(' Skipped summaries will use basic text fallback.');
283
+ return;
284
+ }
285
+
286
+ console.log('');
287
+ console.log(' Available providers:');
288
+ PROVIDER_REGISTRY.forEach((p, i) => {
289
+ console.log(` ${i + 1}. ${p.label}`);
290
+ });
291
+ console.log(` ${PROVIDER_REGISTRY.length + 1}. Skip`);
292
+ console.log('');
293
+
294
+ let configureMore = true;
295
+ while (configureMore) {
296
+ const choice = await askRequired(
297
+ `Choose provider (1-${PROVIDER_REGISTRY.length + 1})`,
298
+ (val) => {
299
+ const n = parseInt(val, 10);
300
+ return n >= 1 && n <= PROVIDER_REGISTRY.length + 1
301
+ ? null
302
+ : `Enter a number 1-${PROVIDER_REGISTRY.length + 1}`;
242
303
  }
304
+ );
305
+
306
+ const idx = parseInt(choice, 10) - 1;
307
+ if (idx >= PROVIDER_REGISTRY.length) {
308
+ break;
243
309
  }
244
310
 
245
- config.apiKey = generateSecureKey();
246
- const maskedKey = mask(config.apiKey);
247
- console.log(green(` API key generated: ${maskedKey}`));
248
- console.log(dim(' Push notifications skipped (run "shooter setup" to configure later)'));
311
+ const provider = PROVIDER_REGISTRY[idx];
312
+ console.log(` ${provider.hint}`);
313
+
314
+ for (const envKey of provider.envKeys) {
315
+ const value = await askRequired(`${envKey}`, (val) =>
316
+ val.length > 10 ? null : 'Key seems too short'
317
+ );
318
+ config[envKey] = value;
319
+ }
320
+
321
+ // Extra optional keys (e.g., LITELLM_MODEL)
322
+ if (provider.extraKeys) {
323
+ for (const envKey of provider.extraKeys) {
324
+ const value = await ask(`${envKey} (Enter to skip)`);
325
+ if (value) {
326
+ config[envKey] = value;
327
+ }
328
+ }
329
+ }
330
+
331
+ // Set as preferred provider if first one configured
332
+ if (!config.NEUROLINK_PROVIDER) {
333
+ config.NEUROLINK_PROVIDER = provider.id;
334
+ }
335
+
336
+ console.log(` ✓ ${provider.label} configured`);
249
337
  console.log('');
250
- return config;
251
- }
252
338
 
253
- // ── API Key ──────────────────────────────────────────────────────
254
- console.log(bold('2. Server authentication\n'));
255
- const apiKeyAnswer = await ask(` API_KEY ${dim('(press Enter to auto-generate)')}: `);
256
- if (apiKeyAnswer) {
257
- config.apiKey = apiKeyAnswer;
258
- } else {
259
- config.apiKey = generateSecureKey();
260
- const maskedKey = mask(config.apiKey);
261
- console.log(green(` Generated API key: ${maskedKey}`));
262
- console.log(dim(' (Saved to ~/.shooter/.env)'));
339
+ configureMore = await confirm('Configure another provider?');
263
340
  }
264
- console.log('');
341
+ }
342
+
343
+ // ── Collect configuration ────────────────────────────────────────────
265
344
 
345
+ // ── Collect push notification config (separate flow) ────────────────
346
+
347
+ async function collectPushConfig(config) {
266
348
  // ── iOS Push Notifications ───────────────────────────────────────
267
- console.log(bold('3. iOS Push Notifications\n'));
268
- config.wantIos = await confirm(' Do you want iOS push notifications?');
349
+ console.log(bold(' iOS Push Notifications\n'));
350
+ config.wantIos = await confirm(' Configure iOS push notifications?');
269
351
 
270
352
  if (config.wantIos) {
271
353
  console.log('');
272
354
 
273
- // APNs key (.p8)
274
- config.apnsKey = await askMultiline(' APNS_KEY (.p8 private key contents):');
275
- if (!config.apnsKey.includes('BEGIN PRIVATE KEY')) {
276
- console.log(yellow(' Warning: Key does not look like a .p8 file. Continuing anyway.'));
355
+ // APNs key (.p8) — accept file path or pasted content, with retry on invalid input
356
+ async function askForP8Key() {
357
+ const apnsKeyInput = await askRequired(` APNS_KEY (.p8 file path or paste key): `, (val) => {
358
+ if (!val) return 'APNs key is required.';
359
+ return null;
360
+ });
361
+
362
+ let key;
363
+
364
+ // Check if input is a file path
365
+ if (fs.existsSync(apnsKeyInput)) {
366
+ try {
367
+ key = fs.readFileSync(apnsKeyInput, 'utf-8').trim();
368
+ console.log(green(` Read key from ${apnsKeyInput}`));
369
+ } catch (err) {
370
+ console.log(red(` Could not read file: ${err.message}`));
371
+ process.exit(1);
372
+ }
373
+ } else if (apnsKeyInput.includes('BEGIN PRIVATE KEY')) {
374
+ // User pasted inline content
375
+ key = apnsKeyInput;
376
+ } else {
377
+ // Might be a partial path or multiline paste — try multiline
378
+ console.log(yellow(' Input does not look like a file path or key.'));
379
+ console.log(
380
+ dim(' Paste the full .p8 key contents (press Enter on empty line to finish):')
381
+ );
382
+ const lines = [apnsKeyInput];
383
+ while (true) {
384
+ const line = await ask('');
385
+ if (line === '') break;
386
+ lines.push(line);
387
+ }
388
+ key = lines.join('\n');
389
+ }
390
+
391
+ if (!key.includes('BEGIN PRIVATE KEY')) {
392
+ console.log(yellow(' Warning: File does not appear to be a valid .p8 private key.'));
393
+ const retry = await ask(cyan(' Re-enter path? [Y/n]: '));
394
+ if (retry.toLowerCase() !== 'n') {
395
+ return askForP8Key(); // Recursive retry
396
+ }
397
+ console.log(yellow(' Skipping APNs configuration.'));
398
+ return null;
399
+ }
400
+
401
+ return key;
402
+ }
403
+
404
+ config.apnsKey = await askForP8Key();
405
+ if (!config.apnsKey) {
406
+ config.wantIos = false;
277
407
  }
278
408
  console.log('');
279
409
 
@@ -302,15 +432,37 @@ async function collectConfig() {
302
432
  console.log('');
303
433
 
304
434
  // ── Android Push Notifications ───────────────────────────────────
305
- console.log(bold('4. Android Push Notifications\n'));
306
- config.wantAndroid = await confirm(' Do you want Android push notifications?');
435
+ console.log(bold(' Android Push Notifications\n'));
436
+ config.wantAndroid = await confirm(' Configure Android push notifications?');
307
437
 
308
438
  if (config.wantAndroid) {
309
439
  console.log('');
310
440
  config.fcmProjectId = await askRequired(' FCM_PROJECT_ID: ');
311
441
  config.fcmClientEmail = await askRequired(' FCM_CLIENT_EMAIL: ', validateEmail);
312
442
 
313
- config.fcmPrivateKey = await askMultiline(' FCM_PRIVATE_KEY (service account private key):');
443
+ const fcmKeyInput = await askRequired(' FCM_PRIVATE_KEY (file path or paste key): ');
444
+
445
+ if (fs.existsSync(fcmKeyInput)) {
446
+ try {
447
+ config.fcmPrivateKey = fs.readFileSync(fcmKeyInput, 'utf-8').trim();
448
+ console.log(green(` Read key from ${fcmKeyInput}`));
449
+ } catch (err) {
450
+ console.log(red(` Could not read file: ${err.message}`));
451
+ process.exit(1);
452
+ }
453
+ } else if (fcmKeyInput.includes('BEGIN')) {
454
+ config.fcmPrivateKey = fcmKeyInput;
455
+ } else {
456
+ console.log(dim(' Paste the full private key (press Enter on empty line to finish):'));
457
+ const lines = [fcmKeyInput];
458
+ while (true) {
459
+ const line = await ask('');
460
+ if (line === '') break;
461
+ lines.push(line);
462
+ }
463
+ config.fcmPrivateKey = lines.join('\n');
464
+ }
465
+
314
466
  if (!config.fcmPrivateKey.includes('BEGIN')) {
315
467
  console.log(
316
468
  yellow(' Warning: Key does not look like a PEM private key. Continuing anyway.')
@@ -326,6 +478,160 @@ async function collectConfig() {
326
478
  }
327
479
  }
328
480
  console.log('');
481
+ }
482
+
483
+ // ── Load existing AI config from .env (preserve during re-run) ──────────
484
+
485
+ function loadExistingAIConfig(config) {
486
+ if (!fs.existsSync(DOT_ENV_PATH)) return;
487
+ const existing = fs.readFileSync(DOT_ENV_PATH, 'utf-8');
488
+
489
+ const get = (key) => {
490
+ const m = existing.match(new RegExp(`^${key}=["']?(.+?)["']?$`, 'm'));
491
+ return m ? m[1] : '';
492
+ };
493
+
494
+ for (const key of AI_ENV_KEYS) {
495
+ const value = get(key);
496
+ if (value) {
497
+ config[key] = value;
498
+ }
499
+ }
500
+ }
501
+
502
+ // ── Load existing push config from .env (preserve during non-push setup) ──
503
+
504
+ function loadExistingPushConfig(config) {
505
+ if (!fs.existsSync(DOT_ENV_PATH)) return;
506
+ const existing = fs.readFileSync(DOT_ENV_PATH, 'utf-8');
507
+
508
+ const get = (key) => {
509
+ const m = existing.match(new RegExp(`^${key}=["']?(.+?)["']?$`, 'm'));
510
+ return m ? m[1] : '';
511
+ };
512
+
513
+ // Preserve iOS config if it was previously set
514
+ const apnsKeyId = get('APNS_KEY_ID');
515
+ if (apnsKeyId) {
516
+ config.wantIos = true;
517
+ // Read the raw APNS_KEY (may have escaped newlines)
518
+ const rawApnsKey = get('APNS_KEY');
519
+ config.apnsKey = rawApnsKey ? rawApnsKey.replace(/\\n/g, '\n') : '';
520
+ config.apnsKeyId = apnsKeyId;
521
+ config.apnsTeamId = get('APNS_TEAM_ID');
522
+ config.apnsBundleId = get('APNS_BUNDLE_ID');
523
+ config.apnsProduction = get('APNS_PRODUCTION') === 'true';
524
+ config.deviceToken = get('DEVICE_TOKEN');
525
+ }
526
+
527
+ // Preserve Android config if it was previously set
528
+ const fcmProjectId = get('FCM_PROJECT_ID');
529
+ if (fcmProjectId) {
530
+ config.wantAndroid = true;
531
+ config.fcmProjectId = fcmProjectId;
532
+ config.fcmClientEmail = get('FCM_CLIENT_EMAIL');
533
+ const rawFcmKey = get('FCM_PRIVATE_KEY');
534
+ config.fcmPrivateKey = rawFcmKey ? rawFcmKey.replace(/\\n/g, '\n') : '';
535
+ config.androidDeviceToken = get('ANDROID_DEVICE_TOKEN');
536
+ }
537
+ }
538
+
539
+ async function collectConfig() {
540
+ const config = {
541
+ apiKey: '',
542
+ // iOS APNs
543
+ wantIos: false,
544
+ apnsKey: '',
545
+ apnsKeyId: '',
546
+ apnsTeamId: '',
547
+ apnsBundleId: '',
548
+ apnsProduction: false,
549
+ deviceToken: '',
550
+ // Android FCM
551
+ wantAndroid: false,
552
+ fcmProjectId: '',
553
+ fcmClientEmail: '',
554
+ fcmPrivateKey: '',
555
+ androidDeviceToken: '',
556
+ };
557
+
558
+ // ── Auto mode: reuse existing key or generate new, skip push config ──
559
+ if (AUTO_MODE) {
560
+ step('Auto-configuring...');
561
+
562
+ // Preserve existing API_KEY if .env already exists
563
+ if (fs.existsSync(DOT_ENV_PATH)) {
564
+ const existing = fs.readFileSync(DOT_ENV_PATH, 'utf-8');
565
+ const match = existing.match(/^API_KEY=["']?(.+?)["']?$/m);
566
+ if (match && match[1]) {
567
+ config.apiKey = match[1];
568
+ const maskedKey = mask(config.apiKey);
569
+ console.log(green(` Existing API key preserved: ${maskedKey}`));
570
+ loadExistingPushConfig(config);
571
+ loadExistingAIConfig(config); // Load existing AI config in auto mode
572
+ if (config.wantIos || config.wantAndroid) {
573
+ console.log(green(' Existing push config preserved.'));
574
+ } else {
575
+ console.log(
576
+ dim(' Push notifications not configured (add later with "shooter setup --push")')
577
+ );
578
+ }
579
+ if (config.NEUROLINK_PROVIDER) {
580
+ console.log(green(' Existing AI config preserved.'));
581
+ } else {
582
+ console.log(dim(' AI providers not configured (add later with "shooter setup")'));
583
+ }
584
+ console.log('');
585
+ return config;
586
+ }
587
+ }
588
+
589
+ config.apiKey = generateApiKey(); // Changed to generateApiKey()
590
+ const maskedKey = mask(config.apiKey);
591
+ console.log(green(` API key generated: ${maskedKey}`));
592
+ console.log(dim(' Push notifications not configured (add later with "shooter setup --push")'));
593
+ console.log(dim(' AI providers not configured (add later with "shooter setup")'));
594
+ console.log('');
595
+ return config;
596
+ }
597
+
598
+ // ── API Key ──────────────────────────────────────────────────────
599
+ step('Server authentication');
600
+ const apiKeyAnswer = await ask(` API_KEY ${dim('(press Enter to auto-generate)')}: `);
601
+ if (apiKeyAnswer) {
602
+ config.apiKey = apiKeyAnswer;
603
+ } else {
604
+ config.apiKey = generateApiKey(); // Changed to generateApiKey()
605
+ const maskedKey = mask(config.apiKey);
606
+ console.log(green(` Generated API key: ${maskedKey}`));
607
+ console.log(dim(' (Saved to ~/.shooter/.env)'));
608
+ }
609
+ console.log('');
610
+
611
+ // ── Push notifications: only if --push flag or user opts in ──────
612
+ if (PUSH_MODE) {
613
+ // Direct push config mode — user explicitly asked for it
614
+ step('Push notification setup');
615
+ await collectPushConfig(config);
616
+ } else {
617
+ // Default: skip push, show how to add later
618
+ // Preserve any existing push config from a previous setup
619
+ loadExistingPushConfig(config);
620
+
621
+ if (config.wantIos || config.wantAndroid) {
622
+ console.log(dim(' Existing push notification config preserved.'));
623
+ console.log(dim(' To reconfigure: shooter setup --push'));
624
+ } else {
625
+ console.log(dim(' Push notifications are optional and not required for the server.'));
626
+ console.log(dim(' Terminals, sessions, and the web UI work without push config.'));
627
+ console.log(dim(` Add push later: ${cyan('shooter setup --push')}`));
628
+ }
629
+ console.log('');
630
+ }
631
+
632
+ // ── AI Providers ───────────────────────────────────────────────────
633
+ loadExistingAIConfig(config);
634
+ await collectAIConfig(config);
329
635
 
330
636
  return config;
331
637
  }
@@ -338,7 +644,7 @@ function buildEnvContent(config) {
338
644
  `# ${new Date().toISOString()}`,
339
645
  '',
340
646
  '# Authentication',
341
- `API_KEY="${escapeForDoubleQuotedShell(config.apiKey)}"`,
647
+ `API_KEY=${config.apiKey}`,
342
648
  '',
343
649
  ];
344
650
 
@@ -398,6 +704,17 @@ function buildEnvContent(config) {
398
704
  }
399
705
  lines.push('');
400
706
 
707
+ // AI / NeuroLink — preserve existing AI keys across re-runs
708
+ lines.push('# AI / NeuroLink');
709
+ for (const key of AI_ENV_KEYS) {
710
+ if (config[key]) {
711
+ lines.push(`${key}=${config[key]}`);
712
+ } else {
713
+ lines.push(`# ${key}=`);
714
+ }
715
+ }
716
+ lines.push('');
717
+
401
718
  // Server
402
719
  lines.push('# Server');
403
720
  lines.push('# PORT=54007');
@@ -407,7 +724,7 @@ function buildEnvContent(config) {
407
724
  }
408
725
 
409
726
  async function writeEnv(config) {
410
- console.log(bold('5. Writing .env file\n'));
727
+ step('Writing .env file');
411
728
 
412
729
  if (fs.existsSync(DOT_ENV_PATH) && !AUTO_MODE) {
413
730
  const overwrite = await confirm(' .env already exists. Overwrite it?');
@@ -427,69 +744,17 @@ async function writeEnv(config) {
427
744
  }
428
745
 
429
746
  fs.writeFileSync(DOT_ENV_PATH, content, { mode: 0o600 });
747
+ // Enforce secure permissions even if file already existed
748
+ fs.chmodSync(DOT_ENV_PATH, 0o600);
430
749
  console.log(green(' .env written successfully.'));
431
750
  console.log('');
432
751
  return true;
433
752
  }
434
753
 
435
- // ── Shell profile export ─────────────────────────────────────────────
436
-
437
- function detectShellProfile() {
438
- const home = require('os').homedir();
439
- const shell = process.env.SHELL || '';
440
-
441
- if (shell.endsWith('/zsh')) {
442
- return path.join(home, '.zshrc');
443
- }
444
- if (shell.endsWith('/bash')) {
445
- // macOS uses .bash_profile for login shells; Linux uses .bashrc
446
- const bashProfile = path.join(home, '.bash_profile');
447
- if (fs.existsSync(bashProfile)) return bashProfile;
448
- return path.join(home, '.bashrc');
449
- }
450
- // Fallback for other shells
451
- return path.join(home, '.profile');
452
- }
453
-
454
- async function offerShellExport(apiKey) {
455
- console.log(bold('6. Shell environment\n'));
456
-
457
- const profilePath = detectShellProfile();
458
- const profileName = path.basename(profilePath);
459
-
460
- // Check if export already exists
461
- if (fs.existsSync(profilePath)) {
462
- const contents = fs.readFileSync(profilePath, 'utf-8');
463
- if (contents.includes('export API_KEY=')) {
464
- console.log(green(` export API_KEY is already in ~/${profileName}`));
465
- console.log('');
466
- return;
467
- }
468
- }
469
-
470
- console.log(dim(' The Claude Code hooks need API_KEY in your shell environment.'));
471
- const shouldAdd = AUTO_MODE || await confirm(` Add 'export API_KEY=...' to ~/${profileName}?`);
472
-
473
- if (shouldAdd) {
474
- const exportLine = `\nexport API_KEY="${escapeForDoubleQuotedShell(apiKey)}"\n`;
475
- fs.appendFileSync(profilePath, exportLine, 'utf-8');
476
- console.log(green(` Added to ~/${profileName}`));
477
- console.log(
478
- yellow(` Run ${cyan(`source ~/${profileName}`)} or open a new terminal for hooks to work.`)
479
- );
480
- } else {
481
- console.log(
482
- yellow(' Skipped. You will need to set API_KEY manually for hooks to authenticate.')
483
- );
484
- console.log(dim(` Add this to your shell profile: export API_KEY="<your key from .env>"`));
485
- }
486
- console.log('');
487
- }
488
-
489
754
  // ── Build ────────────────────────────────────────────────────────────
490
755
 
491
756
  function runBuild() {
492
- console.log(bold('7. Building the project...\n'));
757
+ step('Building the project...');
493
758
  try {
494
759
  execSync('pnpm build', { cwd: ROOT, stdio: 'inherit' });
495
760
  console.log('');
@@ -505,107 +770,6 @@ function runBuild() {
505
770
  }
506
771
  }
507
772
 
508
- // ── Health check ─────────────────────────────────────────────────────
509
-
510
- function testHealth() {
511
- return new Promise((resolve) => {
512
- console.log(bold('9. Testing server health...\n'));
513
-
514
- const port = process.env.PORT || 54007;
515
- let serverProcess;
516
- let resolved = false;
517
-
518
- function finish(ok, msg) {
519
- if (resolved) return;
520
- resolved = true;
521
- if (serverProcess && !serverProcess.killed) {
522
- serverProcess.kill('SIGTERM');
523
- }
524
- if (ok) {
525
- console.log(green(` ${msg}`));
526
- } else {
527
- console.log(yellow(` ${msg}`));
528
- }
529
- console.log('');
530
- resolve(ok);
531
- }
532
-
533
- // Start the server
534
- serverProcess = spawn('node', ['--import', 'tsx', 'server.ts'], {
535
- cwd: ROOT,
536
- stdio: 'pipe',
537
- env: { ...process.env, PORT: port.toString(), SHOOTER_HOME, SHOOTER_PKG_ROOT: ROOT },
538
- });
539
-
540
- serverProcess.on('error', (err) => {
541
- finish(false, `Could not start server: ${err.message}`);
542
- });
543
-
544
- serverProcess.on('exit', (code) => {
545
- if (!resolved) {
546
- finish(false, `Server exited unexpectedly (code ${code}).`);
547
- }
548
- });
549
-
550
- // Capture stderr/stdout for "listening" signal; try health after delay
551
- let output = '';
552
- const collectOutput = (data) => {
553
- output += data.toString();
554
- };
555
- serverProcess.stdout.on('data', collectOutput);
556
- serverProcess.stderr.on('data', collectOutput);
557
-
558
- // Give the server up to 15 seconds to start, polling every second
559
- let attempts = 0;
560
- const maxAttempts = 15;
561
-
562
- const poll = setInterval(() => {
563
- attempts++;
564
- if (resolved) {
565
- clearInterval(poll);
566
- return;
567
- }
568
- if (attempts > maxAttempts) {
569
- clearInterval(poll);
570
- finish(
571
- false,
572
- 'Server did not respond within 15 seconds. You can test manually with: curl http://localhost:' +
573
- port +
574
- '/api/health'
575
- );
576
- return;
577
- }
578
-
579
- const req = http.get(`http://localhost:${port}/api/health`, (res) => {
580
- let body = '';
581
- res.on('data', (chunk) => {
582
- body += chunk;
583
- });
584
- res.on('end', () => {
585
- clearInterval(poll);
586
- try {
587
- const data = JSON.parse(body);
588
- if (data.status === 'healthy') {
589
- finish(true, `Health check passed: status=${data.status}`);
590
- } else {
591
- finish(
592
- true,
593
- `Server running (status=${data.status}). Some optional features may need configuration.`
594
- );
595
- }
596
- } catch {
597
- finish(true, 'Server responded but health response was not JSON.');
598
- }
599
- });
600
- });
601
- req.on('error', () => {
602
- // Server not ready yet — will retry
603
- });
604
- req.setTimeout(2000, () => req.destroy());
605
- }, 1000);
606
- });
607
- }
608
-
609
773
  // ── Main ─────────────────────────────────────────────────────────────
610
774
 
611
775
  async function main() {
@@ -625,17 +789,30 @@ async function main() {
625
789
  rl.close();
626
790
  });
627
791
 
792
+ // Reset step counter; push mode adds one extra step, AI step adds another
793
+ _stepNum = 0;
794
+ _totalSteps = AUTO_MODE ? 5 : PUSH_MODE ? 7 : 6;
795
+
628
796
  printBanner();
797
+
798
+ if (PUSH_MODE) {
799
+ console.log(bold(' Adding push notification support...\n'));
800
+ }
801
+
629
802
  checkPrerequisites();
630
803
 
631
804
  const config = await collectConfig();
632
805
  await writeEnv(config);
633
- await offerShellExport(config.apiKey);
806
+
807
+ // API_KEY is stored in ~/.shooter/.env — hooks read it automatically
808
+ console.log(dim(' API_KEY is stored in ~/.shooter/.env'));
809
+ console.log(dim(' Claude Code hooks read it automatically from there.'));
810
+ console.log('');
634
811
 
635
812
  const buildOk = runBuild();
636
813
 
637
814
  // ── Remote access info ───────────────────────────────────────────────
638
- console.log(bold('8. Remote access (optional)\n'));
815
+ step('Remote access (optional)');
639
816
 
640
817
  let cloudflaredAvailable = false;
641
818
  try {
@@ -648,14 +825,19 @@ async function main() {
648
825
  if (!cloudflaredAvailable) {
649
826
  console.log(yellow(' cloudflared not found.'));
650
827
  console.log(dim(' Install it to get a public URL for your phone:'));
651
- console.log(dim(' https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
828
+ console.log(
829
+ dim(
830
+ ' https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'
831
+ )
832
+ );
652
833
  console.log('');
653
834
  console.log(dim(' Once installed, run:'));
654
835
  console.log(cyan(' cloudflared tunnel --url http://localhost:54007'));
655
836
  console.log('');
656
837
  } else {
657
838
  const port = process.env.PORT || 54007;
658
- const startTunnel = AUTO_MODE || await confirm(' Start a Cloudflare Tunnel now to get your public URL?');
839
+ const startTunnel =
840
+ AUTO_MODE || (await confirm(' Start a Cloudflare Tunnel now to get your public URL?'));
659
841
  if (startTunnel) {
660
842
  console.log(dim('\n Starting tunnel... (this may take a few seconds)\n'));
661
843
  await new Promise((resolve) => {
@@ -672,7 +854,11 @@ async function main() {
672
854
  console.log(green(' Tunnel URL (use this on your phone):'));
673
855
  console.log(`\n ${C.bold}${C.cyan}${match[0]}${C.reset}\n`);
674
856
  console.log(dim(' Keep this terminal session open to maintain the tunnel.'));
675
- console.log(dim(' For a persistent tunnel, see: https://developers.cloudflare.com/cloudflare-one/'));
857
+ console.log(
858
+ dim(
859
+ ' For a persistent tunnel, see: https://developers.cloudflare.com/cloudflare-one/'
860
+ )
861
+ );
676
862
  console.log('');
677
863
  resolve();
678
864
  }
@@ -710,7 +896,8 @@ async function main() {
710
896
  rl.close();
711
897
 
712
898
  if (buildOk) {
713
- await testHealth();
899
+ console.log(green(' Ready to start! Run: shooter start'));
900
+ console.log('');
714
901
  }
715
902
 
716
903
  // ── Done ───────────────────────────────────────────────────────────
@@ -726,8 +913,17 @@ async function main() {
726
913
  console.log(bold(' Your API key (enter this in the app on your phone):'));
727
914
  console.log(`\n ${C.bold}${C.cyan}${mask(config.apiKey)}${C.reset}\n`);
728
915
  if (!config.wantIos && !config.wantAndroid) {
729
- console.log(yellow(' Note: No push notification platform was configured.'));
730
- console.log(dim(' Run shooter setup again to add iOS or Android push notifications.'));
916
+ console.log(bold(' Optional add-ons:'));
917
+ console.log(` ${dim('Push notifications:')} ${cyan('shooter setup --push')}`);
918
+ console.log(
919
+ ` ${dim('Cloudflare Tunnel:')} ${cyan('shooter start')} ${dim('(auto-starts tunnel)')}`
920
+ );
921
+ console.log('');
922
+ } else {
923
+ const platforms = [];
924
+ if (config.wantIos) platforms.push('iOS');
925
+ if (config.wantAndroid) platforms.push('Android');
926
+ console.log(green(` Push notifications: ${platforms.join(' + ')} configured`));
731
927
  console.log('');
732
928
  }
733
929