@juspay/shooter 1.19.0 → 1.21.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 (283) hide show
  1. package/build/client/_app/immutable/assets/11.F10lvwyh.css +1 -0
  2. package/build/client/_app/immutable/assets/11.F10lvwyh.css.br +0 -0
  3. package/build/client/_app/immutable/assets/11.F10lvwyh.css.gz +0 -0
  4. package/build/client/_app/immutable/assets/2.BHi6pjT2.css +1 -0
  5. package/build/client/_app/immutable/assets/2.BHi6pjT2.css.br +0 -0
  6. package/build/client/_app/immutable/assets/2.BHi6pjT2.css.gz +0 -0
  7. package/build/client/_app/immutable/chunks/{ZS5XYDx_.js → B1bOvemT.js} +1 -1
  8. package/build/client/_app/immutable/chunks/B1bOvemT.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/{ZS5XYDx_.js.gz → B1bOvemT.js.gz} +0 -0
  10. package/build/client/_app/immutable/chunks/{DCDL_9ys.js → BmfLecb1.js} +1 -1
  11. package/build/client/_app/immutable/chunks/BmfLecb1.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/BmfLecb1.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/C87ZRWX0.js +1 -0
  14. package/build/client/_app/immutable/chunks/C87ZRWX0.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/C87ZRWX0.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/{BvmdJful.js → CJulw9ux.js} +1 -1
  17. package/build/client/_app/immutable/chunks/CJulw9ux.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/CJulw9ux.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/DOEXXmsh.js +3 -0
  20. package/build/client/_app/immutable/chunks/DOEXXmsh.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/DOEXXmsh.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/{ClIPTXf3.js → DomZZqvG.js} +1 -1
  23. package/build/client/_app/immutable/chunks/DomZZqvG.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/DomZZqvG.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/{DA4Zt9Me.js → EqMAkEha.js} +1 -1
  26. package/build/client/_app/immutable/chunks/EqMAkEha.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/EqMAkEha.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/J5-Cr5oR.js +6 -0
  29. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/{BB2l8o4X.js → i5iZvmIH.js} +1 -1
  32. package/build/client/_app/immutable/chunks/i5iZvmIH.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/i5iZvmIH.js.gz +0 -0
  34. package/build/client/_app/immutable/entry/{app.D4TXlu7A.js → app.CeSxgGat.js} +2 -2
  35. package/build/client/_app/immutable/entry/app.CeSxgGat.js.br +0 -0
  36. package/build/client/_app/immutable/entry/app.CeSxgGat.js.gz +0 -0
  37. package/build/client/_app/immutable/entry/start.DrnJFwxA.js +1 -0
  38. package/build/client/_app/immutable/entry/start.DrnJFwxA.js.br +2 -0
  39. package/build/client/_app/immutable/entry/start.DrnJFwxA.js.gz +0 -0
  40. package/build/client/_app/immutable/nodes/0.oaPwxh1O.js +10 -0
  41. package/build/client/_app/immutable/nodes/0.oaPwxh1O.js.br +0 -0
  42. package/build/client/_app/immutable/nodes/0.oaPwxh1O.js.gz +0 -0
  43. package/build/client/_app/immutable/nodes/{1.BVnLUSs-.js → 1.DMPyoM-M.js} +1 -1
  44. package/build/client/_app/immutable/nodes/1.DMPyoM-M.js.br +0 -0
  45. package/build/client/_app/immutable/nodes/1.DMPyoM-M.js.gz +0 -0
  46. package/build/client/_app/immutable/nodes/{10.D1wl2wPX.js → 10.Cbm7nQKK.js} +1 -1
  47. package/build/client/_app/immutable/nodes/10.Cbm7nQKK.js.br +0 -0
  48. package/build/client/_app/immutable/nodes/10.Cbm7nQKK.js.gz +0 -0
  49. package/build/client/_app/immutable/nodes/11.CKmZjP_a.js +2 -0
  50. package/build/client/_app/immutable/nodes/11.CKmZjP_a.js.br +0 -0
  51. package/build/client/_app/immutable/nodes/11.CKmZjP_a.js.gz +0 -0
  52. package/build/client/_app/immutable/nodes/2.zlrdNFtH.js +13 -0
  53. package/build/client/_app/immutable/nodes/2.zlrdNFtH.js.br +0 -0
  54. package/build/client/_app/immutable/nodes/2.zlrdNFtH.js.gz +0 -0
  55. package/build/client/_app/immutable/nodes/{3.Wfz3TcJd.js → 3.BgLpGnzb.js} +1 -1
  56. package/build/client/_app/immutable/nodes/3.BgLpGnzb.js.br +0 -0
  57. package/build/client/_app/immutable/nodes/3.BgLpGnzb.js.gz +0 -0
  58. package/build/client/_app/immutable/nodes/{4.CBX9A3ka.js → 4.BFYS2g9C.js} +3 -3
  59. package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.br +0 -0
  60. package/build/client/_app/immutable/nodes/4.BFYS2g9C.js.gz +0 -0
  61. package/build/client/_app/immutable/nodes/{5.DIVKuZc9.js → 5.Avc1-gVb.js} +1 -1
  62. package/build/client/_app/immutable/nodes/5.Avc1-gVb.js.br +0 -0
  63. package/build/client/_app/immutable/nodes/5.Avc1-gVb.js.gz +0 -0
  64. package/build/client/_app/immutable/nodes/{6.DtZAEPXb.js → 6.Dw2wEssJ.js} +1 -1
  65. package/build/client/_app/immutable/nodes/6.Dw2wEssJ.js.br +0 -0
  66. package/build/client/_app/immutable/nodes/6.Dw2wEssJ.js.gz +0 -0
  67. package/build/client/_app/immutable/nodes/{7.MfBRh32I.js → 7.DwKZjoBg.js} +1 -1
  68. package/build/client/_app/immutable/nodes/7.DwKZjoBg.js.br +0 -0
  69. package/build/client/_app/immutable/nodes/7.DwKZjoBg.js.gz +0 -0
  70. package/build/client/_app/immutable/nodes/{8.DVE6LnOC.js → 8.ZUAI6g5E.js} +1 -1
  71. package/build/client/_app/immutable/nodes/8.ZUAI6g5E.js.br +0 -0
  72. package/build/client/_app/immutable/nodes/8.ZUAI6g5E.js.gz +0 -0
  73. package/build/client/_app/immutable/nodes/{9.BCel5OqI.js → 9.I_KGXPwB.js} +1 -1
  74. package/build/client/_app/immutable/nodes/9.I_KGXPwB.js.br +0 -0
  75. package/build/client/_app/immutable/nodes/9.I_KGXPwB.js.gz +0 -0
  76. package/build/client/_app/version.json +1 -1
  77. package/build/client/_app/version.json.br +0 -0
  78. package/build/client/_app/version.json.gz +0 -0
  79. package/build/server/chunks/{0-DJqyZZTr.js → 0-vrTNAfZB.js} +4 -2
  80. package/build/server/chunks/0-vrTNAfZB.js.map +1 -0
  81. package/build/server/chunks/{1-2YUVen1F.js → 1-nbr-bOoF.js} +2 -2
  82. package/build/server/chunks/{1-2YUVen1F.js.map → 1-nbr-bOoF.js.map} +1 -1
  83. package/build/server/chunks/{10-D1X7LB3v.js → 10-ChyvvJ6w.js} +2 -2
  84. package/build/server/chunks/{10-D1X7LB3v.js.map → 10-ChyvvJ6w.js.map} +1 -1
  85. package/build/server/chunks/{11-qXSPdF5j.js → 11-6ZAjL3uU.js} +4 -4
  86. package/build/server/chunks/11-6ZAjL3uU.js.map +1 -0
  87. package/build/server/chunks/{2-BD7kj1mt.js → 2-DWFRVDWJ.js} +3 -3
  88. package/build/server/chunks/{2-BD7kj1mt.js.map → 2-DWFRVDWJ.js.map} +1 -1
  89. package/build/server/chunks/{3-oNjv-BhZ.js → 3-CKANM_WM.js} +2 -2
  90. package/build/server/chunks/{3-oNjv-BhZ.js.map → 3-CKANM_WM.js.map} +1 -1
  91. package/build/server/chunks/{4-Bb5VFhsO.js → 4-D92KnTmb.js} +3 -3
  92. package/build/server/chunks/{4-Bb5VFhsO.js.map → 4-D92KnTmb.js.map} +1 -1
  93. package/build/server/chunks/{5-oNoWuIsn.js → 5-BxVjs2qi.js} +2 -2
  94. package/build/server/chunks/{5-oNoWuIsn.js.map → 5-BxVjs2qi.js.map} +1 -1
  95. package/build/server/chunks/{6-DRJGUqHG.js → 6-Cbf1AAMQ.js} +2 -2
  96. package/build/server/chunks/{6-DRJGUqHG.js.map → 6-Cbf1AAMQ.js.map} +1 -1
  97. package/build/server/chunks/{7-_giJiu0L.js → 7-CMK2quEf.js} +2 -2
  98. package/build/server/chunks/{7-_giJiu0L.js.map → 7-CMK2quEf.js.map} +1 -1
  99. package/build/server/chunks/{8-zvWAVNT5.js → 8-DhdfkfDM.js} +2 -2
  100. package/build/server/chunks/{8-zvWAVNT5.js.map → 8-DhdfkfDM.js.map} +1 -1
  101. package/build/server/chunks/{9-DVyDL445.js → 9-CPpxtRM5.js} +2 -2
  102. package/build/server/chunks/{9-DVyDL445.js.map → 9-CPpxtRM5.js.map} +1 -1
  103. package/build/server/chunks/Banner-BgaAs1rs.js.map +1 -1
  104. package/build/server/chunks/Button-D0hZ7JYt.js.map +1 -1
  105. package/build/server/chunks/Icon-D0GBnDcs.js.map +1 -1
  106. package/build/server/chunks/Input-OmIiydSx.js.map +1 -1
  107. package/build/server/chunks/Pill-4xJ-VhAA.js.map +1 -1
  108. package/build/server/chunks/Shimmer-Dw2uvTC1.js.map +1 -1
  109. package/build/server/chunks/_error.svelte-CZnkxeLr.js.map +1 -1
  110. package/build/server/chunks/_layout.svelte-DfgNGGiM.js.map +1 -1
  111. package/build/server/chunks/_page.svelte-BTlfUsBp.js.map +1 -1
  112. package/build/server/chunks/_page.svelte-BX2FMgSg.js.map +1 -1
  113. package/build/server/chunks/_page.svelte-C7B0qdrC.js.map +1 -1
  114. package/build/server/chunks/_page.svelte-CE7COWnF.js.map +1 -1
  115. package/build/server/chunks/_page.svelte-CWsjjd4l.js.map +1 -1
  116. package/build/server/chunks/_page.svelte-D5S2hkBk.js.map +1 -1
  117. package/build/server/chunks/_page.svelte-D_Ey8QRG.js.map +1 -1
  118. package/build/server/chunks/{_page.svelte-BLo2v_8E.js → _page.svelte-Gv9p8nlS.js} +3 -4
  119. package/build/server/chunks/_page.svelte-Gv9p8nlS.js.map +1 -0
  120. package/build/server/chunks/{_page.svelte-BUBLUSGo.js → _page.svelte-dabsQl9c.js} +206 -5
  121. package/build/server/chunks/_page.svelte-dabsQl9c.js.map +1 -0
  122. package/build/server/chunks/_page.svelte-tBuIq8Pg.js.map +1 -1
  123. package/build/server/chunks/_server.ts-B-evHL2q.js +13 -0
  124. package/build/server/chunks/_server.ts-B-evHL2q.js.map +1 -0
  125. package/build/server/chunks/_server.ts-BB46Fbqn.js +59 -0
  126. package/build/server/chunks/_server.ts-BB46Fbqn.js.map +1 -0
  127. package/build/server/chunks/{_server.ts-DMm0hBP4.js → _server.ts-BWVlO8iV.js} +8 -5
  128. package/build/server/chunks/_server.ts-BWVlO8iV.js.map +1 -0
  129. package/build/server/chunks/{_server.ts-BRAzC6W1.js → _server.ts-BevnuePu.js} +27 -6
  130. package/build/server/chunks/_server.ts-BevnuePu.js.map +1 -0
  131. package/build/server/chunks/_server.ts-CA5KUENM.js +95 -0
  132. package/build/server/chunks/_server.ts-CA5KUENM.js.map +1 -0
  133. package/build/server/chunks/{_server.ts-DhJx0DLr.js → _server.ts-CC2K8-L2.js} +16 -7
  134. package/build/server/chunks/_server.ts-CC2K8-L2.js.map +1 -0
  135. package/build/server/chunks/{_server.ts-Bi0Oe4PF.js → _server.ts-CD7JP3fz.js} +14 -9
  136. package/build/server/chunks/_server.ts-CD7JP3fz.js.map +1 -0
  137. package/build/server/chunks/{_server.ts-C_OOUqsd.js → _server.ts-D0zRDSx0.js} +2 -2
  138. package/build/server/chunks/{_server.ts-C_OOUqsd.js.map → _server.ts-D0zRDSx0.js.map} +1 -1
  139. package/build/server/chunks/{_server.ts-DxT9IlZF.js → _server.ts-Dp-hXW_I.js} +3 -3
  140. package/build/server/chunks/{_server.ts-DxT9IlZF.js.map → _server.ts-Dp-hXW_I.js.map} +1 -1
  141. package/build/server/chunks/_server.ts-DpRr0Tfh.js +68 -0
  142. package/build/server/chunks/_server.ts-DpRr0Tfh.js.map +1 -0
  143. package/build/server/chunks/{_server.ts-Bjbr7glm.js → _server.ts-QN-Bo5ql.js} +12 -5
  144. package/build/server/chunks/_server.ts-QN-Bo5ql.js.map +1 -0
  145. package/build/server/chunks/_server.ts-VzDcFFgy.js +157 -0
  146. package/build/server/chunks/_server.ts-VzDcFFgy.js.map +1 -0
  147. package/build/server/chunks/{_server.ts-BrqaMMAa.js → _server.ts-W6i3EnGX.js} +29 -6
  148. package/build/server/chunks/_server.ts-W6i3EnGX.js.map +1 -0
  149. package/build/server/chunks/{_server.ts-CRVNEOd2.js → _server.ts-X1R7L_QI.js} +3 -3
  150. package/build/server/chunks/{_server.ts-CRVNEOd2.js.map → _server.ts-X1R7L_QI.js.map} +1 -1
  151. package/build/server/chunks/cache-BlMaDsHi.js.map +1 -1
  152. package/build/server/chunks/guest-registry-Dxvd7p-g.js +48 -0
  153. package/build/server/chunks/guest-registry-Dxvd7p-g.js.map +1 -0
  154. package/build/server/chunks/index-CoYB03g7.js.map +1 -1
  155. package/build/server/chunks/index2-dSGQ9Eaa.js.map +1 -1
  156. package/build/server/chunks/{pty-manager-41h3IK8K.js → pty-manager-ZqXqa-6A.js} +6 -2
  157. package/build/server/chunks/pty-manager-ZqXqa-6A.js.map +1 -0
  158. package/build/server/chunks/root-D4IoFC8F.js.map +1 -1
  159. package/build/server/chunks/share-auth-BS7JuiHf.js +27 -0
  160. package/build/server/chunks/share-auth-BS7JuiHf.js.map +1 -0
  161. package/build/server/chunks/share-store-B9jMpVg0.js +127 -0
  162. package/build/server/chunks/share-store-B9jMpVg0.js.map +1 -0
  163. package/build/server/chunks/state.svelte-CmHqngc_.js.map +1 -1
  164. package/build/server/chunks/stores-CRYxfF0o.js.map +1 -1
  165. package/build/server/index.js +1 -1
  166. package/build/server/index.js.map +1 -1
  167. package/build/server/manifest.js +57 -22
  168. package/build/server/manifest.js.map +1 -1
  169. package/package.json +2 -2
  170. package/server.ts +10 -3
  171. package/src/lib/modules/client/dashboard/AutopilotPanel.svelte +400 -0
  172. package/src/lib/modules/client/dashboard/index.ts +1 -0
  173. package/src/lib/modules/client/neurolink/provider-config.ts +13 -37
  174. package/src/lib/modules/client/terminal/ShareGate.svelte +96 -0
  175. package/src/lib/modules/client/terminal/ShareSheet.svelte +395 -0
  176. package/src/lib/modules/client/terminal/xterm-wrapper.ts +19 -2
  177. package/src/lib/modules/server/sessions/autopilot-engine.ts +346 -0
  178. package/src/lib/modules/server/sessions/litellm-client.ts +115 -0
  179. package/src/lib/modules/server/sessions/next-step-consensus.ts +185 -0
  180. package/src/lib/modules/server/sessions/summary-store.ts +111 -0
  181. package/src/lib/modules/server/terminal/pty-manager.ts +6 -0
  182. package/src/lib/modules/server/terminal/share-auth.ts +37 -0
  183. package/src/lib/modules/server/terminal/share-store.ts +172 -0
  184. package/src/lib/modules/server/ws/events-handler.ts +32 -0
  185. package/src/lib/modules/server/ws/guest-registry.ts +49 -0
  186. package/src/lib/modules/server/ws/server.ts +22 -3
  187. package/src/lib/modules/server/ws/session-handler.ts +18 -4
  188. package/src/lib/modules/server/ws/terminal-handler.ts +21 -2
  189. package/src/lib/modules/server/ws/ticket-store.ts +18 -10
  190. package/src/lib/types/autopilot.ts +73 -0
  191. package/src/lib/types/generated/Client.ts +25 -1
  192. package/src/lib/types/generated/Share.ts +404 -0
  193. package/src/lib/types/generated/WsProtocol.ts +73 -2
  194. package/src/lib/types/generated/index.ts +1 -0
  195. package/src/lib/types/index.ts +1 -0
  196. package/src/lib/types/terminal-client.ts +21 -2
  197. package/src/lib/types/ws.ts +1 -0
  198. package/src/routes/+layout.server.ts +2 -0
  199. package/src/routes/+layout.svelte +19 -4
  200. package/src/routes/+page.svelte +9 -1
  201. package/src/routes/api/autopilot/+server.ts +74 -0
  202. package/src/routes/api/neurolink-proxy/+server.ts +31 -6
  203. package/src/routes/api/notify/+server.ts +20 -3
  204. package/src/routes/api/summaries/+server.ts +99 -0
  205. package/src/routes/api/terminals/[id]/+server.ts +14 -3
  206. package/src/routes/api/terminals/[id]/paste-image/+server.ts +8 -4
  207. package/src/routes/api/terminals/[id]/resize/+server.ts +8 -4
  208. package/src/routes/api/terminals/[id]/share/+server.ts +98 -0
  209. package/src/routes/api/terminals/[id]/share/auth/+server.ts +81 -0
  210. package/src/routes/api/terminals/[id]/share/status/+server.ts +11 -0
  211. package/src/routes/api/ws-ticket/+server.ts +26 -5
  212. package/src/routes/terminals/[id]/+page.svelte +184 -43
  213. package/build/client/_app/immutable/assets/11.v5KA95xm.css +0 -1
  214. package/build/client/_app/immutable/assets/11.v5KA95xm.css.br +0 -0
  215. package/build/client/_app/immutable/assets/11.v5KA95xm.css.gz +0 -0
  216. package/build/client/_app/immutable/assets/2.DjiwkLqE.css +0 -1
  217. package/build/client/_app/immutable/assets/2.DjiwkLqE.css.br +0 -0
  218. package/build/client/_app/immutable/assets/2.DjiwkLqE.css.gz +0 -0
  219. package/build/client/_app/immutable/chunks/BB2l8o4X.js.br +0 -0
  220. package/build/client/_app/immutable/chunks/BB2l8o4X.js.gz +0 -0
  221. package/build/client/_app/immutable/chunks/BcqA7eKM.js +0 -3
  222. package/build/client/_app/immutable/chunks/BcqA7eKM.js.br +0 -0
  223. package/build/client/_app/immutable/chunks/BcqA7eKM.js.gz +0 -0
  224. package/build/client/_app/immutable/chunks/BvmdJful.js.br +0 -0
  225. package/build/client/_app/immutable/chunks/BvmdJful.js.gz +0 -0
  226. package/build/client/_app/immutable/chunks/CR6bkGJW.js +0 -6
  227. package/build/client/_app/immutable/chunks/CR6bkGJW.js.br +0 -0
  228. package/build/client/_app/immutable/chunks/CR6bkGJW.js.gz +0 -0
  229. package/build/client/_app/immutable/chunks/ClIPTXf3.js.br +0 -0
  230. package/build/client/_app/immutable/chunks/ClIPTXf3.js.gz +0 -0
  231. package/build/client/_app/immutable/chunks/DA4Zt9Me.js.br +0 -0
  232. package/build/client/_app/immutable/chunks/DA4Zt9Me.js.gz +0 -0
  233. package/build/client/_app/immutable/chunks/DCDL_9ys.js.br +0 -0
  234. package/build/client/_app/immutable/chunks/DCDL_9ys.js.gz +0 -0
  235. package/build/client/_app/immutable/chunks/ZS5XYDx_.js.br +0 -0
  236. package/build/client/_app/immutable/chunks/pRcLbE0d.js +0 -1
  237. package/build/client/_app/immutable/chunks/pRcLbE0d.js.br +0 -0
  238. package/build/client/_app/immutable/chunks/pRcLbE0d.js.gz +0 -0
  239. package/build/client/_app/immutable/entry/app.D4TXlu7A.js.br +0 -0
  240. package/build/client/_app/immutable/entry/app.D4TXlu7A.js.gz +0 -0
  241. package/build/client/_app/immutable/entry/start.BBQhtURO.js +0 -1
  242. package/build/client/_app/immutable/entry/start.BBQhtURO.js.br +0 -0
  243. package/build/client/_app/immutable/entry/start.BBQhtURO.js.gz +0 -0
  244. package/build/client/_app/immutable/nodes/0.1zylwAPT.js +0 -10
  245. package/build/client/_app/immutable/nodes/0.1zylwAPT.js.br +0 -0
  246. package/build/client/_app/immutable/nodes/0.1zylwAPT.js.gz +0 -0
  247. package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.br +0 -0
  248. package/build/client/_app/immutable/nodes/1.BVnLUSs-.js.gz +0 -0
  249. package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.br +0 -0
  250. package/build/client/_app/immutable/nodes/10.D1wl2wPX.js.gz +0 -0
  251. package/build/client/_app/immutable/nodes/11.C18nMGmp.js +0 -2
  252. package/build/client/_app/immutable/nodes/11.C18nMGmp.js.br +0 -0
  253. package/build/client/_app/immutable/nodes/11.C18nMGmp.js.gz +0 -0
  254. package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js +0 -13
  255. package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.br +0 -0
  256. package/build/client/_app/immutable/nodes/2.D1Mm0DUX.js.gz +0 -0
  257. package/build/client/_app/immutable/nodes/3.Wfz3TcJd.js.br +0 -0
  258. package/build/client/_app/immutable/nodes/3.Wfz3TcJd.js.gz +0 -0
  259. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.br +0 -0
  260. package/build/client/_app/immutable/nodes/4.CBX9A3ka.js.gz +0 -0
  261. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.br +0 -0
  262. package/build/client/_app/immutable/nodes/5.DIVKuZc9.js.gz +0 -0
  263. package/build/client/_app/immutable/nodes/6.DtZAEPXb.js.br +0 -0
  264. package/build/client/_app/immutable/nodes/6.DtZAEPXb.js.gz +0 -0
  265. package/build/client/_app/immutable/nodes/7.MfBRh32I.js.br +0 -0
  266. package/build/client/_app/immutable/nodes/7.MfBRh32I.js.gz +0 -0
  267. package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.br +0 -0
  268. package/build/client/_app/immutable/nodes/8.DVE6LnOC.js.gz +0 -0
  269. package/build/client/_app/immutable/nodes/9.BCel5OqI.js.br +0 -0
  270. package/build/client/_app/immutable/nodes/9.BCel5OqI.js.gz +0 -0
  271. package/build/server/chunks/0-DJqyZZTr.js.map +0 -1
  272. package/build/server/chunks/11-qXSPdF5j.js.map +0 -1
  273. package/build/server/chunks/_page.svelte-BLo2v_8E.js.map +0 -1
  274. package/build/server/chunks/_page.svelte-BUBLUSGo.js.map +0 -1
  275. package/build/server/chunks/_server.ts-BRAzC6W1.js.map +0 -1
  276. package/build/server/chunks/_server.ts-Bi0Oe4PF.js.map +0 -1
  277. package/build/server/chunks/_server.ts-Bjbr7glm.js.map +0 -1
  278. package/build/server/chunks/_server.ts-BrqaMMAa.js.map +0 -1
  279. package/build/server/chunks/_server.ts-DMm0hBP4.js.map +0 -1
  280. package/build/server/chunks/_server.ts-DhJx0DLr.js.map +0 -1
  281. package/build/server/chunks/events-handler-Dm1mNPQP.js +0 -20
  282. package/build/server/chunks/events-handler-Dm1mNPQP.js.map +0 -1
  283. package/build/server/chunks/pty-manager-41h3IK8K.js.map +0 -1
@@ -146,6 +146,7 @@ export type WireTerminalServerMessage =
146
146
  | { bytes: number; type: 'output-dropped' }
147
147
  | { chunk: number; data: string; total: number; type: 'scrollback' }
148
148
  | { code: null | number; signal: null | string; type: 'exit' }
149
+ | { cols: number; rows: number; type: 'resize' }
149
150
  | { data: string; type: 'output' }
150
151
  | { message: string; type: 'error' };
151
152
 
@@ -5,5 +5,7 @@ import type { LayoutServerLoad } from './$types';
5
5
 
6
6
  export const load: LayoutServerLoad = () => ({
7
7
  aiProviders: getProviderAvailability(env),
8
+ litellmBaseUrl: env.LITELLM_BASE_URL ?? '',
9
+ litellmModel: env.LITELLM_MODEL ?? 'open-large',
8
10
  neurolinkProvider: env.NEUROLINK_PROVIDER ?? '',
9
11
  });
@@ -22,11 +22,26 @@
22
22
  return;
23
23
  }
24
24
  (window as unknown as Record<string, unknown>).__aiProviders = data.aiProviders;
25
+
26
+ // Ensure window.process.env exists minimally so env vars can be injected.
27
+ const win = window as unknown as Record<string, unknown>;
28
+ if (!win.process || typeof win.process !== 'object') {
29
+ win.process = { env: {} };
30
+ }
31
+ const proc = win.process as Record<string, unknown>;
32
+ if (!proc.env || typeof proc.env !== 'object') {
33
+ proc.env = {};
34
+ }
35
+ const procEnv = proc.env as Record<string, string>;
36
+
25
37
  if (data.neurolinkProvider) {
26
- const proc = (window as unknown as { process?: { env?: Record<string, string> } }).process;
27
- if (proc?.env) {
28
- proc.env.NEUROLINK_PROVIDER = data.neurolinkProvider;
29
- }
38
+ procEnv.NEUROLINK_PROVIDER = data.neurolinkProvider;
39
+ }
40
+ if (data.litellmBaseUrl) {
41
+ procEnv.LITELLM_BASE_URL = data.litellmBaseUrl;
42
+ }
43
+ if (data.litellmModel) {
44
+ procEnv.LITELLM_MODEL = data.litellmModel;
30
45
  }
31
46
  });
32
47
 
@@ -12,7 +12,13 @@
12
12
  isShooterConfig,
13
13
  setCache,
14
14
  } from '$lib/modules/client/common';
15
- import { connect, DashboardView, disconnect, getCards } from '$lib/modules/client/dashboard';
15
+ import {
16
+ AutopilotPanel,
17
+ connect,
18
+ DashboardView,
19
+ disconnect,
20
+ getCards,
21
+ } from '$lib/modules/client/dashboard';
16
22
  import { Banner, Button, EmptyState, Icon, Pill, Shimmer } from '@juspay/svelte-ui-components';
17
23
  import { onDestroy, onMount } from 'svelte';
18
24
 
@@ -203,6 +209,8 @@
203
209
  <Button classes="btn-primary" onclick={navigateToConfig} text="Configure Settings" />
204
210
  </EmptyState>
205
211
  {:else}
212
+ <AutopilotPanel />
213
+
206
214
  <!-- Dashboard section: active terminal sessions -->
207
215
  {#if cards.length > 0}
208
216
  <div class="dashboard-section">
@@ -0,0 +1,74 @@
1
+ // Control endpoint for the always-on server-side autopilot engine.
2
+ // GET returns the enabled flag; POST { enabled } toggles it.
3
+ //
4
+ // The engine runs in the server.ts module graph and exposes its control on
5
+ // globalThis.__shooter_autopilot. This route (bundled handler graph) reaches it
6
+ // there rather than importing the engine — importing it would start a second
7
+ // event subscriber. Falls back to the on-disk state file if the engine has not
8
+ // started yet.
9
+
10
+ import { validateAuth } from '$lib/modules/server/auth';
11
+ import { json } from '@sveltejs/kit';
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
13
+ import { homedir } from 'os';
14
+ import { join } from 'path';
15
+
16
+ import type { RequestHandler } from './$types';
17
+
18
+ const STATE_FILE = join(homedir(), '.shooter', 'autopilot.json');
19
+
20
+ function control(): undefined | { isEnabled: () => boolean; setEnabled: (v: boolean) => void } {
21
+ return (globalThis as Record<string, unknown>).__shooter_autopilot as
22
+ | undefined
23
+ | { isEnabled: () => boolean; setEnabled: (v: boolean) => void };
24
+ }
25
+
26
+ function readFileEnabled(): boolean {
27
+ try {
28
+ if (existsSync(STATE_FILE)) {
29
+ const parsed: unknown = JSON.parse(readFileSync(STATE_FILE, 'utf-8'));
30
+ return Boolean((parsed as { enabled?: unknown })?.enabled);
31
+ }
32
+ } catch {
33
+ // corrupt / unreadable
34
+ }
35
+ return false;
36
+ }
37
+
38
+ export const GET: RequestHandler = ({ request }) => {
39
+ const authError = validateAuth(request);
40
+ if (authError) {
41
+ return authError;
42
+ }
43
+ const ctrl = control();
44
+ return json({ enabled: ctrl ? ctrl.isEnabled() : readFileEnabled(), running: Boolean(ctrl) });
45
+ };
46
+
47
+ export const POST: RequestHandler = async ({ request }) => {
48
+ const authError = validateAuth(request);
49
+ if (authError) {
50
+ return authError;
51
+ }
52
+ let body: { enabled?: unknown };
53
+ try {
54
+ body = (await request.json()) as { enabled?: unknown };
55
+ } catch {
56
+ return json({ error: 'Invalid JSON body' }, { status: 400 });
57
+ }
58
+ if (typeof body.enabled !== 'boolean') {
59
+ return json({ error: 'enabled must be a boolean' }, { status: 400 });
60
+ }
61
+
62
+ const ctrl = control();
63
+ if (ctrl) {
64
+ ctrl.setEnabled(body.enabled);
65
+ } else {
66
+ try {
67
+ mkdirSync(join(homedir(), '.shooter'), { recursive: true });
68
+ writeFileSync(STATE_FILE, JSON.stringify({ enabled: body.enabled }), 'utf-8');
69
+ } catch {
70
+ // best-effort
71
+ }
72
+ }
73
+ return json({ enabled: body.enabled, running: Boolean(ctrl) });
74
+ };
@@ -15,6 +15,7 @@ import type { RequestHandler } from './$types';
15
15
  const ALLOWED_CLIENT_HEADERS: Record<string, Set<string>> = {
16
16
  anthropic: new Set(['anthropic-beta', 'anthropic-version']),
17
17
  'google-ai': new Set<string>([]),
18
+ litellm: new Set<string>([]),
18
19
  mistral: new Set([]),
19
20
  openai: new Set(['openai-organization', 'openai-project']),
20
21
  };
@@ -63,15 +64,29 @@ export const POST: RequestHandler = async ({ request }) => {
63
64
  openai: 'https://api.openai.com/',
64
65
  };
65
66
 
66
- const allowedPrefix = ALLOWED_PREFIXES[provider];
67
- if (!allowedPrefix || !url.startsWith(allowedPrefix)) {
68
- return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
67
+ if (provider === 'litellm') {
68
+ // SSRF-safe: only forward to the operator-configured LiteLLM endpoint.
69
+ // Trim to align with getProviderAvailability semantics (whitespace-only = not configured).
70
+ const rawBase = env.LITELLM_BASE_URL?.trim();
71
+ if (!rawBase) {
72
+ return json({ error: 'LiteLLM is not configured on this server' }, { status: 403 });
73
+ }
74
+ const litellmPrefix = `${rawBase.replace(/\/+$/, '')}/`;
75
+ if (!url.startsWith(litellmPrefix)) {
76
+ return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
77
+ }
78
+ } else {
79
+ const allowedPrefix = ALLOWED_PREFIXES[provider];
80
+ if (!allowedPrefix || !url.startsWith(allowedPrefix)) {
81
+ return json({ error: `Provider "${provider}" or URL not allowed` }, { status: 403 });
82
+ }
69
83
  }
70
84
 
71
85
  // Inject the server-side API key so the browser never sees it
72
86
  const apiKeyEnv: Record<string, string> = {
73
87
  anthropic: env.ANTHROPIC_API_KEY ?? '',
74
88
  'google-ai': env.GOOGLE_AI_API_KEY ?? '',
89
+ litellm: env.LITELLM_API_KEY ?? '',
75
90
  mistral: env.MISTRAL_API_KEY ?? '',
76
91
  openai: env.OPENAI_API_KEY ?? '',
77
92
  };
@@ -93,16 +108,26 @@ export const POST: RequestHandler = async ({ request }) => {
93
108
  ...normalizedReqHeaders,
94
109
  };
95
110
 
96
- // Override / set auth header with server-side key
111
+ // Override / set auth header with server-side key.
112
+ // For Bearer-token providers, only inject the header when the key is non-empty
113
+ // to avoid sending a malformed `Authorization: Bearer ` to the upstream.
97
114
  if (provider === 'anthropic') {
98
115
  forwardHeaders['x-api-key'] = apiKeyEnv.anthropic;
99
116
  forwardHeaders['anthropic-version'] = forwardHeaders['anthropic-version'] ?? '2023-06-01';
100
117
  } else if (provider === 'google-ai') {
101
118
  forwardHeaders['x-goog-api-key'] = apiKeyEnv['google-ai'];
102
119
  } else if (provider === 'openai') {
103
- forwardHeaders.Authorization = `Bearer ${apiKeyEnv.openai}`;
120
+ if (apiKeyEnv.openai) {
121
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.openai}`;
122
+ }
104
123
  } else if (provider === 'mistral') {
105
- forwardHeaders.Authorization = `Bearer ${apiKeyEnv.mistral}`;
124
+ if (apiKeyEnv.mistral) {
125
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.mistral}`;
126
+ }
127
+ } else if (provider === 'litellm') {
128
+ if (apiKeyEnv.litellm) {
129
+ forwardHeaders.Authorization = `Bearer ${apiKeyEnv.litellm}`;
130
+ }
106
131
  }
107
132
 
108
133
  const controller = new AbortController();
@@ -238,7 +238,15 @@ function isDuplicateNotification(
238
238
  return false;
239
239
  }
240
240
 
241
- const key = `${title}|${message}|${data?.category || 'unknown'}`;
241
+ // Autopilot pushes include a stable dedupKey keyed on sessionId + top-step text
242
+ // (not the variable summary). Prefer it over the title|message|category key so
243
+ // two runs with the same top-step but different summary wording are correctly
244
+ // deduplicated.
245
+ const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
246
+ const key = dataRecord?.dedupKey
247
+ ? dataRecord.dedupKey
248
+ : `${title}|${message}|${data?.category || 'unknown'}`;
249
+
242
250
  const now = Date.now();
243
251
 
244
252
  if (notificationCache.has(key)) {
@@ -262,7 +270,10 @@ function isDuplicateNotification(
262
270
 
263
271
  /** Record a notification key in the dedup cache after successful delivery. */
264
272
  function recordNotification(title: string, message: string, data?: NotificationData): void {
265
- const key = `${title}|${message}|${data?.category || 'unknown'}`;
273
+ const dataRecord = data as (NotificationData & { dedupKey?: string }) | undefined;
274
+ const key = dataRecord?.dedupKey
275
+ ? dataRecord.dedupKey
276
+ : `${title}|${message}|${data?.category || 'unknown'}`;
266
277
  notificationCache.set(key, Date.now());
267
278
  }
268
279
 
@@ -302,6 +313,11 @@ export const POST: RequestHandler = async ({ request }) => {
302
313
  const waitForResponse =
303
314
  typeof body.waitForResponse === 'boolean' ? body.waitForResponse : false;
304
315
 
316
+ // forcePush: when true, send the push even if WS clients are connected.
317
+ // Used by the autopilot engine for high-signal notifications.
318
+ // Deduplication still applies — only the WS-client skip is bypassed.
319
+ const forcePush = typeof body.forcePush === 'boolean' ? body.forcePush : false;
320
+
305
321
  // Dynamic-options fields (PR-2/PR-3). When the caller wants to drive
306
322
  // a richer notification category — plan-mode approval, MCP
307
323
  // elicitation, AskUserQuestion choices — these arrive in the top
@@ -375,7 +391,8 @@ export const POST: RequestHandler = async ({ request }) => {
375
391
  // (for bidirectional permission polling) without actually sending a push
376
392
  // notification. This happens when WebSocket clients are connected and the
377
393
  // events channel will broadcast the permission-requested event instead.
378
- if (skipPush) {
394
+ // forcePush overrides this so high-signal autopilot pushes still reach the device.
395
+ if (skipPush && !forcePush) {
379
396
  if (waitForResponse) {
380
397
  createPendingRequest(canonicalRequestId, {
381
398
  options,
@@ -0,0 +1,99 @@
1
+ import type { SessionSummaryRecord } from '$lib/types';
2
+
3
+ import { validateAuth } from '$lib/modules/server/auth';
4
+ import { summaryStore } from '$lib/modules/server/sessions/summary-store';
5
+ import { json } from '@sveltejs/kit';
6
+
7
+ import type { RequestHandler } from './$types';
8
+
9
+ const DEFAULT_LIMIT = 50;
10
+ const MAX_LIMIT = 200;
11
+
12
+ export const POST: RequestHandler = async ({ request }) => {
13
+ const authError = validateAuth(request);
14
+ if (authError) {
15
+ return authError;
16
+ }
17
+
18
+ const rawBody: unknown = await request.json();
19
+
20
+ if (!rawBody || typeof rawBody !== 'object' || Array.isArray(rawBody)) {
21
+ return json({ error: 'Request body must be a JSON object' }, { status: 400 });
22
+ }
23
+
24
+ const body = rawBody as Record<string, unknown>;
25
+
26
+ // Validate required fields with length caps to prevent unbounded storage growth.
27
+ if (typeof body.id !== 'string' || !body.id) {
28
+ return json({ error: 'id is required and must be a non-empty string' }, { status: 400 });
29
+ }
30
+ if (body.id.length > 128) {
31
+ return json({ error: 'id must be 128 characters or fewer' }, { status: 400 });
32
+ }
33
+ if (typeof body.summary !== 'string' || !body.summary) {
34
+ return json({ error: 'summary is required and must be a non-empty string' }, { status: 400 });
35
+ }
36
+ if (body.summary.length > 1000) {
37
+ return json({ error: 'summary must be 1000 characters or fewer' }, { status: 400 });
38
+ }
39
+ if (typeof body.trigger !== 'string' || !body.trigger) {
40
+ return json({ error: 'trigger is required and must be a non-empty string' }, { status: 400 });
41
+ }
42
+ if (body.trigger.length > 64) {
43
+ return json({ error: 'trigger must be 64 characters or fewer' }, { status: 400 });
44
+ }
45
+ if (typeof body.createdAt !== 'string' || !body.createdAt) {
46
+ return json({ error: 'createdAt is required and must be a non-empty string' }, { status: 400 });
47
+ }
48
+ if (body.createdAt.length > 64 || isNaN(Date.parse(body.createdAt))) {
49
+ return json({ error: 'createdAt must be a valid ISO 8601 date string' }, { status: 400 });
50
+ }
51
+ if (typeof body.nextSteps === 'string' && body.nextSteps.length > 8192) {
52
+ return json({ error: 'nextSteps must be 8192 characters or fewer' }, { status: 400 });
53
+ }
54
+
55
+ const record: SessionSummaryRecord = {
56
+ createdAt: body.createdAt,
57
+ id: body.id,
58
+ nextSteps: typeof body.nextSteps === 'string' ? body.nextSteps : '[]',
59
+ projectName: typeof body.projectName === 'string' ? body.projectName : null,
60
+ sessionId: typeof body.sessionId === 'string' ? body.sessionId : null,
61
+ summary: body.summary,
62
+ terminalId: typeof body.terminalId === 'string' ? body.terminalId : null,
63
+ trigger: body.trigger,
64
+ };
65
+
66
+ try {
67
+ summaryStore.insert(record);
68
+ } catch (err: unknown) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ // Avoid surfacing raw SQLite error messages (may leak schema details).
71
+ const isDuplicate = typeof message === 'string' && message.includes('UNIQUE constraint failed');
72
+ if (isDuplicate) {
73
+ return json({ error: 'Record with this id already exists' }, { status: 409 });
74
+ }
75
+ return json({ error: 'Failed to persist summary' }, { status: 500 });
76
+ }
77
+
78
+ return json({ id: record.id, success: true }, { status: 201 });
79
+ };
80
+
81
+ export const GET: RequestHandler = ({ request, url }) => {
82
+ const authError = validateAuth(request);
83
+ if (authError) {
84
+ return authError;
85
+ }
86
+
87
+ const sessionId = url.searchParams.get('sessionId') ?? undefined;
88
+ const rawLimit = parseInt(url.searchParams.get('limit') || String(DEFAULT_LIMIT));
89
+ const limit =
90
+ Number.isFinite(rawLimit) && rawLimit >= 1 ? Math.min(rawLimit, MAX_LIMIT) : DEFAULT_LIMIT;
91
+
92
+ const summaries = summaryStore.listRecent(limit, sessionId);
93
+
94
+ return json({
95
+ count: summaries.length,
96
+ summaries,
97
+ timestamp: new Date().toISOString(),
98
+ });
99
+ };
@@ -1,6 +1,9 @@
1
1
  import { validateAuth } from '$lib/modules/server/auth';
2
2
  import { ptyManager } from '$lib/modules/server/terminal/pty-manager.js';
3
+ import { resolveAccess } from '$lib/modules/server/terminal/share-auth';
4
+ import { shareStore } from '$lib/modules/server/terminal/share-store';
3
5
  import { toErrorMessage } from '$lib/modules/server/utils/error';
6
+ import { closeGuests } from '$lib/modules/server/ws/guest-registry';
4
7
  import { json } from '@sveltejs/kit';
5
8
 
6
9
  import type { RequestHandler } from './$types';
@@ -21,10 +24,11 @@ function lastScrollbackLine(scrollback: string): null | string {
21
24
  }
22
25
 
23
26
  // GET /api/terminals/:id — Get terminal details by ID
27
+ // Accepts the API key (owner) or a share session token scoped to this terminal.
24
28
  export const GET: RequestHandler = ({ params, request }) => {
25
- const authError = validateAuth(request);
26
- if (authError) {
27
- return authError;
29
+ const access = resolveAccess(request, params.id);
30
+ if (!access) {
31
+ return json({ error: 'Unauthorized' }, { status: 401 });
28
32
  }
29
33
 
30
34
  try {
@@ -37,6 +41,7 @@ export const GET: RequestHandler = ({ params, request }) => {
37
41
  return json({
38
42
  args: terminal.args,
39
43
  clientCount: terminal.clients.size,
44
+ cols: terminal.cols,
40
45
  command: terminal.command,
41
46
  createdAt: terminal.createdAt.toISOString(),
42
47
  cwd: terminal.cwd,
@@ -45,10 +50,12 @@ export const GET: RequestHandler = ({ params, request }) => {
45
50
  id: terminal.id,
46
51
  lastOutput: lastScrollbackLine(terminal.scrollback),
47
52
  pid: terminal.pid,
53
+ rows: terminal.rows,
48
54
  sessionWs: `/ws/session/${terminal.id}`,
49
55
  status: terminal.status,
50
56
  timestamp: new Date().toISOString(),
51
57
  ws: `/ws/terminal/${terminal.id}`,
58
+ ...(access.level === 'guest' ? { shareMode: access.mode } : {}),
52
59
  });
53
60
  } catch (error) {
54
61
  console.error('[terminals] Failed to get terminal:', toErrorMessage(error));
@@ -72,6 +79,8 @@ export const DELETE: RequestHandler = ({ params, request }) => {
72
79
 
73
80
  if (terminal.status === 'exited') {
74
81
  ptyManager.remove(params.id);
82
+ shareStore.deleteShare(params.id);
83
+ closeGuests(params.id);
75
84
  console.log(`[terminals] Removed exited terminal ${params.id}`);
76
85
  return json({
77
86
  removed: true,
@@ -81,6 +90,8 @@ export const DELETE: RequestHandler = ({ params, request }) => {
81
90
  }
82
91
 
83
92
  ptyManager.kill(params.id);
93
+ shareStore.deleteShare(params.id);
94
+ closeGuests(params.id);
84
95
 
85
96
  console.log(`[terminals] Killed terminal ${params.id} (pid=${terminal.pid})`);
86
97
 
@@ -1,4 +1,4 @@
1
- import { validateAuth } from '$lib/modules/server/auth';
1
+ import { resolveAccess } from '$lib/modules/server/terminal/share-auth';
2
2
  import { toErrorMessage } from '$lib/modules/server/utils/error';
3
3
  import { json } from '@sveltejs/kit';
4
4
  import { mkdirSync, writeFileSync } from 'fs';
@@ -6,10 +6,14 @@ import { mkdirSync, writeFileSync } from 'fs';
6
6
  import type { RequestHandler } from './$types';
7
7
 
8
8
  // POST /api/terminals/[id]/paste-image — Write clipboard image for a terminal
9
+ // Accepts the API key (owner) or a control-mode share token for this terminal.
9
10
  export const POST: RequestHandler = async ({ params, request }) => {
10
- const authError = validateAuth(request);
11
- if (authError) {
12
- return authError;
11
+ const access = resolveAccess(request, params.id);
12
+ if (!access) {
13
+ return json({ error: 'Unauthorized' }, { status: 401 });
14
+ }
15
+ if (access.level === 'guest' && access.mode !== 'control') {
16
+ return json({ error: 'View-only access' }, { status: 403 });
13
17
  }
14
18
 
15
19
  const terminalId = params.id;
@@ -1,15 +1,19 @@
1
- import { validateAuth } from '$lib/modules/server/auth';
2
1
  import { ptyManager } from '$lib/modules/server/terminal/pty-manager.js';
2
+ import { resolveAccess } from '$lib/modules/server/terminal/share-auth';
3
3
  import { toErrorMessage } from '$lib/modules/server/utils/error';
4
4
  import { json } from '@sveltejs/kit';
5
5
 
6
6
  import type { RequestHandler } from './$types';
7
7
 
8
8
  // POST /api/terminals/:id/resize — Resize terminal
9
+ // Accepts the API key (owner) or a control-mode share token for this terminal.
9
10
  export const POST: RequestHandler = async ({ params, request }) => {
10
- const authError = validateAuth(request);
11
- if (authError) {
12
- return authError;
11
+ const access = resolveAccess(request, params.id);
12
+ if (!access) {
13
+ return json({ error: 'Unauthorized' }, { status: 401 });
14
+ }
15
+ if (access.level === 'guest' && access.mode !== 'control') {
16
+ return json({ error: 'View-only access' }, { status: 403 });
13
17
  }
14
18
 
15
19
  let body: { cols?: number; rows?: number };
@@ -0,0 +1,98 @@
1
+ // /api/terminals/[id]/share — owner management of a terminal's share.
2
+ // GET: current state. PUT: create/update. DELETE: revoke.
3
+ // All methods require the API key (owners only).
4
+
5
+ import type { ShareConfigRequest, ShareInfoResponse, ShareMode } from '$lib/types';
6
+
7
+ import { validateAuth } from '$lib/modules/server/auth';
8
+ import { ptyManager } from '$lib/modules/server/terminal/pty-manager.js';
9
+ import { hashPassword, shareStore } from '$lib/modules/server/terminal/share-store';
10
+ import { closeGuests } from '$lib/modules/server/ws/guest-registry';
11
+ import { json } from '@sveltejs/kit';
12
+
13
+ import type { RequestHandler } from './$types';
14
+
15
+ const MIN_PASSWORD_LENGTH = 6;
16
+ const MODES: ShareMode[] = ['view', 'control'];
17
+
18
+ function toInfo(terminalId: string): ShareInfoResponse {
19
+ const share = shareStore.getShare(terminalId);
20
+ if (!share) {
21
+ return { active: false, createdAt: null, mode: null, updatedAt: null };
22
+ }
23
+ return { active: true, createdAt: share.createdAt, mode: share.mode, updatedAt: share.updatedAt };
24
+ }
25
+
26
+ export const GET: RequestHandler = ({ params, request }) => {
27
+ const authError = validateAuth(request);
28
+ if (authError) {
29
+ return authError;
30
+ }
31
+ return json(toInfo(params.id));
32
+ };
33
+
34
+ export const PUT: RequestHandler = async ({ params, request }) => {
35
+ const authError = validateAuth(request);
36
+ if (authError) {
37
+ return authError;
38
+ }
39
+ if (!ptyManager.get(params.id)) {
40
+ return json({ error: 'Terminal not found' }, { status: 404 });
41
+ }
42
+
43
+ let body: ShareConfigRequest;
44
+ try {
45
+ body = (await request.json()) as ShareConfigRequest;
46
+ } catch {
47
+ return json({ error: 'Invalid JSON' }, { status: 400 });
48
+ }
49
+ if (!MODES.includes(body.mode)) {
50
+ return json({ error: "mode must be 'view' or 'control'" }, { status: 400 });
51
+ }
52
+
53
+ const existing = shareStore.getShare(params.id);
54
+ const password = typeof body.password === 'string' ? body.password : '';
55
+ if (!existing && password.length < MIN_PASSWORD_LENGTH) {
56
+ return json(
57
+ { error: `password is required (min ${String(MIN_PASSWORD_LENGTH)} chars)` },
58
+ { status: 400 }
59
+ );
60
+ }
61
+ if (password && password.length < MIN_PASSWORD_LENGTH) {
62
+ return json(
63
+ { error: `password must be at least ${String(MIN_PASSWORD_LENGTH)} chars` },
64
+ { status: 400 }
65
+ );
66
+ }
67
+
68
+ const now = Date.now();
69
+ shareStore.setShare({
70
+ createdAt: existing?.createdAt ?? now,
71
+ mode: body.mode,
72
+ // `existing` is guaranteed non-null when password is empty (validated above).
73
+ passwordHash: password ? hashPassword(password) : (existing?.passwordHash ?? ''),
74
+ terminalId: params.id,
75
+ updatedAt: now,
76
+ });
77
+
78
+ // A new password invalidates existing guest sessions; any change to the
79
+ // share forces connected guests to reconnect under the new scope.
80
+ if (password) {
81
+ shareStore.deleteSessions(params.id);
82
+ }
83
+ if (password || existing?.mode !== body.mode) {
84
+ closeGuests(params.id);
85
+ }
86
+
87
+ return json(toInfo(params.id));
88
+ };
89
+
90
+ export const DELETE: RequestHandler = ({ params, request }) => {
91
+ const authError = validateAuth(request);
92
+ if (authError) {
93
+ return authError;
94
+ }
95
+ shareStore.deleteShare(params.id);
96
+ const closed = closeGuests(params.id);
97
+ return json({ closedConnections: closed, success: true });
98
+ };
@@ -0,0 +1,81 @@
1
+ // POST /api/terminals/[id]/share/auth — exchange the share password for a
2
+ // guest session token. Public endpoint; brute-force-limited per IP+terminal.
3
+
4
+ import type { ShareAuthRequest } from '$lib/types';
5
+
6
+ import { shareStore, verifyPassword } from '$lib/modules/server/terminal/share-store';
7
+ import { json } from '@sveltejs/kit';
8
+
9
+ import type { RequestHandler } from './$types';
10
+
11
+ const RATE_LIMIT_WINDOW_MS = 60_000;
12
+ const RATE_LIMIT_MAX = 10;
13
+
14
+ /** Maps "ip:terminalId" -> attempt timestamps (epoch ms). */
15
+ const attempts = new Map<string, number[]>();
16
+
17
+ function checkRateLimit(key: string): boolean {
18
+ const now = Date.now();
19
+ const recent = (attempts.get(key) ?? []).filter((t) => t > now - RATE_LIMIT_WINDOW_MS);
20
+ attempts.set(key, recent);
21
+ if (recent.length >= RATE_LIMIT_MAX) {
22
+ return false;
23
+ }
24
+ recent.push(now);
25
+ return true;
26
+ }
27
+
28
+ // Cleanup stale rate limit entries every 5 minutes
29
+ setInterval(() => {
30
+ const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
31
+ for (const [key, timestamps] of attempts) {
32
+ const recent = timestamps.filter((t) => t > cutoff);
33
+ if (recent.length === 0) {
34
+ attempts.delete(key);
35
+ } else {
36
+ attempts.set(key, recent);
37
+ }
38
+ }
39
+ }, 300_000).unref();
40
+
41
+ export const POST: RequestHandler = async (event) => {
42
+ const { params, request } = event;
43
+
44
+ const share = shareStore.getShare(params.id);
45
+ if (!share) {
46
+ return json({ error: 'Not shared' }, { status: 404 });
47
+ }
48
+
49
+ // Behind Cloudflare Tunnel the connecting IP arrives in headers.
50
+ let ip = 'unknown';
51
+ const cfIp = request.headers.get('cf-connecting-ip');
52
+ const fwd = request.headers.get('x-forwarded-for');
53
+ if (cfIp) {
54
+ ip = cfIp;
55
+ } else if (fwd) {
56
+ ip = fwd.split(',')[0].trim();
57
+ } else {
58
+ try {
59
+ ip = event.getClientAddress();
60
+ } catch {
61
+ // Keep 'unknown' — the rate limit still applies per terminal.
62
+ }
63
+ }
64
+
65
+ if (!checkRateLimit(`${ip}:${params.id}`)) {
66
+ return json({ error: 'Too many attempts. Try again in a minute.' }, { status: 429 });
67
+ }
68
+
69
+ let body: ShareAuthRequest;
70
+ try {
71
+ body = (await request.json()) as ShareAuthRequest;
72
+ } catch {
73
+ return json({ error: 'Invalid JSON' }, { status: 400 });
74
+ }
75
+ if (typeof body.password !== 'string' || !verifyPassword(body.password, share.passwordHash)) {
76
+ return json({ error: 'Invalid password' }, { status: 401 });
77
+ }
78
+
79
+ const { expiresAt, token } = shareStore.createSession(params.id);
80
+ return json({ expiresAt, mode: share.mode, token });
81
+ };
@@ -0,0 +1,11 @@
1
+ // GET /api/terminals/[id]/share/status — public probe used by the page to
2
+ // decide whether to show the password gate. Reveals only a boolean.
3
+
4
+ import { shareStore } from '$lib/modules/server/terminal/share-store';
5
+ import { json } from '@sveltejs/kit';
6
+
7
+ import type { RequestHandler } from './$types';
8
+
9
+ export const GET: RequestHandler = ({ params }) => {
10
+ return json({ shared: shareStore.getShare(params.id) !== null });
11
+ };