@juspay/shooter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/.claude/hooks/notifier.cjs +1431 -0
  2. package/.claude/settings.json +162 -0
  3. package/README.md +515 -0
  4. package/bin/shooter.cjs +141 -0
  5. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css +1 -0
  6. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.br +0 -0
  7. package/build/client/_app/immutable/assets/0.CM9Hl6d-.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css +1 -0
  9. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.br +1 -0
  10. package/build/client/_app/immutable/assets/2.CAShZ7lQ.css.gz +0 -0
  11. package/build/client/_app/immutable/assets/3.C0uFg0IS.css +1 -0
  12. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.br +0 -0
  13. package/build/client/_app/immutable/assets/3.C0uFg0IS.css.gz +0 -0
  14. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css +1 -0
  15. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.br +0 -0
  16. package/build/client/_app/immutable/assets/4.cJuCkJKZ.css.gz +0 -0
  17. package/build/client/_app/immutable/assets/5.DRjApZQW.css +1 -0
  18. package/build/client/_app/immutable/assets/5.DRjApZQW.css.br +0 -0
  19. package/build/client/_app/immutable/assets/5.DRjApZQW.css.gz +0 -0
  20. package/build/client/_app/immutable/assets/6.AraZrY8I.css +1 -0
  21. package/build/client/_app/immutable/assets/6.AraZrY8I.css.br +0 -0
  22. package/build/client/_app/immutable/assets/6.AraZrY8I.css.gz +0 -0
  23. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css +1 -0
  24. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.br +0 -0
  25. package/build/client/_app/immutable/assets/7.BCJ1IuMx.css.gz +0 -0
  26. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css +1 -0
  27. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.br +0 -0
  28. package/build/client/_app/immutable/assets/ChatView.CsdBAOKx.css.gz +0 -0
  29. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css +1 -0
  30. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.br +0 -0
  31. package/build/client/_app/immutable/assets/markdown.B0b5w2tq.css.gz +0 -0
  32. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css +1 -0
  33. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.br +0 -0
  34. package/build/client/_app/immutable/assets/xterm.DFuMZ0ql.css.gz +0 -0
  35. package/build/client/_app/immutable/chunks/BNJphC1q.js +56 -0
  36. package/build/client/_app/immutable/chunks/BNJphC1q.js.br +0 -0
  37. package/build/client/_app/immutable/chunks/BNJphC1q.js.gz +0 -0
  38. package/build/client/_app/immutable/chunks/BTGVxaYV.js +9 -0
  39. package/build/client/_app/immutable/chunks/BTGVxaYV.js.br +0 -0
  40. package/build/client/_app/immutable/chunks/BTGVxaYV.js.gz +0 -0
  41. package/build/client/_app/immutable/chunks/BlxrFPDK.js +1 -0
  42. package/build/client/_app/immutable/chunks/BlxrFPDK.js.br +0 -0
  43. package/build/client/_app/immutable/chunks/BlxrFPDK.js.gz +0 -0
  44. package/build/client/_app/immutable/chunks/Bvk7mfPM.js +1 -0
  45. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.br +0 -0
  46. package/build/client/_app/immutable/chunks/Bvk7mfPM.js.gz +0 -0
  47. package/build/client/_app/immutable/chunks/CAokzuPQ.js +1 -0
  48. package/build/client/_app/immutable/chunks/CAokzuPQ.js.br +0 -0
  49. package/build/client/_app/immutable/chunks/CAokzuPQ.js.gz +0 -0
  50. package/build/client/_app/immutable/chunks/CGLrx-H5.js +1 -0
  51. package/build/client/_app/immutable/chunks/CGLrx-H5.js.br +0 -0
  52. package/build/client/_app/immutable/chunks/CGLrx-H5.js.gz +0 -0
  53. package/build/client/_app/immutable/chunks/CgCpWzEA.js +1 -0
  54. package/build/client/_app/immutable/chunks/CgCpWzEA.js.br +0 -0
  55. package/build/client/_app/immutable/chunks/CgCpWzEA.js.gz +0 -0
  56. package/build/client/_app/immutable/chunks/Cjwk_cGO.js +6 -0
  57. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.br +0 -0
  58. package/build/client/_app/immutable/chunks/Cjwk_cGO.js.gz +0 -0
  59. package/build/client/_app/immutable/chunks/CtQ8EED1.js +11 -0
  60. package/build/client/_app/immutable/chunks/CtQ8EED1.js.br +0 -0
  61. package/build/client/_app/immutable/chunks/CtQ8EED1.js.gz +0 -0
  62. package/build/client/_app/immutable/chunks/DERQCisl.js +1 -0
  63. package/build/client/_app/immutable/chunks/DERQCisl.js.br +0 -0
  64. package/build/client/_app/immutable/chunks/DERQCisl.js.gz +0 -0
  65. package/build/client/_app/immutable/chunks/DKrg8TQs.js +1 -0
  66. package/build/client/_app/immutable/chunks/DKrg8TQs.js.br +0 -0
  67. package/build/client/_app/immutable/chunks/DKrg8TQs.js.gz +0 -0
  68. package/build/client/_app/immutable/chunks/DLu6yJIZ.js +1 -0
  69. package/build/client/_app/immutable/chunks/DLu6yJIZ.js.br +0 -0
  70. package/build/client/_app/immutable/chunks/DLu6yJIZ.js.gz +0 -0
  71. package/build/client/_app/immutable/chunks/Dkkpz_4D.js +126 -0
  72. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.br +0 -0
  73. package/build/client/_app/immutable/chunks/Dkkpz_4D.js.gz +0 -0
  74. package/build/client/_app/immutable/chunks/DoczjQhA.js +1 -0
  75. package/build/client/_app/immutable/chunks/DoczjQhA.js.br +0 -0
  76. package/build/client/_app/immutable/chunks/DoczjQhA.js.gz +0 -0
  77. package/build/client/_app/immutable/chunks/PPVm8Dsz.js +1 -0
  78. package/build/client/_app/immutable/chunks/PPVm8Dsz.js.br +0 -0
  79. package/build/client/_app/immutable/chunks/PPVm8Dsz.js.gz +0 -0
  80. package/build/client/_app/immutable/chunks/RpcNruLP.js +2 -0
  81. package/build/client/_app/immutable/chunks/RpcNruLP.js.br +0 -0
  82. package/build/client/_app/immutable/chunks/RpcNruLP.js.gz +0 -0
  83. package/build/client/_app/immutable/chunks/a-St0Zwo.js +1 -0
  84. package/build/client/_app/immutable/chunks/a-St0Zwo.js.br +0 -0
  85. package/build/client/_app/immutable/chunks/a-St0Zwo.js.gz +0 -0
  86. package/build/client/_app/immutable/chunks/bo70OQUZ.js +1 -0
  87. package/build/client/_app/immutable/chunks/bo70OQUZ.js.br +0 -0
  88. package/build/client/_app/immutable/chunks/bo70OQUZ.js.gz +0 -0
  89. package/build/client/_app/immutable/entry/app.QvGgdvTI.js +2 -0
  90. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.br +0 -0
  91. package/build/client/_app/immutable/entry/app.QvGgdvTI.js.gz +0 -0
  92. package/build/client/_app/immutable/entry/start.BntDNRMC.js +1 -0
  93. package/build/client/_app/immutable/entry/start.BntDNRMC.js.br +0 -0
  94. package/build/client/_app/immutable/entry/start.BntDNRMC.js.gz +0 -0
  95. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js +1 -0
  96. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.br +0 -0
  97. package/build/client/_app/immutable/nodes/0.CzkdvJ7j.js.gz +0 -0
  98. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js +1 -0
  99. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.br +0 -0
  100. package/build/client/_app/immutable/nodes/1.MG1QhfrI.js.gz +0 -0
  101. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js +1 -0
  102. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.br +0 -0
  103. package/build/client/_app/immutable/nodes/2.B4MlOSh6.js.gz +0 -0
  104. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js +3 -0
  105. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.br +0 -0
  106. package/build/client/_app/immutable/nodes/3.DIwYkjDn.js.gz +0 -0
  107. package/build/client/_app/immutable/nodes/4.D-cIe70D.js +1 -0
  108. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.br +0 -0
  109. package/build/client/_app/immutable/nodes/4.D-cIe70D.js.gz +0 -0
  110. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js +1 -0
  111. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.br +0 -0
  112. package/build/client/_app/immutable/nodes/5.D7zPRe3L.js.gz +0 -0
  113. package/build/client/_app/immutable/nodes/6.BB7QE48r.js +2 -0
  114. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.br +0 -0
  115. package/build/client/_app/immutable/nodes/6.BB7QE48r.js.gz +0 -0
  116. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js +2 -0
  117. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.br +0 -0
  118. package/build/client/_app/immutable/nodes/7.D8mqsrZG.js.gz +0 -0
  119. package/build/client/_app/version.json +1 -0
  120. package/build/client/_app/version.json.br +0 -0
  121. package/build/client/_app/version.json.gz +0 -0
  122. package/build/client/app-icon.png +0 -0
  123. package/build/client/apple-touch-icon.png +0 -0
  124. package/build/client/favicon.png +0 -0
  125. package/build/client/favicon.svg +10 -0
  126. package/build/client/favicon.svg.br +0 -0
  127. package/build/client/favicon.svg.gz +0 -0
  128. package/build/client/manifest.webmanifest +1 -0
  129. package/build/client/pwa-192x192.png +0 -0
  130. package/build/client/pwa-512x512.png +0 -0
  131. package/build/client/registerSW.js +1 -0
  132. package/build/client/registerSW.js.br +0 -0
  133. package/build/client/registerSW.js.gz +0 -0
  134. package/build/client/sw.js +222 -0
  135. package/build/client/sw.js.br +0 -0
  136. package/build/client/sw.js.gz +0 -0
  137. package/build/client/workbox-5119daf5.js +3395 -0
  138. package/build/client/workbox-5119daf5.js.br +0 -0
  139. package/build/client/workbox-5119daf5.js.gz +0 -0
  140. package/build/env.js +94 -0
  141. package/build/handler.js +1494 -0
  142. package/build/index.js +345 -0
  143. package/build/pty-holder.cjs +510 -0
  144. package/build/server/chunks/0-q2IUp76Y.js +9 -0
  145. package/build/server/chunks/0-q2IUp76Y.js.map +1 -0
  146. package/build/server/chunks/1-CU50G5wZ.js +9 -0
  147. package/build/server/chunks/1-CU50G5wZ.js.map +1 -0
  148. package/build/server/chunks/2-D01t9s8T.js +9 -0
  149. package/build/server/chunks/2-D01t9s8T.js.map +1 -0
  150. package/build/server/chunks/3-5PUQ04wC.js +9 -0
  151. package/build/server/chunks/3-5PUQ04wC.js.map +1 -0
  152. package/build/server/chunks/4-e7gywnSG.js +9 -0
  153. package/build/server/chunks/4-e7gywnSG.js.map +1 -0
  154. package/build/server/chunks/5-CA1SA6KZ.js +9 -0
  155. package/build/server/chunks/5-CA1SA6KZ.js.map +1 -0
  156. package/build/server/chunks/6-71H221sV.js +9 -0
  157. package/build/server/chunks/6-71H221sV.js.map +1 -0
  158. package/build/server/chunks/7-Bo-vmdyz.js +9 -0
  159. package/build/server/chunks/7-Bo-vmdyz.js.map +1 -0
  160. package/build/server/chunks/_layout.svelte-SFHOxs74.js +132 -0
  161. package/build/server/chunks/_layout.svelte-SFHOxs74.js.map +1 -0
  162. package/build/server/chunks/_page.svelte-B4w-2wD-.js +120 -0
  163. package/build/server/chunks/_page.svelte-B4w-2wD-.js.map +1 -0
  164. package/build/server/chunks/_page.svelte-B_qAXjkh.js +213 -0
  165. package/build/server/chunks/_page.svelte-B_qAXjkh.js.map +1 -0
  166. package/build/server/chunks/_page.svelte-CsF1_TRG.js +50 -0
  167. package/build/server/chunks/_page.svelte-CsF1_TRG.js.map +1 -0
  168. package/build/server/chunks/_page.svelte-DJC6U-P0.js +68 -0
  169. package/build/server/chunks/_page.svelte-DJC6U-P0.js.map +1 -0
  170. package/build/server/chunks/_page.svelte-DQ6HBtsz.js +407 -0
  171. package/build/server/chunks/_page.svelte-DQ6HBtsz.js.map +1 -0
  172. package/build/server/chunks/_page.svelte-LbhhjP21.js +148 -0
  173. package/build/server/chunks/_page.svelte-LbhhjP21.js.map +1 -0
  174. package/build/server/chunks/_server.ts-BL2FGb5Z.js +387 -0
  175. package/build/server/chunks/_server.ts-BL2FGb5Z.js.map +1 -0
  176. package/build/server/chunks/_server.ts-BgdjBZco.js +47 -0
  177. package/build/server/chunks/_server.ts-BgdjBZco.js.map +1 -0
  178. package/build/server/chunks/_server.ts-BihKSdj_.js +59 -0
  179. package/build/server/chunks/_server.ts-BihKSdj_.js.map +1 -0
  180. package/build/server/chunks/_server.ts-BjOJsoy4.js +63 -0
  181. package/build/server/chunks/_server.ts-BjOJsoy4.js.map +1 -0
  182. package/build/server/chunks/_server.ts-C29xzfaw.js +77 -0
  183. package/build/server/chunks/_server.ts-C29xzfaw.js.map +1 -0
  184. package/build/server/chunks/_server.ts-CPa6DgIt.js +71 -0
  185. package/build/server/chunks/_server.ts-CPa6DgIt.js.map +1 -0
  186. package/build/server/chunks/_server.ts-CbDRDIoP.js +36 -0
  187. package/build/server/chunks/_server.ts-CbDRDIoP.js.map +1 -0
  188. package/build/server/chunks/_server.ts-Cl1OEWL4.js +54 -0
  189. package/build/server/chunks/_server.ts-Cl1OEWL4.js.map +1 -0
  190. package/build/server/chunks/_server.ts-ColfDHW8.js +60 -0
  191. package/build/server/chunks/_server.ts-ColfDHW8.js.map +1 -0
  192. package/build/server/chunks/_server.ts-Cv_OrRuL.js +494 -0
  193. package/build/server/chunks/_server.ts-Cv_OrRuL.js.map +1 -0
  194. package/build/server/chunks/_server.ts-D4MNi4cD.js +25 -0
  195. package/build/server/chunks/_server.ts-D4MNi4cD.js.map +1 -0
  196. package/build/server/chunks/_server.ts-DRVbgm6k.js +125 -0
  197. package/build/server/chunks/_server.ts-DRVbgm6k.js.map +1 -0
  198. package/build/server/chunks/_server.ts-DfajWaqh.js +39 -0
  199. package/build/server/chunks/_server.ts-DfajWaqh.js.map +1 -0
  200. package/build/server/chunks/_server.ts-y9-WYDMa.js +35 -0
  201. package/build/server/chunks/_server.ts-y9-WYDMa.js.map +1 -0
  202. package/build/server/chunks/auth-CEgFis71.js +32 -0
  203. package/build/server/chunks/auth-CEgFis71.js.map +1 -0
  204. package/build/server/chunks/client-CxCatAKr.js +255 -0
  205. package/build/server/chunks/client-CxCatAKr.js.map +1 -0
  206. package/build/server/chunks/error.svelte-BqdwMWdK.js +26 -0
  207. package/build/server/chunks/error.svelte-BqdwMWdK.js.map +1 -0
  208. package/build/server/chunks/exports-CJ0Q5XmL.js +4081 -0
  209. package/build/server/chunks/exports-CJ0Q5XmL.js.map +1 -0
  210. package/build/server/chunks/index2-DAxIoAO-.js +36 -0
  211. package/build/server/chunks/index2-DAxIoAO-.js.map +1 -0
  212. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js +137 -0
  213. package/build/server/chunks/jsonl-parser-dmZU_Hyu.js.map +1 -0
  214. package/build/server/chunks/library-apns-BHxLmuIx.js +104 -0
  215. package/build/server/chunks/library-apns-BHxLmuIx.js.map +1 -0
  216. package/build/server/chunks/markdown-Bxrl3cCF.js +1241 -0
  217. package/build/server/chunks/markdown-Bxrl3cCF.js.map +1 -0
  218. package/build/server/chunks/pending-requests-D8UiTw7L.js +44 -0
  219. package/build/server/chunks/pending-requests-D8UiTw7L.js.map +1 -0
  220. package/build/server/chunks/pty-manager-C0FhBiVq.js +1697 -0
  221. package/build/server/chunks/pty-manager-C0FhBiVq.js.map +1 -0
  222. package/build/server/chunks/shared-server-BDY8jh20.js +200 -0
  223. package/build/server/chunks/shared-server-BDY8jh20.js.map +1 -0
  224. package/build/server/chunks/stores-D0HorpgL.js +36 -0
  225. package/build/server/chunks/stores-D0HorpgL.js.map +1 -0
  226. package/build/server/index.js +6466 -0
  227. package/build/server/index.js.map +1 -0
  228. package/build/server/manifest.js +184 -0
  229. package/build/server/manifest.js.map +1 -0
  230. package/build/shims.js +32 -0
  231. package/package.json +94 -0
  232. package/scripts/clipboard-shims/wl-paste +48 -0
  233. package/scripts/clipboard-shims/xclip +31 -0
  234. package/scripts/install.sh +477 -0
  235. package/scripts/setup-node-pty.sh +63 -0
  236. package/scripts/setup.cjs +571 -0
  237. package/scripts/test-runner.ts +243 -0
  238. package/scripts/vercel-env-commands.sh +60 -0
  239. package/server.ts +139 -0
  240. package/src/app.css +1835 -0
  241. package/src/app.d.ts +31 -0
  242. package/src/app.html +24 -0
  243. package/src/generated/types/APN.ts +305 -0
  244. package/src/generated/types/CLI.ts +52 -0
  245. package/src/generated/types/JWT.ts +92 -0
  246. package/src/generated/types/Terminal.ts +2736 -0
  247. package/src/generated/types/index.ts +6 -0
  248. package/src/lib/assets/icons/alert-triangle.svg +5 -0
  249. package/src/lib/assets/icons/bell.svg +4 -0
  250. package/src/lib/assets/icons/check-circle.svg +4 -0
  251. package/src/lib/assets/icons/file.svg +4 -0
  252. package/src/lib/assets/icons/folder.svg +3 -0
  253. package/src/lib/assets/icons/play.svg +3 -0
  254. package/src/lib/assets/icons/refresh.svg +4 -0
  255. package/src/lib/assets/icons/settings.svg +4 -0
  256. package/src/lib/assets/icons/terminal.svg +1 -0
  257. package/src/lib/assets/icons/tool.svg +3 -0
  258. package/src/lib/assets/icons/x-circle.svg +5 -0
  259. package/src/lib/modules/client/common/Card.svelte +26 -0
  260. package/src/lib/modules/client/common/EmptyState.svelte +36 -0
  261. package/src/lib/modules/client/common/Icon.svelte +61 -0
  262. package/src/lib/modules/client/common/StatusBadge.svelte +38 -0
  263. package/src/lib/modules/client/common/cache.ts +31 -0
  264. package/src/lib/modules/client/common/config-guard.ts +18 -0
  265. package/src/lib/modules/client/common/index.ts +12 -0
  266. package/src/lib/modules/client/common/markdown.ts +23 -0
  267. package/src/lib/modules/client/common/native-bridge.ts +50 -0
  268. package/src/lib/modules/client/common/time.ts +22 -0
  269. package/src/lib/modules/client/common/tool-title.ts +28 -0
  270. package/src/lib/modules/client/terminal/ChatView.svelte +400 -0
  271. package/src/lib/modules/client/terminal/CommandPalette.svelte +60 -0
  272. package/src/lib/modules/client/terminal/ConnectionStatus.svelte +99 -0
  273. package/src/lib/modules/client/terminal/LaunchSheet.svelte +294 -0
  274. package/src/lib/modules/client/terminal/QuickKeys.svelte +71 -0
  275. package/src/lib/modules/client/terminal/ShortcutsHelp.svelte +79 -0
  276. package/src/lib/modules/client/terminal/keyboard-shortcuts.ts +70 -0
  277. package/src/lib/modules/client/terminal/xterm-wrapper.ts +243 -0
  278. package/src/lib/modules/server/apn/library-apns.ts +137 -0
  279. package/src/lib/modules/server/apn/notification-history.ts +35 -0
  280. package/src/lib/modules/server/apn/notification-sessions.ts +117 -0
  281. package/src/lib/modules/server/apn/pending-requests.ts +65 -0
  282. package/src/lib/modules/server/apn/types.ts +51 -0
  283. package/src/lib/modules/server/auth.ts +34 -0
  284. package/src/lib/modules/server/cli/index.ts +79 -0
  285. package/src/lib/modules/server/cli/runner.ts +162 -0
  286. package/src/lib/modules/server/fcm/fcm-service.ts +72 -0
  287. package/src/lib/modules/server/sessions/jsonl-parser.ts +197 -0
  288. package/src/lib/modules/server/sessions/jsonl-reader.ts +301 -0
  289. package/src/lib/modules/server/sessions/opencode-reader.ts +264 -0
  290. package/src/lib/modules/server/sessions/types.ts +53 -0
  291. package/src/lib/modules/server/terminal/holder-client.ts +273 -0
  292. package/src/lib/modules/server/terminal/opencode-watcher.ts +661 -0
  293. package/src/lib/modules/server/terminal/pty-holder.cjs +510 -0
  294. package/src/lib/modules/server/terminal/pty-manager.ts +1012 -0
  295. package/src/lib/modules/server/terminal/session-watcher.ts +320 -0
  296. package/src/lib/modules/server/terminal/terminal-store.ts +198 -0
  297. package/src/lib/modules/server/ws/events-handler.ts +73 -0
  298. package/src/lib/modules/server/ws/keepalive.ts +108 -0
  299. package/src/lib/modules/server/ws/server.ts +93 -0
  300. package/src/lib/modules/server/ws/session-handler.ts +462 -0
  301. package/src/lib/modules/server/ws/terminal-handler.ts +197 -0
  302. package/src/lib/modules/server/ws/ticket-store.ts +58 -0
  303. package/src/lib/theme.css +529 -0
  304. package/src/lib/types/config.ts +6 -0
  305. package/src/routes/+layout.svelte +218 -0
  306. package/src/routes/+page.svelte +261 -0
  307. package/src/routes/api/debug/+server.ts +33 -0
  308. package/src/routes/api/device-token/+server.ts +85 -0
  309. package/src/routes/api/health/+server.ts +100 -0
  310. package/src/routes/api/notify/+server.ts +418 -0
  311. package/src/routes/api/qr-config/+server.ts +45 -0
  312. package/src/routes/api/response/+server.ts +73 -0
  313. package/src/routes/api/sessions/+server.ts +120 -0
  314. package/src/routes/api/terminals/+server.ts +141 -0
  315. package/src/routes/api/terminals/[id]/+server.ts +75 -0
  316. package/src/routes/api/terminals/[id]/paste-image/+server.ts +61 -0
  317. package/src/routes/api/terminals/[id]/resize/+server.ts +60 -0
  318. package/src/routes/api/webhook/+server.ts +42 -0
  319. package/src/routes/api/ws-status/+server.ts +23 -0
  320. package/src/routes/api/ws-ticket/+server.ts +86 -0
  321. package/src/routes/config/+page.svelte +600 -0
  322. package/src/routes/project/+page.svelte +274 -0
  323. package/src/routes/session/[id]/+page.svelte +434 -0
  324. package/src/routes/terminals/+page.svelte +618 -0
  325. package/src/routes/terminals/[id]/+page.svelte +968 -0
  326. package/svelte.config.js +18 -0
  327. package/tsconfig.json +14 -0
@@ -0,0 +1,968 @@
1
+ <script lang="ts">
2
+ import type {
3
+ ConversationMessage,
4
+ MessagePart,
5
+ ToolUsePart,
6
+ } from '$lib/modules/server/sessions/types';
7
+
8
+ import { browser } from '$app/environment';
9
+ import { goto } from '$app/navigation';
10
+ import { page } from '$app/stores';
11
+ import { Button, Input, Pill, Tabs, Tooltip } from '@juspay/svelte-ui-components';
12
+ import { EmptyState } from '$lib/modules/client/common';
13
+ import ChatView from '$lib/modules/client/terminal/ChatView.svelte';
14
+ import ConnectionStatus from '$lib/modules/client/terminal/ConnectionStatus.svelte';
15
+ import { createShortcutManager, modLabel } from '$lib/modules/client/terminal/keyboard-shortcuts';
16
+ import CommandPalette from '$lib/modules/client/terminal/CommandPalette.svelte';
17
+ import QuickKeys from '$lib/modules/client/terminal/QuickKeys.svelte';
18
+ import ShortcutsHelp from '$lib/modules/client/terminal/ShortcutsHelp.svelte';
19
+ import { onDestroy, onMount } from 'svelte';
20
+
21
+ // ------- Interfaces -------
22
+
23
+ interface TerminalDetail {
24
+ args: string[];
25
+ command: string;
26
+ createdAt: string;
27
+ cwd: string;
28
+ exitCode: null | number;
29
+ exitedAt: null | string;
30
+ id: string;
31
+ pid: number;
32
+ sessionWs: string;
33
+ status: 'exited' | 'running';
34
+ ws: string;
35
+ }
36
+
37
+ // ------- Constants -------
38
+
39
+ const AI_COMMANDS = ['claude', 'opencode'];
40
+
41
+ // ------- Reactive state -------
42
+
43
+ let terminal = $state<null | TerminalDetail>(null);
44
+ let loading = $state(true);
45
+ let error = $state<null | string>(null);
46
+ let killing = $state(false);
47
+ let removing = $state(false);
48
+ let isActive = $state(false);
49
+ let currentCwd = $state<null | string>(null);
50
+ let viewMode = $state<'chat' | 'raw'>('raw');
51
+ let connectionStatus = $state<'connected' | 'disconnected' | 'reconnecting'>('disconnected');
52
+ let inputText = $state('');
53
+ let chatMessages = $state<ConversationMessage[]>([]);
54
+ let chatSessionEnded = $state(false);
55
+
56
+ // DOM references
57
+ let termContainer = $state<HTMLDivElement | null>(null);
58
+ let inputRef = $state<HTMLInputElement | null>(null);
59
+
60
+ // WebSocket and terminal instance refs (not reactive)
61
+ let termInstance: null | { dispose: () => void; sendInput: (data: string) => void; term: { cols: number; rows: number }; } = null;
62
+ let shortcutManager: null | { destroy: () => void } = null;
63
+ let showShortcutsHelp = $state(false);
64
+ let showCommandPalette = $state(false);
65
+ let sessionWs: null | WebSocket = null;
66
+ let disposed = false;
67
+
68
+ // ------- Derived -------
69
+
70
+ const terminalId = $derived($page.params.id);
71
+ const isAI = $derived(
72
+ terminal ? AI_COMMANDS.includes((terminal.command.split('/').pop() || '').toLowerCase()) : false
73
+ );
74
+ const isRunning = $derived(terminal?.status === 'running');
75
+ const commandName = $derived(terminal?.command.split('/').pop() || 'terminal');
76
+ const badgeLabel = $derived(isAI ? 'AI' : 'SHELL');
77
+ const badgeClass = $derived(isAI ? 'pill-badge-ai' : 'pill-badge-shell');
78
+ const tabItems = ['Raw', 'Chat'];
79
+ const tabActiveIndex = $derived(viewMode === 'raw' ? 0 : 1);
80
+ const displayCwd = $derived(shortenPath(currentCwd || terminal?.cwd || ''));
81
+ const paletteCommands = $derived.by(() => {
82
+ const cmds = [
83
+ { label: 'Go to Home', action: () => void goto('/') },
84
+ { label: 'Go to Terminals', action: () => void goto('/terminals') },
85
+ { label: 'Go to Settings', action: () => void goto('/config') },
86
+ { label: 'Show keyboard shortcuts', action: () => { showShortcutsHelp = true; } },
87
+ ];
88
+ if (isRunning) {
89
+ cmds.push({ label: 'Kill terminal', action: () => void killTerminal() });
90
+ }
91
+ return cmds;
92
+ });
93
+
94
+ // ------- Helpers -------
95
+
96
+ function shortenPath(p: string): string {
97
+ if (!p) return '';
98
+ const home = typeof window !== 'undefined' ? '' : '';
99
+ let display = p;
100
+ // Replace home prefix with ~
101
+ if (typeof navigator !== 'undefined') {
102
+ // Best effort home detection from path
103
+ const parts = p.split('/');
104
+ if (parts.length >= 3 && parts[1] === 'Users') {
105
+ display = '~/' + parts.slice(3).join('/');
106
+ } else if (parts.length >= 3 && parts[1] === 'home') {
107
+ display = '~/' + parts.slice(3).join('/');
108
+ }
109
+ }
110
+ // Show last 2 segments if too long
111
+ if (display.length > 30) {
112
+ const segs = display.split('/');
113
+ if (segs.length > 2) {
114
+ return '.../' + segs.slice(-2).join('/');
115
+ }
116
+ }
117
+ return display || home;
118
+ }
119
+
120
+ function getConfig(): null | { apiKey: string } {
121
+ try {
122
+ const saved = localStorage.getItem('shooter_config');
123
+ if (!saved) {return null;}
124
+ const parsed = JSON.parse(saved);
125
+ if (typeof parsed?.apiKey === 'string' && parsed.apiKey) {return parsed;}
126
+ return null;
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+
132
+
133
+ // ------- API calls -------
134
+
135
+ async function fetchTerminal(): Promise<void> {
136
+ if (!browser) {return;}
137
+
138
+ const config = getConfig();
139
+ if (!config) {
140
+ error = 'No configuration found. Please configure settings first.';
141
+ loading = false;
142
+ return;
143
+ }
144
+
145
+ try {
146
+ const res = await fetch(`/api/terminals/${terminalId}`, {
147
+ headers: { Authorization: `Bearer ${config.apiKey}` },
148
+ });
149
+ if (!res.ok) {
150
+ error = res.status === 404 ? 'Terminal not found' : 'Failed to load terminal';
151
+ loading = false;
152
+ return;
153
+ }
154
+ terminal = await res.json();
155
+ } catch {
156
+ error = 'Failed to connect to server';
157
+ }
158
+ loading = false;
159
+ }
160
+
161
+ async function getWsTicket(): Promise<null | string> {
162
+ const config = getConfig();
163
+ if (!config) {return null;}
164
+ try {
165
+ const res = await fetch('/api/ws-ticket', {
166
+ headers: { Authorization: `Bearer ${config.apiKey}` },
167
+ method: 'POST',
168
+ });
169
+ if (!res.ok) {return null;}
170
+ const data: { ticket: string } = await res.json();
171
+ return data.ticket;
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+
177
+ async function killTerminal(): Promise<void> {
178
+ if (!terminal || killing) {return;}
179
+ const config = getConfig();
180
+ if (!config) {return;}
181
+
182
+ killing = true;
183
+ try {
184
+ const res = await fetch(`/api/terminals/${terminalId}`, {
185
+ headers: { Authorization: `Bearer ${config.apiKey}` },
186
+ method: 'DELETE',
187
+ });
188
+ if (res.ok) {
189
+ void goto('/terminals');
190
+ } else {
191
+ killing = false;
192
+ }
193
+ } catch {
194
+ killing = false;
195
+ }
196
+ }
197
+
198
+ async function removeTerminal(): Promise<void> {
199
+ if (!terminal || removing) {return;}
200
+ const config = getConfig();
201
+ if (!config) {return;}
202
+
203
+ removing = true;
204
+ try {
205
+ const res = await fetch(`/api/terminals/${terminalId}`, {
206
+ headers: { Authorization: `Bearer ${config.apiKey}` },
207
+ method: 'DELETE',
208
+ });
209
+ if (res.ok) {
210
+ void goto('/terminals');
211
+ } else {
212
+ removing = false;
213
+ }
214
+ } catch {
215
+ removing = false;
216
+ }
217
+ }
218
+
219
+ // ------- Raw terminal (xterm.js) -------
220
+
221
+ async function initRawTerminal(): Promise<void> {
222
+ if (!termContainer || !terminal || disposed) {return;}
223
+
224
+ // Build the WebSocket URL
225
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
226
+ const wsUrl = `${protocol}//${window.location.host}${terminal.ws}`;
227
+
228
+ const getTicket = async (): Promise<string> => {
229
+ const ticket = await getWsTicket();
230
+ if (!ticket) {throw new Error('Failed to obtain WebSocket ticket');}
231
+ return ticket;
232
+ };
233
+
234
+ try {
235
+ const { createTerminal } = await import('$lib/modules/client/terminal/xterm-wrapper');
236
+
237
+ if (disposed || !termContainer) {return;}
238
+
239
+ const instance = await createTerminal({
240
+ apiKey: getConfig()?.apiKey,
241
+ container: termContainer,
242
+ fontSize: window.innerWidth < 768 ? 12 : 14,
243
+ getTicket,
244
+ terminalId: terminalId,
245
+ onActivity: (active: boolean) => {
246
+ if (!disposed) {isActive = active;}
247
+ },
248
+ onCwd: (path: string) => {
249
+ if (!disposed) {currentCwd = path;}
250
+ },
251
+ onDisconnect: () => {
252
+ if (!disposed) {connectionStatus = 'reconnecting';}
253
+ },
254
+ onExit: (code: number) => {
255
+ if (!disposed && terminal) {
256
+ terminal = { ...terminal, exitCode: code, exitedAt: new Date().toISOString(), status: 'exited' };
257
+ connectionStatus = 'disconnected';
258
+ }
259
+ },
260
+ onReconnect: () => {
261
+ if (!disposed) {connectionStatus = 'connected';}
262
+ },
263
+ wsUrl,
264
+ });
265
+
266
+ if (disposed) {
267
+ instance.dispose();
268
+ return;
269
+ }
270
+
271
+ termInstance = instance;
272
+ connectionStatus = 'connected';
273
+ } catch (err) {
274
+ console.error('Failed to initialize terminal:', err);
275
+ connectionStatus = 'disconnected';
276
+ }
277
+ }
278
+
279
+ function disposeRawTerminal(): void {
280
+ if (termInstance) {
281
+ termInstance.dispose();
282
+ termInstance = null;
283
+ }
284
+ }
285
+
286
+ // ------- Chat (session WebSocket) -------
287
+
288
+ async function connectSessionWs(): Promise<void> {
289
+ if (!terminal || disposed) {return;}
290
+
291
+ const ticket = await getWsTicket();
292
+ if (!ticket || disposed || !terminal) {
293
+ connectionStatus = 'disconnected';
294
+ return;
295
+ }
296
+
297
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
298
+ const wsUrl = `${protocol}//${window.location.host}${terminal.sessionWs}?ticket=${ticket}`;
299
+
300
+ sessionWs = new WebSocket(wsUrl);
301
+
302
+ sessionWs.onopen = () => {
303
+ if (!disposed) {
304
+ connectionStatus = 'connected';
305
+ sessionWs?.send(JSON.stringify({ sessionId: terminalId, type: 'subscribe' }));
306
+ }
307
+ };
308
+
309
+ sessionWs.onmessage = (event) => {
310
+ if (disposed) {return;}
311
+ try {
312
+ const msg = JSON.parse(event.data);
313
+ handleSessionMessage(msg);
314
+ } catch {
315
+ // Ignore malformed messages
316
+ }
317
+ };
318
+
319
+ sessionWs.onclose = () => {
320
+ if (!disposed && terminal?.status === 'running') {
321
+ connectionStatus = 'reconnecting';
322
+ // Reconnect after delay (will fetch a fresh ticket)
323
+ setTimeout(() => {
324
+ if (!disposed && terminal?.status === 'running') {void connectSessionWs();}
325
+ }, 2000);
326
+ }
327
+ };
328
+
329
+ sessionWs.onerror = () => {
330
+ if (!disposed) {connectionStatus = 'disconnected';}
331
+ };
332
+ }
333
+
334
+ function handleSessionMessage(msg: Record<string, unknown>): void {
335
+ if (msg.type === 'history') {
336
+ // History messages use `content` on the wire; convert to `parts` for ConversationMessage
337
+ const historyRaw = (msg.messages || []) as Array<{
338
+ content: MessagePart[];
339
+ id: string;
340
+ role: 'assistant' | 'system' | 'user';
341
+ timestamp: string;
342
+ }>;
343
+ chatMessages = historyRaw.map((m) => ({
344
+ id: m.id,
345
+ parts: m.content,
346
+ role: m.role,
347
+ timestamp: m.timestamp,
348
+ }));
349
+ } else if (msg.type === 'message') {
350
+ chatMessages = [
351
+ ...chatMessages,
352
+ {
353
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
354
+ parts: (msg.content as MessagePart[]) || [],
355
+ role: (msg.role as ConversationMessage['role']) || 'assistant',
356
+ timestamp: (msg.timestamp as string) || new Date().toISOString(),
357
+ },
358
+ ];
359
+ } else if (msg.type === 'tool-use') {
360
+ // Append tool use as an assistant message fragment
361
+ chatMessages = [
362
+ ...chatMessages,
363
+ {
364
+ id: `tool-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
365
+ parts: [
366
+ {
367
+ id: (msg.id as string) || `tool-${Date.now()}`,
368
+ input: (msg.input as Record<string, unknown>) || {},
369
+ toolName: msg.name as string,
370
+ type: 'tool_use',
371
+ } as ToolUsePart,
372
+ ],
373
+ role: 'assistant',
374
+ timestamp: new Date().toISOString(),
375
+ },
376
+ ];
377
+ } else if (msg.type === 'tool-result') {
378
+ chatMessages = [
379
+ ...chatMessages,
380
+ {
381
+ id: `result-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
382
+ parts: [
383
+ {
384
+ isError: msg.isError || false,
385
+ output: (msg.output as string) || '',
386
+ toolUseId: msg.id as string,
387
+ type: 'tool_result',
388
+ } as MessagePart,
389
+ ],
390
+ role: 'system',
391
+ timestamp: new Date().toISOString(),
392
+ },
393
+ ];
394
+ } else if (msg.type === 'thinking') {
395
+ // Append thinking block to the last assistant message
396
+ const thinkPart: MessagePart = {
397
+ content: (msg.text as string) || '',
398
+ type: 'thinking',
399
+ };
400
+ const lastMsg = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1] : null;
401
+ if (lastMsg?.role === 'assistant') {
402
+ chatMessages = [
403
+ ...chatMessages.slice(0, -1),
404
+ { ...lastMsg, parts: [...lastMsg.parts, thinkPart] },
405
+ ];
406
+ } else {
407
+ chatMessages = [
408
+ ...chatMessages,
409
+ {
410
+ id: `think-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
411
+ parts: [thinkPart],
412
+ role: 'assistant',
413
+ timestamp: new Date().toISOString(),
414
+ },
415
+ ];
416
+ }
417
+ } else if (msg.type === 'error') {
418
+ // Server-sent error — display as a system message
419
+ chatMessages = [
420
+ ...chatMessages,
421
+ {
422
+ id: `err-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
423
+ parts: [{ content: (msg.message as string) || 'Unknown error', type: 'text' } as MessagePart],
424
+ role: 'system',
425
+ timestamp: new Date().toISOString(),
426
+ },
427
+ ];
428
+ } else if (msg.type === 'session-end') {
429
+ chatSessionEnded = true;
430
+ if (terminal) { terminal = { ...terminal, status: 'exited' }; }
431
+ }
432
+ }
433
+
434
+ function disconnectSessionWs(): void {
435
+ if (sessionWs) {
436
+ sessionWs.onclose = null;
437
+ sessionWs.close();
438
+ sessionWs = null;
439
+ }
440
+ }
441
+
442
+ // ------- Input handling -------
443
+
444
+ function handleRawInput(): void {
445
+ if (!inputText.trim()) {return;}
446
+ // Send the text as raw PTY input with a newline via the xterm wrapper's WebSocket
447
+ if (termInstance && connectionStatus === 'connected') {
448
+ termInstance.sendInput(`${inputText }\r`);
449
+ }
450
+ inputText = '';
451
+ inputRef?.focus();
452
+ }
453
+
454
+ function handleChatSendInput(text: string): void {
455
+ if (!text.trim() || sessionWs?.readyState !== WebSocket.OPEN) { return; }
456
+ sessionWs.send(JSON.stringify({ text, type: 'send-input' }));
457
+ }
458
+
459
+ function handleChatCancel(): void {
460
+ if (sessionWs?.readyState === WebSocket.OPEN) {
461
+ sessionWs.send(JSON.stringify({ type: 'cancel' }));
462
+ }
463
+ }
464
+
465
+ function handleQuickKey(key: string): void {
466
+ if (viewMode === 'raw') {
467
+ if (termInstance && connectionStatus === 'connected') {
468
+ termInstance.sendInput(key);
469
+ }
470
+ } else if (viewMode === 'chat') {
471
+ // For chat mode, Ctrl+C sends a cancel signal
472
+ if (key === '\x03') {
473
+ handleChatCancel();
474
+ }
475
+ }
476
+ }
477
+
478
+ function handleInputKeydown(e: KeyboardEvent): void {
479
+ if (e.key === 'Enter') {
480
+ e.preventDefault();
481
+ handleRawInput();
482
+ }
483
+ }
484
+
485
+ // ------- View mode switching -------
486
+
487
+ // Track whether each mode has been initialized at least once
488
+ let rawInitialized = false;
489
+ let chatInitialized = false;
490
+
491
+ function setViewMode(mode: 'chat' | 'raw'): void {
492
+ if (mode === viewMode) {return;}
493
+ viewMode = mode;
494
+
495
+ // Lazily initialize connections on first switch — keep them alive afterwards
496
+ if (mode === 'raw' && !rawInitialized) {
497
+ requestAnimationFrame(() => {
498
+ void initRawTerminal();
499
+ });
500
+ rawInitialized = true;
501
+ } else if (mode === 'chat' && !chatInitialized) {
502
+ void connectSessionWs();
503
+ chatInitialized = true;
504
+ }
505
+
506
+ }
507
+
508
+ // ------- Retry connection -------
509
+
510
+ function handleRetry(): void {
511
+ if (viewMode === 'raw') {
512
+ disposeRawTerminal();
513
+ connectionStatus = 'reconnecting';
514
+ void initRawTerminal();
515
+ rawInitialized = true;
516
+ } else {
517
+ disconnectSessionWs();
518
+ connectionStatus = 'reconnecting';
519
+ void connectSessionWs();
520
+ chatInitialized = true;
521
+ }
522
+ }
523
+
524
+ // ------- Lifecycle -------
525
+
526
+ onMount(async () => {
527
+ await fetchTerminal();
528
+
529
+ // Set up keyboard shortcuts
530
+ shortcutManager = createShortcutManager({
531
+ onHelp: () => { showShortcutsHelp = !showShortcutsHelp; },
532
+ });
533
+
534
+ if (!terminal || error) {return;}
535
+
536
+ // Default view: Chat on mobile for AI sessions, Raw on desktop
537
+ if (isAI && window.innerWidth < 768) {
538
+ viewMode = 'chat';
539
+ } else {
540
+ viewMode = 'raw';
541
+ }
542
+
543
+ if (viewMode === 'raw') {
544
+ // Wait one tick for the DOM to render the terminal container
545
+ requestAnimationFrame(() => {
546
+ void initRawTerminal();
547
+ });
548
+ rawInitialized = true;
549
+ } else {
550
+ void connectSessionWs();
551
+ chatInitialized = true;
552
+ }
553
+ });
554
+
555
+ onDestroy(() => {
556
+ disposed = true;
557
+ disposeRawTerminal();
558
+ disconnectSessionWs();
559
+ shortcutManager?.destroy();
560
+ });
561
+ </script>
562
+
563
+ <svelte:head>
564
+ <title>{commandName} - Terminal - Shooter</title>
565
+ <meta name="description" content="Interactive terminal session" />
566
+ </svelte:head>
567
+
568
+ {#if loading}
569
+ <div class="term-page">
570
+ <div class="term-topbar">
571
+ <div class="skeleton" style="width: 120px; height: 20px;"></div>
572
+ </div>
573
+ <div class="term-body-loading">
574
+ <div class="skeleton" style="width: 100%; height: 100%;"></div>
575
+ </div>
576
+ </div>
577
+ {:else if error}
578
+ <main class="main">
579
+ <div class="session-back-row">
580
+ <a href="/terminals" class="back-link">
581
+ <span class="back-arrow">&larr;</span>
582
+ Terminals
583
+ </a>
584
+ </div>
585
+ <EmptyState icon="alert-triangle" title="Error" description={error} />
586
+ </main>
587
+ {:else if terminal}
588
+ <div class="term-page">
589
+ <!-- Top Bar -->
590
+ <div class="term-topbar">
591
+ <div class="term-topbar-left">
592
+ <a href="/terminals" class="term-back" aria-label="Back to terminals">&larr;</a>
593
+ <span class="term-command-name">{commandName}</span>
594
+ <Pill text={badgeLabel} classes={badgeClass} />
595
+ {#if isRunning}
596
+ <Tooltip text={isActive ? 'Active' : 'Idle'} position="bottom">
597
+ <span class="activity-dot {isActive ? 'activity-active' : 'activity-idle'}"></span>
598
+ </Tooltip>
599
+ {/if}
600
+ {#if displayCwd}
601
+ <Tooltip text={currentCwd || terminal?.cwd || ''} position="bottom">
602
+ <span class="term-cwd">{displayCwd}</span>
603
+ </Tooltip>
604
+ {/if}
605
+ <ConnectionStatus status={connectionStatus} onretry={handleRetry} />
606
+ </div>
607
+
608
+ <div class="term-topbar-right">
609
+ {#if isAI}
610
+ <Tabs
611
+ items={tabItems}
612
+ activeIndex={tabActiveIndex}
613
+ onchange={(index) => setViewMode(index === 0 ? 'raw' : 'chat')}
614
+ classes="term-tabs"
615
+ />
616
+ {/if}
617
+
618
+ <button
619
+ class="term-shortcuts-btn"
620
+ onclick={() => { showShortcutsHelp = !showShortcutsHelp; }}
621
+ type="button"
622
+ title="{modLabel}+/ for shortcuts"
623
+ aria-label="Keyboard shortcuts"
624
+ >?</button>
625
+
626
+ {#if isRunning}
627
+ <Button
628
+ classes="btn-danger btn-sm"
629
+ onclick={killTerminal}
630
+ disabled={killing}
631
+ showLoader={killing}
632
+ text="Kill"
633
+ />
634
+ {:else}
635
+ <Button
636
+ classes="btn-secondary btn-sm"
637
+ onclick={removeTerminal}
638
+ disabled={removing}
639
+ showLoader={removing}
640
+ text="Remove"
641
+ />
642
+ {/if}
643
+ </div>
644
+ </div>
645
+
646
+ <!-- Body: both views always rendered, toggled via CSS display to preserve WebSocket connections -->
647
+
648
+ <!-- Raw Terminal View -->
649
+ <div class="term-body" bind:this={termContainer} style:display={viewMode === 'raw' ? 'flex' : 'none'}></div>
650
+
651
+ <!-- Raw Input Bar + Quick Keys -->
652
+ {#if isRunning && viewMode === 'raw'}
653
+ <div class="term-input-area">
654
+ <QuickKeys onKey={handleQuickKey} />
655
+ <div class="term-input-bar">
656
+ <Input
657
+ bind:value={inputText}
658
+ dataType="text"
659
+ placeholder="Type command..."
660
+ classes="input-mono term-input-field"
661
+ onKeyDown={handleInputKeydown}
662
+ />
663
+ <Button
664
+ classes="btn-primary btn-send"
665
+ onclick={handleRawInput}
666
+ disabled={!inputText.trim()}
667
+ text="&crarr;"
668
+ />
669
+ </div>
670
+ </div>
671
+ {/if}
672
+
673
+ <!-- Chat View -->
674
+ <div class="term-chat-body" style:display={viewMode === 'chat' ? 'flex' : 'none'}>
675
+ <ChatView
676
+ messages={chatMessages}
677
+ connectionState={connectionStatus}
678
+ sessionEnded={chatSessionEnded}
679
+ showInput={isRunning}
680
+ onSendInput={handleChatSendInput}
681
+ onCancel={handleChatCancel}
682
+ />
683
+ </div>
684
+
685
+ <!-- Exited overlay for non-running terminals -->
686
+ {#if !isRunning}
687
+ <div class="term-exited-bar">
688
+ <span>Process exited</span>
689
+ {#if terminal.exitCode !== null}
690
+ <Pill text="code {terminal.exitCode}" classes={terminal.exitCode !== 0 ? 'pill-exit-error' : 'pill-exit-ok'} />
691
+ {/if}
692
+ </div>
693
+ {/if}
694
+ </div>
695
+ {/if}
696
+
697
+ <ShortcutsHelp open={showShortcutsHelp} onClose={() => { showShortcutsHelp = false; }} />
698
+ <CommandPalette bind:open={showCommandPalette} commands={paletteCommands} onClose={() => { showCommandPalette = false; }} />
699
+
700
+ <style>
701
+ /* ============================================
702
+ Terminal Page — full viewport layout
703
+ ============================================ */
704
+
705
+ .term-page {
706
+ display: flex;
707
+ flex-direction: column;
708
+ height: calc(100vh - var(--header-height) - 64px);
709
+ height: calc(100dvh - var(--header-height) - 64px);
710
+ overflow: hidden;
711
+ background: var(--ds-background-200);
712
+ }
713
+
714
+ /* Top bar */
715
+ .term-topbar {
716
+ display: flex;
717
+ align-items: center;
718
+ justify-content: space-between;
719
+ gap: var(--space-3);
720
+ padding: var(--space-2) var(--space-4);
721
+ background: var(--ds-background-100);
722
+ border-bottom: 1px solid var(--border);
723
+ flex-shrink: 0;
724
+ min-height: 48px;
725
+ }
726
+
727
+ .term-topbar-left {
728
+ display: flex;
729
+ align-items: center;
730
+ gap: var(--space-2);
731
+ min-width: 0;
732
+ overflow: hidden;
733
+ }
734
+
735
+ .term-topbar-right {
736
+ display: flex;
737
+ align-items: center;
738
+ gap: var(--space-3);
739
+ flex-shrink: 1;
740
+ }
741
+
742
+ /* Back button */
743
+ .term-back {
744
+ display: inline-flex;
745
+ align-items: center;
746
+ justify-content: center;
747
+ width: 44px;
748
+ height: 44px;
749
+ border-radius: var(--radius-md);
750
+ background: transparent;
751
+ color: var(--text-secondary);
752
+ text-decoration: none;
753
+ font-size: 18px;
754
+ transition:
755
+ background var(--transition-fast),
756
+ color var(--transition-fast);
757
+ flex-shrink: 0;
758
+ }
759
+
760
+ .term-back:hover {
761
+ background: var(--ds-gray-alpha-100);
762
+ color: var(--text-primary);
763
+ }
764
+
765
+ /* Command name */
766
+ .term-command-name {
767
+ font-family: var(--font-mono);
768
+ font-size: var(--text-base);
769
+ font-weight: 600;
770
+ color: var(--text-primary);
771
+ white-space: nowrap;
772
+ overflow: hidden;
773
+ text-overflow: ellipsis;
774
+ }
775
+
776
+ /* Tabs compact override for term topbar */
777
+ :global(.term-tabs) {
778
+ --tabs-bar-border-bottom: none;
779
+ --tabs-bar-background: var(--ds-gray-200);
780
+ --tabs-bar-padding: 2px;
781
+ --tabs-item-padding: 6px 12px;
782
+ --tabs-item-font-size: var(--text-xs);
783
+ --tabs-indicator-height: 0;
784
+ --tabs-active-color: var(--text-primary);
785
+ --tabs-item-color: var(--text-tertiary);
786
+ border-radius: var(--radius-md);
787
+ border: 1px solid var(--ds-gray-400);
788
+ overflow: hidden;
789
+ }
790
+
791
+ /* Shortcuts button */
792
+ .term-shortcuts-btn {
793
+ display: inline-flex;
794
+ align-items: center;
795
+ justify-content: center;
796
+ width: 28px;
797
+ height: 28px;
798
+ border-radius: var(--radius-sm);
799
+ border: 1px solid var(--border);
800
+ background: transparent;
801
+ color: var(--text-tertiary);
802
+ font-size: 14px;
803
+ font-weight: 600;
804
+ cursor: pointer;
805
+ flex-shrink: 0;
806
+ }
807
+
808
+ .term-shortcuts-btn:hover {
809
+ background: var(--component-bg-hover);
810
+ color: var(--text-primary);
811
+ }
812
+
813
+ /* CWD display */
814
+ .term-cwd {
815
+ font-family: var(--font-mono);
816
+ font-size: 11px;
817
+ color: var(--text-tertiary);
818
+ overflow: hidden;
819
+ text-overflow: ellipsis;
820
+ white-space: nowrap;
821
+ max-width: 200px;
822
+ }
823
+
824
+ /* Activity indicator */
825
+ .activity-dot {
826
+ width: 8px;
827
+ height: 8px;
828
+ border-radius: 50%;
829
+ flex-shrink: 0;
830
+ }
831
+
832
+ .activity-active {
833
+ background: #4ade80;
834
+ animation: activity-pulse 600ms ease-in-out infinite;
835
+ }
836
+
837
+ .activity-idle {
838
+ background: var(--ds-gray-600);
839
+ }
840
+
841
+ @keyframes activity-pulse {
842
+ 0%, 100% { transform: scale(1); opacity: 1; }
843
+ 50% { transform: scale(1.5); opacity: 0.7; }
844
+ }
845
+
846
+ /* ============================================
847
+ Terminal body — raw xterm.js
848
+ ============================================ */
849
+
850
+ .term-body {
851
+ flex: 1;
852
+ flex-direction: column;
853
+ min-height: 0;
854
+ overflow: hidden;
855
+ padding: var(--space-1);
856
+ background: #0a0a0f;
857
+ }
858
+
859
+ .term-body :global(.xterm) {
860
+ height: 100%;
861
+ }
862
+
863
+ .term-body :global(.xterm-viewport) {
864
+ overflow-y: auto !important;
865
+ }
866
+
867
+ .term-body-loading {
868
+ flex: 1;
869
+ padding: var(--space-4);
870
+ min-height: 200px;
871
+ }
872
+
873
+ /* ============================================
874
+ Chat body
875
+ ============================================ */
876
+
877
+ .term-chat-body {
878
+ flex: 1;
879
+ display: flex;
880
+ flex-direction: column;
881
+ min-height: 0;
882
+ overflow: hidden;
883
+ }
884
+
885
+
886
+ /* ============================================
887
+ Input area (shared between raw + chat)
888
+ ============================================ */
889
+
890
+ .term-input-area {
891
+ flex-shrink: 0;
892
+ background: var(--ds-background-100);
893
+ border-top: 1px solid var(--border);
894
+ padding-bottom: env(safe-area-inset-bottom, 0px);
895
+ }
896
+
897
+ .term-input-bar {
898
+ display: flex;
899
+ align-items: center;
900
+ gap: var(--space-2);
901
+ padding: var(--space-2) var(--space-3);
902
+ }
903
+
904
+ :global(.term-input-field) {
905
+ --input-container-margin: 0;
906
+ --input-height: 44px;
907
+ flex: 1;
908
+ }
909
+
910
+ :global(.btn-send) {
911
+ --button-height: 44px;
912
+ --button-width: 44px;
913
+ --button-padding: 0;
914
+ --button-font-size: 18px;
915
+ flex-shrink: 0;
916
+ }
917
+
918
+ /* ============================================
919
+ Exited bar
920
+ ============================================ */
921
+
922
+ .term-exited-bar {
923
+ display: flex;
924
+ align-items: center;
925
+ justify-content: center;
926
+ gap: var(--space-2);
927
+ padding: var(--space-2) var(--space-3);
928
+ padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
929
+ background: var(--ds-gray-200);
930
+ border-top: 1px solid var(--border);
931
+ font-size: var(--text-sm);
932
+ color: var(--text-tertiary);
933
+ flex-shrink: 0;
934
+ }
935
+
936
+
937
+ /* ============================================
938
+ Responsive
939
+ ============================================ */
940
+
941
+ @media (max-width: 768px) {
942
+ .term-topbar {
943
+ padding: var(--space-2) var(--space-3);
944
+ gap: var(--space-2);
945
+ }
946
+
947
+ .term-command-name {
948
+ font-size: var(--text-sm);
949
+ max-width: 100px;
950
+ }
951
+ }
952
+
953
+ @media (max-width: 480px) {
954
+ .term-topbar {
955
+ min-height: 44px;
956
+ padding: var(--space-1) var(--space-2);
957
+ }
958
+
959
+ .term-command-name {
960
+ max-width: 80px;
961
+ text-overflow: ellipsis;
962
+ }
963
+
964
+ .term-topbar-right {
965
+ flex-shrink: 1;
966
+ }
967
+ }
968
+ </style>