buninu 0.1.1

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 (435) hide show
  1. package/.bashrc +11 -0
  2. package/ARCHITECTURE.md +452 -0
  3. package/LICENSE +21 -0
  4. package/README.md +121 -0
  5. package/apps/bun/make-bunBin.sh +84 -0
  6. package/apps/jsgotty/LICENSE +23 -0
  7. package/apps/jsgotty/LICENSES/Apache-2.0-zmodem.js.txt +201 -0
  8. package/apps/jsgotty/NOTICE +21 -0
  9. package/apps/jsgotty/README.en.md +290 -0
  10. package/apps/jsgotty/README.md +292 -0
  11. package/apps/jsgotty/client.mjs +700 -0
  12. package/apps/jsgotty/env.sh +2 -0
  13. package/apps/jsgotty/gotty.js +3325 -0
  14. package/apps/jsgotty/node_modules/.package-lock.json +35 -0
  15. package/apps/jsgotty/node_modules/node-addon-api/LICENSE.md +9 -0
  16. package/apps/jsgotty/node_modules/node-addon-api/README.md +319 -0
  17. package/apps/jsgotty/node_modules/node-addon-api/common.gypi +20 -0
  18. package/apps/jsgotty/node_modules/node-addon-api/except.gypi +25 -0
  19. package/apps/jsgotty/node_modules/node-addon-api/index.js +12 -0
  20. package/apps/jsgotty/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  21. package/apps/jsgotty/node_modules/node-addon-api/napi-inl.h +6607 -0
  22. package/apps/jsgotty/node_modules/node-addon-api/napi.h +3201 -0
  23. package/apps/jsgotty/node_modules/node-addon-api/node_addon_api.gyp +32 -0
  24. package/apps/jsgotty/node_modules/node-addon-api/node_api.gyp +9 -0
  25. package/apps/jsgotty/node_modules/node-addon-api/noexcept.gypi +26 -0
  26. package/apps/jsgotty/node_modules/node-addon-api/nothing.c +0 -0
  27. package/apps/jsgotty/node_modules/node-addon-api/package-support.json +21 -0
  28. package/apps/jsgotty/node_modules/node-addon-api/package.json +480 -0
  29. package/apps/jsgotty/node_modules/node-addon-api/tools/README.md +73 -0
  30. package/apps/jsgotty/node_modules/node-addon-api/tools/check-napi.js +99 -0
  31. package/apps/jsgotty/node_modules/node-addon-api/tools/clang-format.js +71 -0
  32. package/apps/jsgotty/node_modules/node-addon-api/tools/conversion.js +301 -0
  33. package/apps/jsgotty/node_modules/node-addon-api/tools/eslint-format.js +79 -0
  34. package/apps/jsgotty/node_modules/ws/LICENSE +20 -0
  35. package/apps/jsgotty/node_modules/ws/README.md +548 -0
  36. package/apps/jsgotty/node_modules/ws/browser.js +8 -0
  37. package/apps/jsgotty/node_modules/ws/index.js +22 -0
  38. package/apps/jsgotty/node_modules/ws/lib/buffer-util.js +131 -0
  39. package/apps/jsgotty/node_modules/ws/lib/constants.js +19 -0
  40. package/apps/jsgotty/node_modules/ws/lib/event-target.js +292 -0
  41. package/apps/jsgotty/node_modules/ws/lib/extension.js +203 -0
  42. package/apps/jsgotty/node_modules/ws/lib/limiter.js +55 -0
  43. package/apps/jsgotty/node_modules/ws/lib/permessage-deflate.js +528 -0
  44. package/apps/jsgotty/node_modules/ws/lib/receiver.js +760 -0
  45. package/apps/jsgotty/node_modules/ws/lib/sender.js +607 -0
  46. package/apps/jsgotty/node_modules/ws/lib/stream.js +161 -0
  47. package/apps/jsgotty/node_modules/ws/lib/subprotocol.js +62 -0
  48. package/apps/jsgotty/node_modules/ws/lib/validation.js +152 -0
  49. package/apps/jsgotty/node_modules/ws/lib/websocket-server.js +562 -0
  50. package/apps/jsgotty/node_modules/ws/lib/websocket.js +1407 -0
  51. package/apps/jsgotty/node_modules/ws/package.json +70 -0
  52. package/apps/jsgotty/node_modules/ws/wrapper.mjs +21 -0
  53. package/apps/jsgotty/package-lock.json +46 -0
  54. package/apps/jsgotty/package.json +13 -0
  55. package/apps/jsgotty/patches/windowsPtyAgent.js +326 -0
  56. package/apps/jsgotty/patches/windowsTerminal.js +204 -0
  57. package/apps/jsgotty/rz.js +199 -0
  58. package/apps/jsgotty/single-exe/README.md +80 -0
  59. package/apps/jsgotty/single-exe/assetsHelper.js +90 -0
  60. package/apps/jsgotty/single-exe/assetsLoader.mjs +85 -0
  61. package/apps/jsgotty/single-exe/compiled.js +138 -0
  62. package/apps/jsgotty/single-exe/entry.mjs +9 -0
  63. package/apps/jsgotty/single-exe/packAssets.sh +7 -0
  64. package/apps/jsgotty/static/css/index.css +13 -0
  65. package/apps/jsgotty/static/css/kitty.css +61 -0
  66. package/apps/jsgotty/static/css/xterm.css +218 -0
  67. package/apps/jsgotty/static/css/xterm_customize.css +32 -0
  68. package/apps/jsgotty/static/favicon.ico +0 -0
  69. package/apps/jsgotty/static/help.md +125 -0
  70. package/apps/jsgotty/static/icon.svg +66 -0
  71. package/apps/jsgotty/static/icon_192.png +0 -0
  72. package/apps/jsgotty/static/index.html +164 -0
  73. package/apps/jsgotty/static/js/gotty.js +31537 -0
  74. package/apps/jsgotty/static/js/gotty.js.LICENSE.txt +7 -0
  75. package/apps/jsgotty/static/js/gotty.js.map +1 -0
  76. package/apps/jsgotty/static/js/gotty.licenses.txt +650 -0
  77. package/apps/jsgotty/static/js/kitty.js +1045 -0
  78. package/apps/jsgotty/static/js/windows_bridge.js +194 -0
  79. package/apps/jsgotty/static/js/xterm-addon-unicode11.js +2 -0
  80. package/apps/jsgotty/static/js/zmodem.js +1 -0
  81. package/apps/jsgotty/static/manifest.json +15 -0
  82. package/apps/jsgotty/sz.js +273 -0
  83. package/apps/jsgotty/tests/kitty-order.test.js +58 -0
  84. package/apps/jsgotty/viu.mjs +393 -0
  85. package/apps/jsgotty/wgotty +62 -0
  86. package/apps/jsgotty/windows-bridge-node.js +60 -0
  87. package/apps/jsgotty/zmodem-node.js +260 -0
  88. package/apps/jsmdcui/CHANGELOG.md +1048 -0
  89. package/apps/jsmdcui/LICENSE +22 -0
  90. package/apps/jsmdcui/README.md +1269 -0
  91. package/apps/jsmdcui/cdp-maze.js +141 -0
  92. package/apps/jsmdcui/clean.sh +9 -0
  93. package/apps/jsmdcui/demos/basic.jpg +0 -0
  94. package/apps/jsmdcui/demos/chinese.md +32 -0
  95. package/apps/jsmdcui/demos/event.md +170 -0
  96. package/apps/jsmdcui/demos/good.jpg +0 -0
  97. package/apps/jsmdcui/demos/image-processor.md +366 -0
  98. package/apps/jsmdcui/demos/image-processor.zh-TW.md +366 -0
  99. package/apps/jsmdcui/demos/img-change.md +73 -0
  100. package/apps/jsmdcui/demos/imgtool-zh.md +298 -0
  101. package/apps/jsmdcui/demos/imgtool.md +298 -0
  102. package/apps/jsmdcui/demos/maze.md +149 -0
  103. package/apps/jsmdcui/demos/reactive.md +40 -0
  104. package/apps/jsmdcui/demos/reader.md +513 -0
  105. package/apps/jsmdcui/demos/select.md +63 -0
  106. package/apps/jsmdcui/demos/todo-zh.md +190 -0
  107. package/apps/jsmdcui/demos/todo.md +191 -0
  108. package/apps/jsmdcui/edit +9 -0
  109. package/apps/jsmdcui/jsmdcui.sh +5 -0
  110. package/apps/jsmdcui/llm-maze.txt +94 -0
  111. package/apps/jsmdcui/package.json +19 -0
  112. package/apps/jsmdcui/project_structure.txt +35 -0
  113. package/apps/jsmdcui/runmd.mjs +1140 -0
  114. package/apps/jsmdcui/runtime/colorschemes/atom-dark.micro +33 -0
  115. package/apps/jsmdcui/runtime/colorschemes/bubblegum.micro +31 -0
  116. package/apps/jsmdcui/runtime/colorschemes/cmc-16.micro +47 -0
  117. package/apps/jsmdcui/runtime/colorschemes/cmc-tc.micro +43 -0
  118. package/apps/jsmdcui/runtime/colorschemes/darcula.micro +34 -0
  119. package/apps/jsmdcui/runtime/colorschemes/default.micro +1 -0
  120. package/apps/jsmdcui/runtime/colorschemes/dracula-tc.micro +49 -0
  121. package/apps/jsmdcui/runtime/colorschemes/dukedark-tc.micro +38 -0
  122. package/apps/jsmdcui/runtime/colorschemes/dukelight-tc.micro +38 -0
  123. package/apps/jsmdcui/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
  124. package/apps/jsmdcui/runtime/colorschemes/geany.micro +29 -0
  125. package/apps/jsmdcui/runtime/colorschemes/gotham.micro +29 -0
  126. package/apps/jsmdcui/runtime/colorschemes/gruvbox-tc.micro +29 -0
  127. package/apps/jsmdcui/runtime/colorschemes/gruvbox.micro +26 -0
  128. package/apps/jsmdcui/runtime/colorschemes/material-tc.micro +36 -0
  129. package/apps/jsmdcui/runtime/colorschemes/monokai-dark.micro +28 -0
  130. package/apps/jsmdcui/runtime/colorschemes/monokai.micro +34 -0
  131. package/apps/jsmdcui/runtime/colorschemes/one-dark.micro +39 -0
  132. package/apps/jsmdcui/runtime/colorschemes/railscast.micro +37 -0
  133. package/apps/jsmdcui/runtime/colorschemes/simple.micro +33 -0
  134. package/apps/jsmdcui/runtime/colorschemes/solarized-tc.micro +31 -0
  135. package/apps/jsmdcui/runtime/colorschemes/solarized.micro +30 -0
  136. package/apps/jsmdcui/runtime/colorschemes/sunny-day.micro +29 -0
  137. package/apps/jsmdcui/runtime/colorschemes/twilight.micro +40 -0
  138. package/apps/jsmdcui/runtime/colorschemes/zenburn.micro +30 -0
  139. package/apps/jsmdcui/runtime/help/actions.md +161 -0
  140. package/apps/jsmdcui/runtime/help/cdp.md +124 -0
  141. package/apps/jsmdcui/runtime/help/colors.md +421 -0
  142. package/apps/jsmdcui/runtime/help/commands.md +161 -0
  143. package/apps/jsmdcui/runtime/help/copypaste.md +149 -0
  144. package/apps/jsmdcui/runtime/help/defaultkeys.md +148 -0
  145. package/apps/jsmdcui/runtime/help/help.md +1269 -0
  146. package/apps/jsmdcui/runtime/help/keybindings.md +355 -0
  147. package/apps/jsmdcui/runtime/help/linter.md +90 -0
  148. package/apps/jsmdcui/runtime/help/options.md +709 -0
  149. package/apps/jsmdcui/runtime/help/plugins.md +544 -0
  150. package/apps/jsmdcui/runtime/help/tutorial.md +112 -0
  151. package/apps/jsmdcui/runtime/jsplugins/cdp/cdp-server.js +1185 -0
  152. package/apps/jsmdcui/runtime/jsplugins/cdp/cdp.js +201 -0
  153. package/apps/jsmdcui/runtime/jsplugins/chapter/chapter.js +111 -0
  154. package/apps/jsmdcui/runtime/jsplugins/diff/diff.js +46 -0
  155. package/apps/jsmdcui/runtime/jsplugins/example/example.js +117 -0
  156. package/apps/jsmdcui/runtime/jsplugins/linter/linter.js +281 -0
  157. package/apps/jsmdcui/runtime/plugins/autoclose/autoclose.lua +75 -0
  158. package/apps/jsmdcui/runtime/plugins/ftoptions/ftoptions.lua +17 -0
  159. package/apps/jsmdcui/runtime/plugins/literate/README.md +5 -0
  160. package/apps/jsmdcui/runtime/plugins/literate/literate.lua +55 -0
  161. package/apps/jsmdcui/runtime/plugins/status/help/status.md +21 -0
  162. package/apps/jsmdcui/runtime/plugins/status/status.lua +62 -0
  163. package/apps/jsmdcui/runtime/syntax/LICENSE +22 -0
  164. package/apps/jsmdcui/runtime/syntax/PowerShell.yaml +128 -0
  165. package/apps/jsmdcui/runtime/syntax/README.md +63 -0
  166. package/apps/jsmdcui/runtime/syntax/ada.yaml +43 -0
  167. package/apps/jsmdcui/runtime/syntax/apacheconf.yaml +59 -0
  168. package/apps/jsmdcui/runtime/syntax/arduino.yaml +101 -0
  169. package/apps/jsmdcui/runtime/syntax/asciidoc.yaml +51 -0
  170. package/apps/jsmdcui/runtime/syntax/asm.yaml +123 -0
  171. package/apps/jsmdcui/runtime/syntax/ats.yaml +99 -0
  172. package/apps/jsmdcui/runtime/syntax/awk.yaml +44 -0
  173. package/apps/jsmdcui/runtime/syntax/b.yaml +87 -0
  174. package/apps/jsmdcui/runtime/syntax/bat.yaml +57 -0
  175. package/apps/jsmdcui/runtime/syntax/c.yaml +60 -0
  176. package/apps/jsmdcui/runtime/syntax/caddyfile.yaml +23 -0
  177. package/apps/jsmdcui/runtime/syntax/cake.yaml +7 -0
  178. package/apps/jsmdcui/runtime/syntax/clojure.yaml +38 -0
  179. package/apps/jsmdcui/runtime/syntax/cmake.yaml +42 -0
  180. package/apps/jsmdcui/runtime/syntax/coffeescript.yaml +56 -0
  181. package/apps/jsmdcui/runtime/syntax/colortest.yaml +19 -0
  182. package/apps/jsmdcui/runtime/syntax/conky.yaml +17 -0
  183. package/apps/jsmdcui/runtime/syntax/cpp.yaml +91 -0
  184. package/apps/jsmdcui/runtime/syntax/crontab.yaml +36 -0
  185. package/apps/jsmdcui/runtime/syntax/crystal.yaml +72 -0
  186. package/apps/jsmdcui/runtime/syntax/csharp.yaml +52 -0
  187. package/apps/jsmdcui/runtime/syntax/css.yaml +44 -0
  188. package/apps/jsmdcui/runtime/syntax/csx.yaml +8 -0
  189. package/apps/jsmdcui/runtime/syntax/cuda.yaml +68 -0
  190. package/apps/jsmdcui/runtime/syntax/cython.yaml +52 -0
  191. package/apps/jsmdcui/runtime/syntax/d.yaml +121 -0
  192. package/apps/jsmdcui/runtime/syntax/dart.yaml +46 -0
  193. package/apps/jsmdcui/runtime/syntax/default.yaml +10 -0
  194. package/apps/jsmdcui/runtime/syntax/dockerfile.yaml +36 -0
  195. package/apps/jsmdcui/runtime/syntax/dot.yaml +29 -0
  196. package/apps/jsmdcui/runtime/syntax/elixir.yaml +30 -0
  197. package/apps/jsmdcui/runtime/syntax/elm.yaml +38 -0
  198. package/apps/jsmdcui/runtime/syntax/erb.yaml +42 -0
  199. package/apps/jsmdcui/runtime/syntax/erlang.yaml +45 -0
  200. package/apps/jsmdcui/runtime/syntax/fish.yaml +48 -0
  201. package/apps/jsmdcui/runtime/syntax/forth.yaml +34 -0
  202. package/apps/jsmdcui/runtime/syntax/fortran.yaml +64 -0
  203. package/apps/jsmdcui/runtime/syntax/freebsd-kernel.yaml +14 -0
  204. package/apps/jsmdcui/runtime/syntax/fsharp.yaml +48 -0
  205. package/apps/jsmdcui/runtime/syntax/gdscript.yaml +61 -0
  206. package/apps/jsmdcui/runtime/syntax/gemini.yaml +19 -0
  207. package/apps/jsmdcui/runtime/syntax/gentoo-ebuild.yaml +48 -0
  208. package/apps/jsmdcui/runtime/syntax/gentoo-etc-portage.yaml +23 -0
  209. package/apps/jsmdcui/runtime/syntax/git-commit.yaml +35 -0
  210. package/apps/jsmdcui/runtime/syntax/git-config.yaml +14 -0
  211. package/apps/jsmdcui/runtime/syntax/git-rebase-todo.yaml +19 -0
  212. package/apps/jsmdcui/runtime/syntax/gleam.yaml +69 -0
  213. package/apps/jsmdcui/runtime/syntax/glsl.yaml +26 -0
  214. package/apps/jsmdcui/runtime/syntax/gnuplot.yaml +15 -0
  215. package/apps/jsmdcui/runtime/syntax/go.yaml +62 -0
  216. package/apps/jsmdcui/runtime/syntax/godoc.yaml +17 -0
  217. package/apps/jsmdcui/runtime/syntax/golo.yaml +73 -0
  218. package/apps/jsmdcui/runtime/syntax/gomod.yaml +31 -0
  219. package/apps/jsmdcui/runtime/syntax/graphql.yaml +47 -0
  220. package/apps/jsmdcui/runtime/syntax/groff.yaml +30 -0
  221. package/apps/jsmdcui/runtime/syntax/groovy.yaml +111 -0
  222. package/apps/jsmdcui/runtime/syntax/haml.yaml +16 -0
  223. package/apps/jsmdcui/runtime/syntax/hare.yaml +52 -0
  224. package/apps/jsmdcui/runtime/syntax/haskell.yaml +52 -0
  225. package/apps/jsmdcui/runtime/syntax/hc.yaml +52 -0
  226. package/apps/jsmdcui/runtime/syntax/html.yaml +70 -0
  227. package/apps/jsmdcui/runtime/syntax/html4.yaml +25 -0
  228. package/apps/jsmdcui/runtime/syntax/html5.yaml +25 -0
  229. package/apps/jsmdcui/runtime/syntax/ini.yaml +23 -0
  230. package/apps/jsmdcui/runtime/syntax/inputrc.yaml +14 -0
  231. package/apps/jsmdcui/runtime/syntax/java.yaml +37 -0
  232. package/apps/jsmdcui/runtime/syntax/javascript.yaml +124 -0
  233. package/apps/jsmdcui/runtime/syntax/jinja2.yaml +19 -0
  234. package/apps/jsmdcui/runtime/syntax/json.yaml +39 -0
  235. package/apps/jsmdcui/runtime/syntax/jsonnet.yaml +92 -0
  236. package/apps/jsmdcui/runtime/syntax/julia.yaml +57 -0
  237. package/apps/jsmdcui/runtime/syntax/justfile.yaml +40 -0
  238. package/apps/jsmdcui/runtime/syntax/keymap.yaml +27 -0
  239. package/apps/jsmdcui/runtime/syntax/kickstart.yaml +16 -0
  240. package/apps/jsmdcui/runtime/syntax/kotlin.yaml +66 -0
  241. package/apps/jsmdcui/runtime/syntax/kvlang.yaml +67 -0
  242. package/apps/jsmdcui/runtime/syntax/ledger.yaml +14 -0
  243. package/apps/jsmdcui/runtime/syntax/lfe.yaml +17 -0
  244. package/apps/jsmdcui/runtime/syntax/lilypond.yaml +26 -0
  245. package/apps/jsmdcui/runtime/syntax/lisp.yaml +17 -0
  246. package/apps/jsmdcui/runtime/syntax/log.yaml +92 -0
  247. package/apps/jsmdcui/runtime/syntax/lua.yaml +111 -0
  248. package/apps/jsmdcui/runtime/syntax/mail.yaml +25 -0
  249. package/apps/jsmdcui/runtime/syntax/makefile.yaml +38 -0
  250. package/apps/jsmdcui/runtime/syntax/man.yaml +12 -0
  251. package/apps/jsmdcui/runtime/syntax/markdown.yaml +64 -0
  252. package/apps/jsmdcui/runtime/syntax/mc.yaml +23 -0
  253. package/apps/jsmdcui/runtime/syntax/meson.yaml +51 -0
  254. package/apps/jsmdcui/runtime/syntax/micro.yaml +34 -0
  255. package/apps/jsmdcui/runtime/syntax/mpdconf.yaml +13 -0
  256. package/apps/jsmdcui/runtime/syntax/msbuild.yaml +6 -0
  257. package/apps/jsmdcui/runtime/syntax/nanorc.yaml +16 -0
  258. package/apps/jsmdcui/runtime/syntax/nftables.yaml +30 -0
  259. package/apps/jsmdcui/runtime/syntax/nginx.yaml +22 -0
  260. package/apps/jsmdcui/runtime/syntax/nim.yaml +27 -0
  261. package/apps/jsmdcui/runtime/syntax/nix.yaml +32 -0
  262. package/apps/jsmdcui/runtime/syntax/nu.yaml +114 -0
  263. package/apps/jsmdcui/runtime/syntax/objc.yaml +60 -0
  264. package/apps/jsmdcui/runtime/syntax/ocaml.yaml +43 -0
  265. package/apps/jsmdcui/runtime/syntax/octave.yaml +83 -0
  266. package/apps/jsmdcui/runtime/syntax/odin.yaml +64 -0
  267. package/apps/jsmdcui/runtime/syntax/pascal.yaml +45 -0
  268. package/apps/jsmdcui/runtime/syntax/patch.yaml +14 -0
  269. package/apps/jsmdcui/runtime/syntax/peg.yaml +16 -0
  270. package/apps/jsmdcui/runtime/syntax/perl.yaml +58 -0
  271. package/apps/jsmdcui/runtime/syntax/php.yaml +60 -0
  272. package/apps/jsmdcui/runtime/syntax/pkg-config.yaml +12 -0
  273. package/apps/jsmdcui/runtime/syntax/po.yaml +12 -0
  274. package/apps/jsmdcui/runtime/syntax/pony.yaml +37 -0
  275. package/apps/jsmdcui/runtime/syntax/pov.yaml +21 -0
  276. package/apps/jsmdcui/runtime/syntax/privoxy-action.yaml +14 -0
  277. package/apps/jsmdcui/runtime/syntax/privoxy-config.yaml +10 -0
  278. package/apps/jsmdcui/runtime/syntax/privoxy-filter.yaml +12 -0
  279. package/apps/jsmdcui/runtime/syntax/proto.yaml +40 -0
  280. package/apps/jsmdcui/runtime/syntax/prql.yaml +84 -0
  281. package/apps/jsmdcui/runtime/syntax/puppet.yaml +22 -0
  282. package/apps/jsmdcui/runtime/syntax/python2.yaml +60 -0
  283. package/apps/jsmdcui/runtime/syntax/python3.yaml +80 -0
  284. package/apps/jsmdcui/runtime/syntax/r.yaml +32 -0
  285. package/apps/jsmdcui/runtime/syntax/raku.yaml +42 -0
  286. package/apps/jsmdcui/runtime/syntax/reST.yaml +18 -0
  287. package/apps/jsmdcui/runtime/syntax/renpy.yaml +15 -0
  288. package/apps/jsmdcui/runtime/syntax/rpmspec.yaml +43 -0
  289. package/apps/jsmdcui/runtime/syntax/ruby.yaml +73 -0
  290. package/apps/jsmdcui/runtime/syntax/rust.yaml +78 -0
  291. package/apps/jsmdcui/runtime/syntax/sage.yaml +60 -0
  292. package/apps/jsmdcui/runtime/syntax/scad.yaml +53 -0
  293. package/apps/jsmdcui/runtime/syntax/scala.yaml +33 -0
  294. package/apps/jsmdcui/runtime/syntax/sed.yaml +13 -0
  295. package/apps/jsmdcui/runtime/syntax/sh.yaml +69 -0
  296. package/apps/jsmdcui/runtime/syntax/sls.yaml +15 -0
  297. package/apps/jsmdcui/runtime/syntax/smalltalk.yaml +55 -0
  298. package/apps/jsmdcui/runtime/syntax/solidity.yaml +41 -0
  299. package/apps/jsmdcui/runtime/syntax/sql.yaml +35 -0
  300. package/apps/jsmdcui/runtime/syntax/stata.yaml +67 -0
  301. package/apps/jsmdcui/runtime/syntax/svelte.yaml +27 -0
  302. package/apps/jsmdcui/runtime/syntax/swift.yaml +103 -0
  303. package/apps/jsmdcui/runtime/syntax/systemd.yaml +16 -0
  304. package/apps/jsmdcui/runtime/syntax/tcl.yaml +18 -0
  305. package/apps/jsmdcui/runtime/syntax/terraform.yaml +87 -0
  306. package/apps/jsmdcui/runtime/syntax/tex.yaml +32 -0
  307. package/apps/jsmdcui/runtime/syntax/toml.yaml +56 -0
  308. package/apps/jsmdcui/runtime/syntax/twig.yaml +55 -0
  309. package/apps/jsmdcui/runtime/syntax/typescript.yaml +49 -0
  310. package/apps/jsmdcui/runtime/syntax/v.yaml +80 -0
  311. package/apps/jsmdcui/runtime/syntax/vala.yaml +26 -0
  312. package/apps/jsmdcui/runtime/syntax/verilog.yaml +60 -0
  313. package/apps/jsmdcui/runtime/syntax/vhdl.yaml +37 -0
  314. package/apps/jsmdcui/runtime/syntax/vi.yaml +31 -0
  315. package/apps/jsmdcui/runtime/syntax/vue.yaml +64 -0
  316. package/apps/jsmdcui/runtime/syntax/xml.yaml +37 -0
  317. package/apps/jsmdcui/runtime/syntax/xresources.yaml +14 -0
  318. package/apps/jsmdcui/runtime/syntax/yaml.yaml +34 -0
  319. package/apps/jsmdcui/runtime/syntax/yum.yaml +12 -0
  320. package/apps/jsmdcui/runtime/syntax/zig.yaml +52 -0
  321. package/apps/jsmdcui/runtime/syntax/zscript.yaml +72 -0
  322. package/apps/jsmdcui/runtime/syntax/zsh.yaml +52 -0
  323. package/apps/jsmdcui/single-exe/README.md +473 -0
  324. package/apps/jsmdcui/single-exe/assetsHelper.js +191 -0
  325. package/apps/jsmdcui/single-exe/assetsLoader.mjs +85 -0
  326. package/apps/jsmdcui/single-exe/compiled.js +212 -0
  327. package/apps/jsmdcui/single-exe/entry.mjs +10 -0
  328. package/apps/jsmdcui/single-exe/packAssets.sh +7 -0
  329. package/apps/jsmdcui/src/MDCUI_DEFAULT_EDIT +0 -0
  330. package/apps/jsmdcui/src/buffer/backup.js +165 -0
  331. package/apps/jsmdcui/src/buffer/buffer.js +126 -0
  332. package/apps/jsmdcui/src/buffer/fixed3-codec.js +140 -0
  333. package/apps/jsmdcui/src/buffer/loc.js +38 -0
  334. package/apps/jsmdcui/src/buffer/message.js +29 -0
  335. package/apps/jsmdcui/src/build-args.js +40 -0
  336. package/apps/jsmdcui/src/config/clean.js +172 -0
  337. package/apps/jsmdcui/src/config/colorscheme.js +99 -0
  338. package/apps/jsmdcui/src/config/config.js +125 -0
  339. package/apps/jsmdcui/src/config/defaults.js +109 -0
  340. package/apps/jsmdcui/src/cui/fence-events.mjs +144 -0
  341. package/apps/jsmdcui/src/cui/heading-ids.mjs +1568 -0
  342. package/apps/jsmdcui/src/cui/id-collision.mjs +111 -0
  343. package/apps/jsmdcui/src/cui/identity.mjs +26 -0
  344. package/apps/jsmdcui/src/cui/kitty-debug.mjs +25 -0
  345. package/apps/jsmdcui/src/cui/kitty-images.mjs +280 -0
  346. package/apps/jsmdcui/src/cui/kitty-mode.mjs +6 -0
  347. package/apps/jsmdcui/src/cui/rerender-queue.mjs +66 -0
  348. package/apps/jsmdcui/src/cui/rpc.mjs +1525 -0
  349. package/apps/jsmdcui/src/cui/server.mjs +128 -0
  350. package/apps/jsmdcui/src/cui/table-render.mjs +286 -0
  351. package/apps/jsmdcui/src/cui/table-row-edit.mjs +226 -0
  352. package/apps/jsmdcui/src/cui/task-checkbox.mjs +60 -0
  353. package/apps/jsmdcui/src/cui/template-components.mjs +253 -0
  354. package/apps/jsmdcui/src/cui/text-block-resize.mjs +177 -0
  355. package/apps/jsmdcui/src/cui/tui-links.mjs +560 -0
  356. package/apps/jsmdcui/src/display/ansi-style.js +60 -0
  357. package/apps/jsmdcui/src/highlight/highlighter.js +243 -0
  358. package/apps/jsmdcui/src/highlight/parser.js +225 -0
  359. package/apps/jsmdcui/src/index.js +9573 -0
  360. package/apps/jsmdcui/src/lua/engine.js +68 -0
  361. package/apps/jsmdcui/src/platform/archive.js +50 -0
  362. package/apps/jsmdcui/src/platform/clipboard.js +278 -0
  363. package/apps/jsmdcui/src/platform/commands.js +221 -0
  364. package/apps/jsmdcui/src/platform/terminal.js +5 -0
  365. package/apps/jsmdcui/src/plugins/js-bridge.js +3377 -0
  366. package/apps/jsmdcui/src/plugins/manager.js +674 -0
  367. package/apps/jsmdcui/src/runtime/encodings.js +10 -0
  368. package/apps/jsmdcui/src/runtime/registry.js +134 -0
  369. package/apps/jsmdcui/src/screen/cell-buffer.js +81 -0
  370. package/apps/jsmdcui/src/screen/events.js +263 -0
  371. package/apps/jsmdcui/src/screen/screen.js +249 -0
  372. package/apps/jsmdcui/src/screen/vt100.js +571 -0
  373. package/apps/jsmdcui/src/shell/shell.js +70 -0
  374. package/apps/jsmdcui/testapp.md +78 -0
  375. package/apps/jsmdcui/tests/assets-helper.test.js +150 -0
  376. package/apps/jsmdcui/tests/backup.test.js +158 -0
  377. package/apps/jsmdcui/tests/cat-markdown.test.js +139 -0
  378. package/apps/jsmdcui/tests/cmphex3b64.js +95 -0
  379. package/apps/jsmdcui/tests/compiled-runtime.test.js +139 -0
  380. package/apps/jsmdcui/tests/config-settings.test.js +52 -0
  381. package/apps/jsmdcui/tests/demo.test.js +447 -0
  382. package/apps/jsmdcui/tests/docs-sync.test.js +14 -0
  383. package/apps/jsmdcui/tests/fence-events.test.js +1293 -0
  384. package/apps/jsmdcui/tests/heading-ids.test.js +559 -0
  385. package/apps/jsmdcui/tests/heading-list-selector.test.js +2393 -0
  386. package/apps/jsmdcui/tests/id-collision.test.js +514 -0
  387. package/apps/jsmdcui/tests/js-plugin-prompts.test.js +110 -0
  388. package/apps/jsmdcui/tests/key-event.md +10 -0
  389. package/apps/jsmdcui/tests/kitty-demo.md +184 -0
  390. package/apps/jsmdcui/tests/kitty-images.test.js +288 -0
  391. package/apps/jsmdcui/tests/kitty-mode.test.js +14 -0
  392. package/apps/jsmdcui/tests/platform-commands.test.js +18 -0
  393. package/apps/jsmdcui/tests/platform-terminal.test.js +8 -0
  394. package/apps/jsmdcui/tests/pty-demo.js +492 -0
  395. package/apps/jsmdcui/tests/rerender-queue.test.js +130 -0
  396. package/apps/jsmdcui/tests/table-render.test.js +150 -0
  397. package/apps/jsmdcui/tests/table-row-edit.test.js +138 -0
  398. package/apps/jsmdcui/tests/task-checkbox.test.js +33 -0
  399. package/apps/jsmdcui/tests/template-components.test.js +422 -0
  400. package/apps/jsmdcui/tests/text-block-resize.test.js +222 -0
  401. package/apps/jsmdcui/tests/textarea.md +8 -0
  402. package/apps/jsmdcui/tests/tui-links.test.js +298 -0
  403. package/apps/jsmdcui/tests/wui-responsive-images.test.js +60 -0
  404. package/apps/jsmdcui/tests/wui.test.js +108 -0
  405. package/apps/jsmdcui/tests/wv-client.js +96 -0
  406. package/apps/jsmdcui/tui +7 -0
  407. package/apps/jsmdcui/wui +9 -0
  408. package/apps/musl-la/LICENSES/Apache-2.0_LLVM_Exceptions_license.txt +311 -0
  409. package/apps/musl-la/LICENSES/Apache-2.0_license.txt +177 -0
  410. package/apps/musl-la/LICENSES/BSD-3-Clause_license.txt +43 -0
  411. package/apps/musl-la/LICENSES/GPL-3.0.txt +674 -0
  412. package/apps/musl-la/LICENSES/LGPL-3.0.txt +165 -0
  413. package/apps/musl-la/LICENSES/MIT.txt +21 -0
  414. package/apps/musl-la/LICENSES/curl_license.txt +22 -0
  415. package/apps/musl-la/LICENSES/gcc_runtime_exception.txt +73 -0
  416. package/apps/musl-la/LICENSES/zlib_license.txt +22 -0
  417. package/apps/musl-la/LICENSE_musl.txt +193 -0
  418. package/apps/musl-la/NOTICE +56 -0
  419. package/apps/musl-la/ldmusl +0 -0
  420. package/apps/musl-la/libgcc_s.so.1 +0 -0
  421. package/apps/musl-la/libstdc++.so.6 +0 -0
  422. package/apps/musl-la/musl-la.sh +5 -0
  423. package/bin/bun.sh +57 -0
  424. package/bin/glow.sh +5 -0
  425. package/bin/init.js +395 -0
  426. package/bin/jmi.sh +5 -0
  427. package/bin/jsgotty.sh +5 -0
  428. package/bin/libsh-loader.c +135 -0
  429. package/bin/libsh-loader.so +0 -0
  430. package/bin/multicall.sh +90 -0
  431. package/bin/shc.js +5 -0
  432. package/bin/switch_to_android.sh +9 -0
  433. package/bin/switch_to_linux.sh +7 -0
  434. package/icon.png +0 -0
  435. package/package.json +31 -0
@@ -0,0 +1,3325 @@
1
+ #!/usr/bin/env bun
2
+
3
+ //Object.defineProperty(process,"platform",{value:'linux'})
4
+
5
+ const fs = require("fs");
6
+ const http = require("http");
7
+ const https = require("https");
8
+ const net = require("net");
9
+ const os = require("os");
10
+ const path = require("path");
11
+ const crypto = require("crypto");
12
+ const zlib = require("zlib");
13
+ const { constants: fsConstants } = require("fs");
14
+ const WebSocket = require("ws");
15
+ const { WebSocketServer } = WebSocket;
16
+
17
+ const MSG_INPUT = "1";
18
+ const MSG_PING = "2";
19
+ const MSG_RESIZE_TERMINAL = "3";
20
+ const MSG_SET_ENCODING = "4";
21
+ const MSG_INPUT_BINARY = "8";
22
+
23
+ const MSG_OUTPUT = "1";
24
+ const MSG_PONG = "2";
25
+ const MSG_SET_WINDOW_TITLE = "3";
26
+ const MSG_SET_PREFERENCES = "4";
27
+ const MSG_SET_RECONNECT = "5";
28
+ const MSG_SET_BUFFER_SIZE = "6";
29
+ const MSG_KITTY_GRAPHICS = "7";
30
+ const MSG_WINDOWS_BRIDGE = "9";
31
+ const MSG_SET_ROLE = "a";
32
+ const MSG_CONTROL_RESULT = "b";
33
+ const MSG_ZMODEM_MODE = "z";
34
+ const MSG_ZMODEM_OUTPUT = "y";
35
+ const MSG_ZMODEM_ACK = "Y";
36
+ const MSG_KITTY_DEBUG = "K";
37
+ const KITTY_TRACE = /^(1|true|yes|on)$/i.test(String(process.env.JSGOTTY_KITTY_DEBUG || ""));
38
+ const WINDOWS_BRIDGE_TRACE = process.env.WINDOWS_BRIDGE_TRACE === "1";
39
+ const WINDOWS_BRIDGE_PREFIX = Buffer.from("@@GOTTYCTL:", "ascii");
40
+ const WINDOWS_BRIDGE_SUFFIX = Buffer.from("@@", "ascii");
41
+ const KITTY_PLACEMENT_LOG = path.resolve(__dirname, "kitty-placement.log");
42
+
43
+ function kittyPlacementLog(stage, details = {}) {
44
+ if (!KITTY_TRACE) {
45
+ return;
46
+ }
47
+ try {
48
+ fs.appendFileSync(KITTY_PLACEMENT_LOG, JSON.stringify({
49
+ time: new Date().toISOString(),
50
+ pid: process.pid,
51
+ stage,
52
+ ...details,
53
+ }) + "\n");
54
+ } catch (error) {
55
+ // Placement diagnostics must never affect terminal output.
56
+ }
57
+ }
58
+
59
+ function cursorControls(buffer) {
60
+ const text = Buffer.from(buffer).toString("utf8");
61
+ return Array.from(text.matchAll(/\u001b\[([0-9;?]*)([A-Za-z])/g), (match) => ({
62
+ params: match[1],
63
+ final: match[2],
64
+ })).filter((entry) => "ABCDEFGHfJKhl".includes(entry.final));
65
+ }
66
+
67
+ const DEFAULTS = {
68
+ address: "0.0.0.0",
69
+ port: "8080",
70
+ path: "/",
71
+ permitWrite: false,
72
+ credential: "",
73
+ randomUrl: false,
74
+ randomUrlLength: 16,
75
+ tls: false,
76
+ tlsCrt: "~/.gotty.crt",
77
+ tlsKey: "~/.gotty.key",
78
+ index: "",
79
+ titleFormat: "{{ .command }}@{{ .hostname }}",
80
+ reconnect: false,
81
+ reconnectTime: -1,
82
+ maxConnection: -1,
83
+ once: false,
84
+ timeout: -1,
85
+ permitArguments: false,
86
+ passHeaders: false,
87
+ width: 0,
88
+ height: 0,
89
+ wsOrigin: "",
90
+ wsQueryArgs: "",
91
+ webgl: false,
92
+ quiet: false,
93
+ closeSignal: "SIGINT",
94
+ closeTimeout: -1
95
+ };
96
+
97
+ const compiledHelperPromise = import("./single-exe/compiled.js").catch(() => null);
98
+ const assetsHelperPromise = import("./single-exe/assetsHelper.js").catch(() => null);
99
+
100
+ let RESOURCE_ROOT = __dirname;
101
+ let STATIC_ROOT = path.resolve(RESOURCE_ROOT, "./static");
102
+ let DEFAULT_INDEX = path.join(STATIC_ROOT, "index.html");
103
+ let DEFAULT_MANIFEST = path.join(STATIC_ROOT, "manifest.json");
104
+ let DEFAULT_HELP = path.join(STATIC_ROOT, "help.md");
105
+ let DEFAULT_README = path.resolve(RESOURCE_ROOT, "./README.md");
106
+ let DEFAULT_README_EN = path.resolve(RESOURCE_ROOT, "./README.en.md");
107
+ let helperAssetPath = null;
108
+ let helperReadInternalAssetText = null;
109
+ let helperReadInternalAssetBytes = null;
110
+
111
+ function configureResourceRoot(compiledHelper) {
112
+ RESOURCE_ROOT = compiledHelper?.REPO_ROOT
113
+ STATIC_ROOT = path.resolve(RESOURCE_ROOT, "./static");
114
+ DEFAULT_INDEX = path.join(STATIC_ROOT, "index.html");
115
+ DEFAULT_MANIFEST = path.join(STATIC_ROOT, "manifest.json");
116
+ DEFAULT_HELP = path.join(STATIC_ROOT, "help.md");
117
+ DEFAULT_README = path.resolve(RESOURCE_ROOT, "./README.md");
118
+ DEFAULT_README_EN = path.resolve(RESOURCE_ROOT, "./README.en.md");
119
+ }
120
+
121
+ let cliBootstrapPromise = null;
122
+ for (const i of ["rz.js", "sz.js", "viu.mjs", "client.mjs"]) {
123
+ let subcmd = i.split(".")[0] ;
124
+ if (process.argv[2] === `--${subcmd}`) {
125
+ process.argv.splice(2, 1);
126
+ process.argv[1] = process.argv[1].replace(/gotty\.js$/, i);
127
+ cliBootstrapPromise = (async () => {
128
+
129
+ if(subcmd=="rz")
130
+ await import("./rz.js");
131
+ else if(subcmd=="sz")
132
+ await import("./sz.js");
133
+ else if(subcmd=="viu")
134
+ await import("./viu.mjs");
135
+ else if(subcmd=="client")
136
+ await import("./client.mjs");
137
+
138
+ })().catch((error) => {
139
+ console.error(String(error && error.stack ? error.stack : error));
140
+ process.exitCode = 1;
141
+ });
142
+ break;
143
+ }
144
+ }
145
+
146
+
147
+ const pkg = require('./package.json');
148
+
149
+
150
+ function printUsage() {
151
+ console.log(`GoTTY JavaScript rewrite
152
+ Linux/macOS: use Bun
153
+ Windows: use Bun/Node
154
+ Version: ${pkg.version}
155
+
156
+ Usage: bun gotty.js [options] <command> [<arguments...>]
157
+
158
+ Built-in tools:
159
+ --rz Start rz.js
160
+ --sz Start sz.js
161
+ --viu Start viu.mjs
162
+ --client Start client.mjs
163
+
164
+ Options:
165
+ -a, --address <value> IP address to listen
166
+ (default: 0.0.0.0)
167
+
168
+ -p, --port <value> Port number to listen
169
+ (default: 8080)
170
+
171
+ -w, --permit-write Permits writing to the TTY
172
+ (default: disabled)
173
+
174
+ -m, --path <value> Base path
175
+ (default: /)
176
+
177
+ -c, --credential <user:pass> Credential for Basic Authentication
178
+
179
+ --once Accept only one client and exit on disconnect
180
+ (default: disabled)
181
+
182
+ -r, --random-url Add a random string to the URL
183
+ --random-url-length <value> Random URL length
184
+ (default: 16)
185
+
186
+ -t, --tls Enable TLS/SSL
187
+ --tls-crt <value> TLS certificate file path
188
+ (default: ~/.gotty.crt)
189
+ --tls-key <value> TLS key file path
190
+ (default: ~/.gotty.key)
191
+
192
+ --index <value> Custom index.html file
193
+ (default: static/index.html)
194
+ --title-format <value> Title format
195
+ (default: {{ .command }}@{{ .hostname }} )
196
+
197
+ --reconnect, --session Enable session mode
198
+ Keep the PTY session resumable after the browser disconnects
199
+ (default: disabled)
200
+ --reconnect-time <value> Time to preserve original TTY (seconds)
201
+ (default: -1 (forever) )
202
+
203
+ --max-connection <value> Maximum concurrent clients
204
+ (default: -1 (no limit) )
205
+
206
+ --timeout <value> Timeout waiting for first/next client (seconds)
207
+ (default: -1)
208
+
209
+ --permit-arguments Permit query args from the client
210
+ ?arg=a&arg=b... from URL => appends to command argv
211
+ (default: disabled)
212
+
213
+ --pass-headers Pass HTTP request headers as environment variables
214
+ Request headers => child env: HTTP_USER_AGENT=...
215
+ (default: disabled)
216
+
217
+ --width <value> Fixed terminal width
218
+ (default: 0 (auto) )
219
+ --height <value> Fixed terminal height
220
+ (default: 0 (auto) )
221
+
222
+ --ws-origin <regex> Allowed WebSocket Origin regex
223
+ (default: disabled)
224
+ --ws-query-args <value> Query arguments appended by the browser client
225
+ Adds a fixed query string to the WebSocket URL
226
+ Format: keyA=valA&keyB=valB
227
+ (default: empty)
228
+
229
+ --webgl Enable xterm WebGL renderer
230
+ (default: disabled)
231
+
232
+ --quiet Disable logging
233
+ (default: log to stderr)
234
+
235
+ --close-signal <value> Signal sent to the child on close
236
+ (default: ${DEFAULTS.closeSignal})
237
+ --close-timeout <value> Seconds before force kill after ${DEFAULTS.closeSignal}
238
+ (default: -1 (disabled) )
239
+
240
+ --help-web Show help for WebUI frontend
241
+ --readme Show README.md and exit
242
+ --readme-en Show README.en.md and exit
243
+ --help, -h Show help for CLI
244
+
245
+ Experimental:
246
+ --build-exe Build a Bun single-file executable and exit
247
+ --build-for <target> Build a Bun single-file executable for target`);
248
+ }
249
+
250
+ function printMarkdownFile(filePath) {
251
+ const text = readTextFile(filePath);
252
+ const markdownAnsi =
253
+ typeof Bun !== "undefined" &&
254
+ Bun &&
255
+ Bun.markdown &&
256
+ typeof Bun.markdown.ansi === "function"
257
+ ? Bun.markdown.ansi
258
+ : null;
259
+ console.log(markdownAnsi ? markdownAnsi(text, { hyperlinks: true }) : text);
260
+ }
261
+
262
+ function printWebHelp() {
263
+ printMarkdownFile(DEFAULT_HELP);
264
+ }
265
+
266
+ function fatal(message, code = 1) {
267
+ console.error(message);
268
+ process.exit(code);
269
+ }
270
+
271
+ function expandHome(filePath) {
272
+ if (!filePath || !filePath.startsWith("~")) {
273
+ return filePath;
274
+ }
275
+ return path.join(os.homedir(), filePath.slice(1));
276
+ }
277
+
278
+ function normalizeBasePath(basePath) {
279
+ let value = basePath || "/";
280
+ if (!value.startsWith("/")) {
281
+ value = `/${value}`;
282
+ }
283
+ if (!value.endsWith("/")) {
284
+ value = `${value}/`;
285
+ }
286
+ return value;
287
+ }
288
+
289
+ function forceRawReadableSocket(socket) {
290
+ if (!socket) {
291
+ return;
292
+ }
293
+
294
+ try {
295
+ if (typeof socket.setEncoding === "function") {
296
+ socket.setEncoding(null);
297
+ }
298
+ } catch (error) {
299
+ // ignore
300
+ }
301
+
302
+ if (Object.prototype.hasOwnProperty.call(socket, "_decoder")) {
303
+ delete socket._decoder;
304
+ }
305
+
306
+ const readableState = socket._readableState;
307
+ if (readableState) {
308
+ readableState.decoder = null;
309
+ readableState.encoding = null;
310
+ }
311
+ }
312
+
313
+ function forceWindowsPtyBinaryMode(proc) {
314
+ if (process.platform !== "win32" || !proc) {
315
+ return;
316
+ }
317
+
318
+ forceRawReadableSocket(proc._socket);
319
+ if (proc._agent) {
320
+ forceRawReadableSocket(proc._agent.outSocket);
321
+ }
322
+ }
323
+
324
+ function encodeWindowsBridgeMessage(message) {
325
+ return `${WINDOWS_BRIDGE_PREFIX.toString("ascii")}${Buffer.from(JSON.stringify(message), "utf8").toString("base64")}${WINDOWS_BRIDGE_SUFFIX.toString("ascii")}`;
326
+ }
327
+
328
+ function windowsBridgeSidecarPath(id) {
329
+ return path.join(os.tmpdir(), "js-gotty-bridge", `${id}.json`);
330
+ }
331
+
332
+ function decodeWindowsBridgePayload(payload) {
333
+ const decoded = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
334
+ if (!decoded || typeof decoded !== "object" || !decoded.$f) {
335
+ return decoded;
336
+ }
337
+
338
+ const filePath = windowsBridgeSidecarPath(String(decoded.$f));
339
+ let text;
340
+ try {
341
+ text = fs.readFileSync(filePath, "utf8");
342
+ } finally {
343
+ try {
344
+ fs.unlinkSync(filePath);
345
+ } catch {}
346
+ }
347
+ return JSON.parse(text);
348
+ }
349
+
350
+ function basenameSafe(name) {
351
+ const base = path.basename(name || "");
352
+ if (!base || base === "." || base === "..") {
353
+ throw new Error(`invalid filename from peer: ${JSON.stringify(name)}`);
354
+ }
355
+ return base;
356
+ }
357
+
358
+ class WindowsBridgeParser {
359
+ constructor(label = "parser") {
360
+ this.label = label;
361
+ this.pending = Buffer.alloc(0);
362
+ }
363
+
364
+ consume(raw) {
365
+ const data = this.pending.length > 0 ? Buffer.concat([this.pending, raw]) : raw;
366
+ bridgeTrace(this.label, "consume", JSON.stringify({
367
+ rawLength: raw.length,
368
+ pendingLength: this.pending.length,
369
+ combinedLength: data.length,
370
+ prefixIndex: data.indexOf(WINDOWS_BRIDGE_PREFIX),
371
+ suffixIndex: data.indexOf(WINDOWS_BRIDGE_SUFFIX)
372
+ }), previewBuffer(data));
373
+ this.pending = Buffer.alloc(0);
374
+ const plain = [];
375
+ const controls = [];
376
+ let offset = 0;
377
+
378
+ for (;;) {
379
+ const start = data.indexOf(WINDOWS_BRIDGE_PREFIX, offset);
380
+ if (start === -1) {
381
+ break;
382
+ }
383
+
384
+ if (start > offset) {
385
+ plain.push(data.subarray(offset, start));
386
+ }
387
+
388
+ const payloadStart = start + WINDOWS_BRIDGE_PREFIX.length;
389
+ const end = data.indexOf(WINDOWS_BRIDGE_SUFFIX, payloadStart);
390
+ if (end === -1) {
391
+ this.pending = data.subarray(start);
392
+ bridgeTrace(this.label, "pending-incomplete", JSON.stringify({
393
+ start,
394
+ pendingLength: this.pending.length
395
+ }), previewBuffer(this.pending));
396
+ return {
397
+ plain: plain.length === 0 ? Buffer.alloc(0) : plain.length === 1 ? Buffer.from(plain[0]) : Buffer.concat(plain),
398
+ controls
399
+ };
400
+ }
401
+
402
+ const payload = data.subarray(payloadStart, end).toString("ascii");
403
+ try {
404
+ controls.push(decodeWindowsBridgePayload(payload));
405
+ bridgeTrace(this.label, "control", JSON.stringify({
406
+ start,
407
+ end,
408
+ controls: controls.length
409
+ }));
410
+ } catch (error) {
411
+ bridgeTrace(this.label, "decode-error", JSON.stringify({
412
+ start,
413
+ end,
414
+ message: error.message
415
+ }), previewBuffer(data.subarray(start, end + WINDOWS_BRIDGE_SUFFIX.length)));
416
+ plain.push(data.subarray(start, end + WINDOWS_BRIDGE_SUFFIX.length));
417
+ }
418
+
419
+ offset = end + WINDOWS_BRIDGE_SUFFIX.length;
420
+ }
421
+
422
+ if (offset < data.length) {
423
+ const tail = data.subarray(offset);
424
+ let partialPrefixLength = 0;
425
+ const maxPrefixLength = Math.min(tail.length, WINDOWS_BRIDGE_PREFIX.length - 1);
426
+ for (let len = maxPrefixLength; len > 0; len -= 1) {
427
+ if (tail.subarray(tail.length - len).equals(WINDOWS_BRIDGE_PREFIX.subarray(0, len))) {
428
+ partialPrefixLength = len;
429
+ break;
430
+ }
431
+ }
432
+
433
+ if (partialPrefixLength > 0) {
434
+ const flushableLength = tail.length - partialPrefixLength;
435
+ if (flushableLength > 0) {
436
+ plain.push(tail.subarray(0, flushableLength));
437
+ }
438
+ this.pending = tail.subarray(flushableLength);
439
+ bridgeTrace(this.label, "pending-partial-prefix", JSON.stringify({
440
+ partialPrefixLength,
441
+ pendingLength: this.pending.length
442
+ }), previewBuffer(this.pending));
443
+ } else {
444
+ plain.push(tail);
445
+ }
446
+ }
447
+
448
+ const plainBuffer = plain.length === 0 ? Buffer.alloc(0) : plain.length === 1 ? Buffer.from(plain[0]) : Buffer.concat(plain);
449
+ bridgeTrace(this.label, "result", JSON.stringify({
450
+ plainLength: plainBuffer.length,
451
+ controls: controls.length,
452
+ pendingLength: this.pending.length
453
+ }), plainBuffer.length > 0 ? previewBuffer(plainBuffer) : "{\"length\":0}");
454
+
455
+ return {
456
+ plain: plainBuffer,
457
+ controls
458
+ };
459
+ }
460
+ }
461
+
462
+ function randomPathSegment(length) {
463
+ const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
464
+ const bytes = crypto.randomBytes(length);
465
+ let output = "";
466
+ for (let i = 0; i < length; i += 1) {
467
+ output += alphabet[bytes[i] % alphabet.length];
468
+ }
469
+ return output;
470
+ }
471
+
472
+ function generateReconnectToken() {
473
+ return crypto.randomBytes(24).toString("base64url");
474
+ }
475
+
476
+ function formatReconnectTokenForLog(token) {
477
+ const value = String(token || "");
478
+ if (!value) {
479
+ return "-";
480
+ }
481
+ return value // .slice(0, 8);
482
+ }
483
+
484
+ function parseInteger(name, value) {
485
+ const parsed = Number.parseInt(value, 10);
486
+ if (Number.isNaN(parsed)) {
487
+ fatal(`Invalid integer for ${name}: ${value}`, 2);
488
+ }
489
+ return parsed;
490
+ }
491
+
492
+ function parseArgs(argv) {
493
+ const options = { ...DEFAULTS };
494
+ const commandArgs = [];
495
+
496
+ for (let i = 0; i < argv.length; i += 1) {
497
+ const arg = argv[i];
498
+ const next = () => {
499
+ i += 1;
500
+ if (i >= argv.length) {
501
+ fatal(`Missing value for ${arg}`, 2);
502
+ }
503
+ return argv[i];
504
+ };
505
+
506
+ switch (arg) {
507
+ case "--help":
508
+ case "-h":
509
+ printUsage();
510
+ process.exit(0);
511
+ break;
512
+ case "--help-web":
513
+ printWebHelp();
514
+ process.exit(0);
515
+ break;
516
+ case "--readme":
517
+ printMarkdownFile(DEFAULT_README);
518
+ process.exit(0);
519
+ break;
520
+ case "--readme-en":
521
+ printMarkdownFile(DEFAULT_README_EN);
522
+ process.exit(0);
523
+ break;
524
+ case "--address":
525
+ case "-a":
526
+ options.address = next();
527
+ break;
528
+ case "--port":
529
+ case "-p":
530
+ options.port = next();
531
+ break;
532
+ case "--path":
533
+ case "-m":
534
+ options.path = next();
535
+ break;
536
+ case "--permit-write":
537
+ case "-w":
538
+ options.permitWrite = true;
539
+ break;
540
+ case "--credential":
541
+ case "-c":
542
+ options.credential = next();
543
+ break;
544
+ case "--random-url":
545
+ case "-r":
546
+ options.randomUrl = true;
547
+ break;
548
+ case "--random-url-length":
549
+ options.randomUrlLength = parseInteger(arg, next());
550
+ break;
551
+ case "--tls":
552
+ case "-t":
553
+ options.tls = true;
554
+ break;
555
+ case "--tls-crt":
556
+ options.tlsCrt = next();
557
+ break;
558
+ case "--tls-key":
559
+ options.tlsKey = next();
560
+ break;
561
+ case "--index":
562
+ options.index = next();
563
+ break;
564
+ case "--title-format":
565
+ options.titleFormat = next();
566
+ break;
567
+ case "--reconnect":
568
+ case "--session":
569
+ options.reconnect = true;
570
+ break;
571
+ case "--reconnect-time":
572
+ options.reconnectTime = parseInteger(arg, next());
573
+ break;
574
+ case "--max-connection":
575
+ options.maxConnection = parseInteger(arg, next());
576
+ break;
577
+ case "--once":
578
+ options.once = true;
579
+ break;
580
+ case "--timeout":
581
+ options.timeout = parseInteger(arg, next());
582
+ break;
583
+ case "--permit-arguments":
584
+ options.permitArguments = true;
585
+ break;
586
+ case "--pass-headers":
587
+ options.passHeaders = true;
588
+ break;
589
+ case "--width":
590
+ options.width = parseInteger(arg, next());
591
+ break;
592
+ case "--height":
593
+ options.height = parseInteger(arg, next());
594
+ break;
595
+ case "--ws-origin":
596
+ options.wsOrigin = next();
597
+ break;
598
+ case "--ws-query-args":
599
+ options.wsQueryArgs = next();
600
+ break;
601
+ case "--webgl":
602
+ options.webgl = true;
603
+ break;
604
+ case "--quiet":
605
+ options.quiet = true;
606
+ break;
607
+ case "--close-signal":
608
+ options.closeSignal = next();
609
+ break;
610
+ case "--close-timeout":
611
+ options.closeTimeout = parseInteger(arg, next());
612
+ break;
613
+ default:
614
+ if (arg.startsWith("-")) {
615
+ console.error(`Unknown option: ${arg}`);
616
+ options[arg.replace(/^-+/,'')] = true ;
617
+ }
618
+ else{
619
+ commandArgs.push(...argv.slice(i));
620
+ i = argv.length;
621
+ }
622
+ break;
623
+ }
624
+ }
625
+
626
+ if (commandArgs.length === 0) {
627
+ printUsage();
628
+ fatal("\nError: No command given.", 1);
629
+ }
630
+
631
+ return {
632
+ options,
633
+ command: commandArgs[0],
634
+ argv: commandArgs.slice(1)
635
+ };
636
+ }
637
+
638
+ function logFactory(quiet) {
639
+ return (...parts) => {
640
+ if (!quiet) {
641
+ console.error(...parts);
642
+ }
643
+ };
644
+ }
645
+
646
+ function traceLog(...parts) {
647
+ if (KITTY_TRACE) {
648
+ console.log("[kitty-trace]", ...parts);
649
+ }
650
+ }
651
+
652
+ function bridgeTrace(...parts) {
653
+ if (WINDOWS_BRIDGE_TRACE) {
654
+ console.log("[windows-bridge]", ...parts);
655
+ }
656
+ }
657
+
658
+ function previewBuffer(buffer, limit = 96) {
659
+ const chunk = buffer.subarray(0, Math.min(limit, buffer.length));
660
+ return JSON.stringify({
661
+ length: buffer.length,
662
+ ascii: chunk.toString("latin1").replace(/[^\x20-\x7e]/g, "."),
663
+ hex: chunk.toString("hex")
664
+ });
665
+ }
666
+
667
+ function readTextFile(filePath) {
668
+ const bundledText = helperReadInternalAssetText?.(internalAssetPathFor(filePath));
669
+ if (bundledText == null) {
670
+ return fs.readFileSync(filePath, "utf8");
671
+ }
672
+ return bundledText;
673
+ }
674
+
675
+ function internalAssetPathFor(filePath) {
676
+ if (!helperAssetPath) {
677
+ return filePath;
678
+ }
679
+
680
+ const normalizedPath = path.normalize(filePath);
681
+ const staticRelativePath = path.relative(STATIC_ROOT, normalizedPath);
682
+ if (staticRelativePath && !staticRelativePath.startsWith("..") && !path.isAbsolute(staticRelativePath)) {
683
+ return helperAssetPath("static", staticRelativePath);
684
+ }
685
+
686
+ if (normalizedPath === DEFAULT_README) {
687
+ return helperAssetPath("README.md");
688
+ }
689
+
690
+ if (normalizedPath === DEFAULT_README_EN) {
691
+ return helperAssetPath("README.en.md");
692
+ }
693
+
694
+ return filePath;
695
+ }
696
+
697
+ function contentType(filePath) {
698
+ const ext = path.extname(filePath).toLowerCase();
699
+ switch (ext) {
700
+ case ".html":
701
+ return "text/html; charset=utf-8";
702
+ case ".css":
703
+ return "text/css; charset=utf-8";
704
+ case ".js":
705
+ return "application/javascript; charset=utf-8";
706
+ case ".json":
707
+ return "application/json; charset=utf-8";
708
+ case ".svg":
709
+ return "image/svg+xml";
710
+ case ".ico":
711
+ return "image/x-icon";
712
+ case ".png":
713
+ return "image/png";
714
+ case ".map":
715
+ return "application/json; charset=utf-8";
716
+ case ".txt":
717
+ return "text/plain; charset=utf-8";
718
+ default:
719
+ return "application/octet-stream";
720
+ }
721
+ }
722
+
723
+ function renderTemplate(template, variables) {
724
+ return template.replace(/\{\{\s*\.([^}]+?)\s*\}\}/g, (_, expression) => {
725
+ const value = resolveTemplateValue(variables, expression.trim());
726
+ return value == null ? "" : String(value);
727
+ });
728
+ }
729
+
730
+ function resolveTemplateValue(variables, expression) {
731
+ const parts = expression.split(".");
732
+ let current = variables;
733
+ for (const part of parts) {
734
+ if (part === "") {
735
+ continue;
736
+ }
737
+ if (current == null || !(part in current)) {
738
+ return "";
739
+ }
740
+ current = current[part];
741
+ }
742
+ if (Array.isArray(current)) {
743
+ return current.join(" ");
744
+ }
745
+ return current;
746
+ }
747
+
748
+ function buildTitleVariables(command, argv, hostname, remoteAddr, slaveVars) {
749
+ const merged = {
750
+ command,
751
+ argv,
752
+ hostname,
753
+ remote_addr: remoteAddr,
754
+ ...slaveVars,
755
+ server: {
756
+ command,
757
+ argv,
758
+ hostname
759
+ },
760
+ master: {
761
+ remote_addr: remoteAddr
762
+ },
763
+ slave: slaveVars
764
+ };
765
+ return merged;
766
+ }
767
+
768
+ function parseInitMessage(raw) {
769
+ let parsed;
770
+ try {
771
+ parsed = JSON.parse(raw.toString("utf8"));
772
+ } catch (error) {
773
+ throw new Error("failed to authenticate websocket connection");
774
+ }
775
+
776
+ return {
777
+ Arguments: typeof parsed.Arguments === "string" ? parsed.Arguments : "",
778
+ AuthToken: typeof parsed.AuthToken === "string" ? parsed.AuthToken : "",
779
+ ReconnectToken: typeof parsed.ReconnectToken === "string"
780
+ ? parsed.ReconnectToken.trim()
781
+ : "",
782
+ ReadOnly: parsed.ReadOnly === true,
783
+ DisconnectWriter: parsed.DisconnectWriter === true,
784
+ TerminateSession: parsed.TerminateSession === true
785
+ };
786
+ }
787
+
788
+ function detectWindowsShell(command) {
789
+ const name = path.basename(String(command || "")).toLowerCase();
790
+ if (name === "powershell" || name === "powershell.exe") {
791
+ return "powershell";
792
+ }
793
+ if (name === "pwsh" || name === "pwsh.exe") {
794
+ return "pwsh";
795
+ }
796
+ if (name === "cmd" || name === "cmd.exe") {
797
+ return "cmd";
798
+ }
799
+ return "";
800
+ }
801
+
802
+ function createHeaderEnvironment(headers) {
803
+ const env = {};
804
+ for (const [key, value] of Object.entries(headers)) {
805
+ if (value == null) {
806
+ continue;
807
+ }
808
+ const normalizedKey = `HTTP_${key.toUpperCase().replace(/-/g, "_")}`;
809
+ env[normalizedKey] = Array.isArray(value) ? value.join(",") : String(value);
810
+ }
811
+ return env;
812
+ }
813
+
814
+ function basicAuthAuthorized(req, credential) {
815
+ const header = req.headers.authorization;
816
+ if (!header) {
817
+ return false;
818
+ }
819
+ const [scheme, encoded] = header.split(" ");
820
+ if (!scheme || !encoded || scheme.toLowerCase() !== "basic") {
821
+ return false;
822
+ }
823
+ let payload;
824
+ try {
825
+ payload = Buffer.from(encoded, "base64").toString("utf8");
826
+ } catch (error) {
827
+ return false;
828
+ }
829
+ return payload === credential;
830
+ }
831
+
832
+ function sendBasicAuthRequired(res) {
833
+ res.writeHead(401, {
834
+ "WWW-Authenticate": 'Basic realm="GoTTY"',
835
+ "Content-Type": "text/plain; charset=utf-8"
836
+ });
837
+ res.end("authorization failed");
838
+ }
839
+
840
+ class CursorStateTracker {
841
+ constructor() {
842
+ this.row = 1;
843
+ this.col = 1;
844
+ this.cols = 80;
845
+ this._state = "text";
846
+ this._params = "";
847
+ this._wrapPending = false;
848
+ }
849
+
850
+ snapshot() {
851
+ return { row: this.row, col: this.col };
852
+ }
853
+
854
+ setDimensions(cols) {
855
+ if (Number.isFinite(cols) && cols > 0) {
856
+ this.cols = Math.max(1, Math.floor(cols));
857
+ this.col = Math.min(this.col, this.cols);
858
+ }
859
+ }
860
+
861
+ consume(buffer) {
862
+ const text = buffer.toString("utf8");
863
+ for (let i = 0; i < text.length; i += 1) {
864
+ const ch = text[i];
865
+ if (this._state === "text") {
866
+ if (ch === "\u001b") {
867
+ this._state = "esc";
868
+ continue;
869
+ }
870
+ if (ch === "\r") {
871
+ this.col = 1;
872
+ this._wrapPending = false;
873
+ continue;
874
+ }
875
+ if (ch === "\n") {
876
+ if (!this._wrapPending) {
877
+ this.row += 1;
878
+ }
879
+ this._wrapPending = false;
880
+ continue;
881
+ }
882
+ if (ch === "\b") {
883
+ this.col = Math.max(1, this.col - 1);
884
+ this._wrapPending = false;
885
+ continue;
886
+ }
887
+ if (this._wrapPending) {
888
+ this.row += 1;
889
+ this.col = 1;
890
+ this._wrapPending = false;
891
+ }
892
+ if (this.col >= this.cols) {
893
+ this._wrapPending = true;
894
+ } else {
895
+ this.col += 1;
896
+ }
897
+ continue;
898
+ }
899
+
900
+ if (this._state === "esc") {
901
+ if (ch === "[") {
902
+ this._state = "csi";
903
+ this._params = "";
904
+ continue;
905
+ }
906
+ this._state = "text";
907
+ continue;
908
+ }
909
+
910
+ if (this._state === "csi") {
911
+ if ((ch >= "0" && ch <= "9") || ch === ";" || ch === "?" || ch === ">") {
912
+ this._params += ch;
913
+ continue;
914
+ }
915
+ this._applyCsi(ch, this._params);
916
+ this._state = "text";
917
+ this._params = "";
918
+ }
919
+ }
920
+ }
921
+
922
+ _applyCsi(finalByte, params) {
923
+ const raw = params.replace(/[?>]/g, "");
924
+ const numbers = raw === "" ? [] : raw.split(";").map((value) => Number.parseInt(value || "0", 10) || 0);
925
+ switch (finalByte) {
926
+ case "A":
927
+ this.row = Math.max(1, this.row - (numbers[0] || 1));
928
+ this._wrapPending = false;
929
+ break;
930
+ case "B":
931
+ this.row = Math.max(1, this.row + (numbers[0] || 1));
932
+ this._wrapPending = false;
933
+ break;
934
+ case "C":
935
+ this.col = Math.max(1, this.col + (numbers[0] || 1));
936
+ this.col = Math.min(this.col, this.cols);
937
+ this._wrapPending = false;
938
+ break;
939
+ case "D":
940
+ this.col = Math.max(1, this.col - (numbers[0] || 1));
941
+ this._wrapPending = false;
942
+ break;
943
+ case "E":
944
+ this.row = Math.max(1, this.row + (numbers[0] || 1));
945
+ this.col = 1;
946
+ this._wrapPending = false;
947
+ break;
948
+ case "F":
949
+ this.row = Math.max(1, this.row - (numbers[0] || 1));
950
+ this.col = 1;
951
+ this._wrapPending = false;
952
+ break;
953
+ case "G":
954
+ this.col = Math.max(1, numbers[0] || 1);
955
+ this.col = Math.min(this.col, this.cols);
956
+ this._wrapPending = false;
957
+ break;
958
+ case "H":
959
+ case "f":
960
+ this.row = Math.max(1, numbers[0] || 1);
961
+ this.col = Math.max(1, numbers[1] || 1);
962
+ this.col = Math.min(this.col, this.cols);
963
+ this._wrapPending = false;
964
+ break;
965
+ default:
966
+ break;
967
+ }
968
+ }
969
+
970
+ applyKittyPlacement(control) {
971
+ if (!control || control.C === "1") {
972
+ return;
973
+ }
974
+ const cols = Math.max(0, Number.parseInt(control.c || "0", 10) || 0);
975
+ const rows = Math.max(0, Number.parseInt(control.r || "0", 10) || 0);
976
+ if (cols > 0) {
977
+ this.col += cols;
978
+ if (this.col > this.cols) {
979
+ this.row += Math.floor((this.col - 1) / this.cols);
980
+ this.col = ((this.col - 1) % this.cols) + 1;
981
+ }
982
+ }
983
+ if (rows > 0) {
984
+ this.row += rows;
985
+ }
986
+ this._wrapPending = false;
987
+ }
988
+ }
989
+
990
+ class KittyGraphicsParser {
991
+ constructor() {
992
+ this.pending = Buffer.alloc(0);
993
+ this.transfers = new Map();
994
+ this.images = new Map();
995
+ this.implicitTransferKey = null;
996
+ this.tempRoots = Array.from(new Set(
997
+ [os.tmpdir(), "/tmp", "/dev/shm", process.env.TMPDIR]
998
+ .filter(Boolean)
999
+ .map((entry) => path.resolve(entry))
1000
+ ));
1001
+ }
1002
+
1003
+ consume(raw) {
1004
+ const data = this.pending.length > 0 ? Buffer.concat([this.pending, raw]) : raw;
1005
+ this.pending = Buffer.alloc(0);
1006
+ const events = [];
1007
+ let offset = 0;
1008
+ let plainStart = 0;
1009
+
1010
+ while (offset < data.length) {
1011
+ if (data[offset] === 0x1b && offset + 2 < data.length && data[offset + 1] === 0x5f && data[offset + 2] === 0x47) {
1012
+ const commandEnd = this._findApcEnd(data, offset + 3);
1013
+ if (commandEnd === -1) {
1014
+ if (plainStart < offset) {
1015
+ events.push({ kind: "plain", data: data.subarray(plainStart, offset) });
1016
+ }
1017
+ this.pending = data.subarray(offset);
1018
+ break;
1019
+ }
1020
+
1021
+ if (plainStart < offset) {
1022
+ events.push({ kind: "plain", data: data.subarray(plainStart, offset) });
1023
+ }
1024
+
1025
+ const packet = data.subarray(offset + 3, commandEnd);
1026
+ events.push({ kind: "kitty", packet });
1027
+
1028
+ offset = commandEnd + 2;
1029
+ plainStart = offset;
1030
+ continue;
1031
+ }
1032
+ offset += 1;
1033
+ }
1034
+
1035
+ if (plainStart < data.length && this.pending.length === 0) {
1036
+ events.push({ kind: "plain", data: data.subarray(plainStart) });
1037
+ }
1038
+
1039
+ return { events };
1040
+ }
1041
+
1042
+ parsePacket(packet, cursor) {
1043
+ return this._parsePacket(packet, cursor);
1044
+ }
1045
+
1046
+ acknowledge(graphic) {
1047
+ if (!graphic || (graphic.kind !== "placement" && graphic.kind !== "query")) {
1048
+ return null;
1049
+ }
1050
+ const control = graphic.control || {};
1051
+ if (graphic.kind === "placement" && control.q !== "1") {
1052
+ return null;
1053
+ }
1054
+ const imageId = control.i || control.I || (graphic.image && graphic.image.id);
1055
+ if (!imageId || control.q === "2") {
1056
+ return null;
1057
+ }
1058
+ const message = graphic.responseMessage || "OK";
1059
+ return `\u001b_Gi=${imageId};${message}\u001b\\`;
1060
+ }
1061
+
1062
+ _findApcEnd(buffer, start) {
1063
+ for (let i = start; i < buffer.length - 1; i += 1) {
1064
+ if (buffer[i] === 0x1b && buffer[i + 1] === 0x5c) {
1065
+ return i;
1066
+ }
1067
+ }
1068
+ return -1;
1069
+ }
1070
+
1071
+ _parsePacket(packet, cursor) {
1072
+ const separator = packet.indexOf(0x3b);
1073
+ const controlText = separator === -1 ? packet.toString("ascii") : packet.subarray(0, separator).toString("ascii");
1074
+ const payloadText = separator === -1 ? "" : packet.subarray(separator + 1).toString("ascii");
1075
+ traceLog("graphics-packet", JSON.stringify({
1076
+ control: controlText,
1077
+ payloadLength: payloadText.length,
1078
+ row: cursor.row,
1079
+ col: cursor.col
1080
+ }));
1081
+ const control = {};
1082
+ for (const part of controlText.split(",")) {
1083
+ if (!part) {
1084
+ continue;
1085
+ }
1086
+ const eq = part.indexOf("=");
1087
+ if (eq === -1) {
1088
+ control[part] = true;
1089
+ } else {
1090
+ control[part.slice(0, eq)] = part.slice(eq + 1);
1091
+ }
1092
+ }
1093
+
1094
+ const action = control.a || "t";
1095
+ if (action === "q") {
1096
+ const imageId = control.i || control.I || null;
1097
+ try {
1098
+ this._resolveImageData(control, payloadText);
1099
+ return {
1100
+ kind: "query",
1101
+ cursor,
1102
+ control,
1103
+ image: {
1104
+ id: imageId
1105
+ },
1106
+ responseMessage: "OK"
1107
+ };
1108
+ } catch (error) {
1109
+ traceLog("graphics-query-error", JSON.stringify({
1110
+ control,
1111
+ message: error.message
1112
+ }));
1113
+ return {
1114
+ kind: "query",
1115
+ cursor,
1116
+ control,
1117
+ image: {
1118
+ id: imageId
1119
+ },
1120
+ responseMessage: `ERROR:${this._sanitizeResponseMessage(error.message)}`
1121
+ };
1122
+ }
1123
+ }
1124
+
1125
+ if (action === "d") {
1126
+ const deleteScope = control.d || "a";
1127
+ const deleteImageId = control.i || control.I || null;
1128
+ if (deleteScope === "A") {
1129
+ this.images.clear();
1130
+ } else if (deleteScope === "I" && deleteImageId) {
1131
+ this.images.delete(deleteImageId);
1132
+ }
1133
+ return {
1134
+ kind: "delete",
1135
+ delete: {
1136
+ scope: deleteScope,
1137
+ imageId: deleteImageId,
1138
+ placementId: control.p || null,
1139
+ cursor
1140
+ }
1141
+ };
1142
+ }
1143
+
1144
+ if (action === "p") {
1145
+ const imageId = control.i || control.I;
1146
+ if (!imageId || !this.images.has(imageId)) {
1147
+ return null;
1148
+ }
1149
+ return {
1150
+ kind: "placement",
1151
+ cursor,
1152
+ control,
1153
+ image: this.images.get(imageId)
1154
+ };
1155
+ }
1156
+
1157
+ if (action !== "T" && action !== "t") {
1158
+ traceLog("graphics-ignored-action", JSON.stringify({
1159
+ action,
1160
+ control
1161
+ }));
1162
+ return null;
1163
+ }
1164
+
1165
+ const key = this._resolveTransferKey(control, action);
1166
+ if (!key) {
1167
+ traceLog("graphics-transfer-error", JSON.stringify({
1168
+ control,
1169
+ message: "missing transfer key"
1170
+ }));
1171
+ return null;
1172
+ }
1173
+ const imageId = key.slice(0, key.lastIndexOf(":"));
1174
+ const existing = this.transfers.get(key) || {
1175
+ control: { ...control },
1176
+ chunks: []
1177
+ };
1178
+ existing.control = { ...existing.control, ...control };
1179
+ existing.chunks.push(payloadText);
1180
+
1181
+ if (control.m === "1") {
1182
+ this.transfers.set(key, existing);
1183
+ if (!control.i && !control.I) {
1184
+ this.implicitTransferKey = key;
1185
+ }
1186
+ return null;
1187
+ }
1188
+
1189
+ this.transfers.delete(key);
1190
+ if (this.implicitTransferKey === key) {
1191
+ this.implicitTransferKey = null;
1192
+ }
1193
+ let binary;
1194
+ try {
1195
+ binary = this._resolveImageData(existing.control, existing.chunks.join(""));
1196
+ } catch (error) {
1197
+ traceLog("graphics-transfer-error", JSON.stringify({
1198
+ control: existing.control,
1199
+ message: error.message
1200
+ }));
1201
+ return null;
1202
+ }
1203
+
1204
+ const image = {
1205
+ id: imageId,
1206
+ format: Number.parseInt(existing.control.f || "32", 10),
1207
+ width: Number.parseInt(existing.control.s || "0", 10),
1208
+ height: Number.parseInt(existing.control.v || "0", 10),
1209
+ mime: existing.control.U || existing.control.mime || null,
1210
+ data: binary.toString("base64")
1211
+ };
1212
+
1213
+ this.images.set(imageId, image);
1214
+
1215
+ return {
1216
+ kind: "placement",
1217
+ cursor,
1218
+ control: existing.control,
1219
+ image
1220
+ };
1221
+ }
1222
+
1223
+ _resolveTransferKey(control, action) {
1224
+ const explicitImageId = control.i || control.I;
1225
+ if (explicitImageId) {
1226
+ return `${explicitImageId}:${control.p || "0"}`;
1227
+ }
1228
+ if (this.implicitTransferKey && (action === "T" || action === "t")) {
1229
+ return this.implicitTransferKey;
1230
+ }
1231
+ return `anon:${Date.now()}:${Math.random()}:${control.p || "0"}`;
1232
+ }
1233
+
1234
+ _resolveImageData(control, payloadText) {
1235
+ const transmission = control.t || "d";
1236
+ if (transmission === "d") {
1237
+ return this._inflateIfNeeded(control, Buffer.from(payloadText, "base64"));
1238
+ }
1239
+ if (transmission === "f" || transmission === "t") {
1240
+ return this._inflateIfNeeded(control, this._readImageFile(control, payloadText, transmission === "t"));
1241
+ }
1242
+ throw new Error(`unsupported transmission ${transmission}`);
1243
+ }
1244
+
1245
+ _inflateIfNeeded(control, binary) {
1246
+ if (control.o === "z") {
1247
+ return zlib.inflateSync(binary);
1248
+ }
1249
+ return binary;
1250
+ }
1251
+
1252
+ _readImageFile(control, payloadText, deleteAfterRead) {
1253
+ const targetPath = Buffer.from(payloadText, "base64").toString("utf8");
1254
+ if (!targetPath) {
1255
+ throw new Error("empty file path");
1256
+ }
1257
+
1258
+ let resolvedPath;
1259
+ try {
1260
+ resolvedPath = fs.realpathSync(targetPath);
1261
+ } catch (error) {
1262
+ throw new Error(`cannot resolve file path: ${error.message}`);
1263
+ }
1264
+
1265
+ let stat;
1266
+ try {
1267
+ stat = fs.statSync(resolvedPath);
1268
+ } catch (error) {
1269
+ throw new Error(`cannot stat file: ${error.message}`);
1270
+ }
1271
+ if (!stat.isFile()) {
1272
+ throw new Error("path is not a regular file");
1273
+ }
1274
+
1275
+ const offset = Math.max(0, Number.parseInt(control.O || "0", 10) || 0);
1276
+ const requestedSize = Math.max(0, Number.parseInt(control.S || "0", 10) || 0);
1277
+ const size = requestedSize > 0 ? requestedSize : Math.max(0, stat.size - offset);
1278
+ const fd = fs.openSync(resolvedPath, fsConstants.O_RDONLY);
1279
+
1280
+ try {
1281
+ const buffer = Buffer.alloc(size);
1282
+ const bytesRead = fs.readSync(fd, buffer, 0, size, offset);
1283
+ return buffer.subarray(0, bytesRead);
1284
+ } finally {
1285
+ fs.closeSync(fd);
1286
+ if (deleteAfterRead && this._isSafeTempGraphicsPath(resolvedPath)) {
1287
+ try {
1288
+ fs.unlinkSync(resolvedPath);
1289
+ } catch (error) {
1290
+ traceLog("graphics-tempfile-unlink-failed", JSON.stringify({
1291
+ path: resolvedPath,
1292
+ message: error.message
1293
+ }));
1294
+ }
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ _isSafeTempGraphicsPath(filePath) {
1300
+ if (!filePath.includes("tty-graphics-protocol")) {
1301
+ return false;
1302
+ }
1303
+ return this.tempRoots.some((root) => filePath === root || filePath.startsWith(`${root}${path.sep}`));
1304
+ }
1305
+
1306
+ _sanitizeResponseMessage(message) {
1307
+ return String(message || "ERROR")
1308
+ .replace(/[^\x20-\x7e]/g, " ")
1309
+ .trim()
1310
+ .slice(0, 200) || "ERROR";
1311
+ }
1312
+ }
1313
+
1314
+ class Counter {
1315
+ constructor(timeoutSeconds, onTimeout) {
1316
+ this.timeoutMs = timeoutSeconds > 0 ? timeoutSeconds * 1000 : 0;
1317
+ this.onTimeout = onTimeout;
1318
+ this.connections = 0;
1319
+ this.timer = null;
1320
+ if (this.timeoutMs > 0) {
1321
+ this.arm();
1322
+ }
1323
+ }
1324
+
1325
+ arm() {
1326
+ this.disarm();
1327
+ this.timer = setTimeout(() => this.onTimeout(), this.timeoutMs);
1328
+ }
1329
+
1330
+ disarm() {
1331
+ if (this.timer) {
1332
+ clearTimeout(this.timer);
1333
+ this.timer = null;
1334
+ }
1335
+ }
1336
+
1337
+ add() {
1338
+ this.disarm();
1339
+ this.connections += 1;
1340
+ return this.connections;
1341
+ }
1342
+
1343
+ done() {
1344
+ this.connections = Math.max(0, this.connections - 1);
1345
+ if (this.connections === 0 && this.timeoutMs > 0) {
1346
+ this.arm();
1347
+ }
1348
+ return this.connections;
1349
+ }
1350
+
1351
+ count() {
1352
+ return this.connections;
1353
+ }
1354
+ }
1355
+
1356
+ class NodePtyBackend {
1357
+ constructor(options) {
1358
+ let nodePty;
1359
+ try {
1360
+ let preventSingleExe="./lib/"
1361
+ nodePty = require(preventSingleExe+"node-pty");
1362
+ } catch (error) {
1363
+ throw new Error("node-pty is required on Windows but is not installed");
1364
+ }
1365
+
1366
+ const system32 = path.join(process.env.windir, 'System32');
1367
+
1368
+ let ocmd = options.command
1369
+
1370
+ if(ocmd=='cmd' || ocmd=="powershell")
1371
+ ocmd+=".exe";
1372
+ else if(!fs.existsSync(ocmd) &&
1373
+ fs.existsSync(ocmd+'.exe'))
1374
+ ocmd+='.exe';
1375
+ else if(!fs.existsSync(path.join(system32,ocmd)) &&
1376
+ fs.existsSync(path.join(system32,ocmd+'.exe')))
1377
+ ocmd = path.join(system32,ocmd+'.exe');
1378
+
1379
+ options.command = ocmd ;
1380
+
1381
+ this.options = options;
1382
+ this.closed = false;
1383
+ this.exitHandlers = [];
1384
+ this.dataHandlers = [];
1385
+ this.closeTimer = null;
1386
+
1387
+ const env = {
1388
+ ...process.env,
1389
+ TERM: "xterm-256color",
1390
+ ...(options.headerEnv || {})
1391
+ };
1392
+
1393
+ this.proc = nodePty.spawn(options.command, options.argv, {
1394
+ name: "xterm-256color",
1395
+ cols: options.width || 80,
1396
+ rows: options.height || 24,
1397
+ cwd: process.cwd(),
1398
+ env
1399
+ });
1400
+
1401
+ forceWindowsPtyBinaryMode(this.proc);
1402
+
1403
+ this.pid = this.proc.pid;
1404
+ this.proc.onData((data) => {
1405
+ const chunk = Buffer.isBuffer(data) ? Buffer.from(data) : Buffer.from(data, "latin1");
1406
+ for (const handler of this.dataHandlers) {
1407
+ handler(chunk);
1408
+ }
1409
+ });
1410
+ this.proc.onExit(() => {
1411
+ this.emitExit();
1412
+ });
1413
+
1414
+ this.bootstrapWindowsUtf8();
1415
+ }
1416
+
1417
+ onData(handler) {
1418
+ this.dataHandlers.push(handler);
1419
+ }
1420
+
1421
+ onExit(handler) {
1422
+ this.exitHandlers.push(handler);
1423
+ }
1424
+
1425
+ emitExit() {
1426
+ if (this.closed) {
1427
+ return;
1428
+ }
1429
+ this.closed = true;
1430
+ for (const handler of this.exitHandlers) {
1431
+ handler();
1432
+ }
1433
+ }
1434
+
1435
+ write(buffer) {
1436
+ this.proc.write(buffer.toString("utf8"));
1437
+ }
1438
+
1439
+ writeBinary(buffer) {
1440
+ this.proc.write(buffer);
1441
+ }
1442
+
1443
+ resize(columns, rows) {
1444
+ this.proc.resize(columns, rows);
1445
+ }
1446
+
1447
+ kill(signal) {
1448
+ try {
1449
+ this.proc.kill(signal);
1450
+ } catch (error) {
1451
+ // ignore
1452
+ }
1453
+ }
1454
+
1455
+ close() {
1456
+ if (this.closeTimer) {
1457
+ clearTimeout(this.closeTimer);
1458
+ this.closeTimer = null;
1459
+ }
1460
+
1461
+ if (process.platform === "win32") {
1462
+ this.kill();
1463
+ return;
1464
+ }
1465
+
1466
+ this.kill(this.options.closeSignal);
1467
+ if (this.options.closeTimeout >= 0) {
1468
+ this.closeTimer = setTimeout(() => {
1469
+ this.kill("SIGKILL");
1470
+ }, this.options.closeTimeout * 1000);
1471
+ }
1472
+ }
1473
+
1474
+ bootstrapWindowsUtf8() {
1475
+ if (process.platform !== "win32") {
1476
+ return;
1477
+ }
1478
+
1479
+ const shell = detectWindowsShell(this.options.command);
1480
+ if (shell === "powershell" || shell === "pwsh") {
1481
+ setTimeout(() => {
1482
+ try {
1483
+ this.proc.write("[Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\r");
1484
+ this.proc.write("$OutputEncoding = [System.Text.UTF8Encoding]::new($false)\r");
1485
+ this.proc.write("chcp 65001 > $null\r");
1486
+ } catch (error) {
1487
+ // ignore
1488
+ }
1489
+ }, 30);
1490
+ return;
1491
+ }
1492
+
1493
+ if (shell === "cmd") {
1494
+ setTimeout(() => {
1495
+ try {
1496
+ this.proc.write("chcp 65001>nul\r");
1497
+ } catch (error) {
1498
+ // ignore
1499
+ }
1500
+ }, 30);
1501
+ }
1502
+ }
1503
+ }
1504
+
1505
+ class BunPtyBackend {
1506
+ constructor(options) {
1507
+ if (typeof Bun === "undefined" || typeof Bun.spawn !== "function") {
1508
+ throw new Error("Bun runtime is required on Linux/macOS for PTY support");
1509
+ }
1510
+
1511
+ this.options = options;
1512
+ this.closed = false;
1513
+ this.exitHandlers = [];
1514
+ this.dataHandlers = [];
1515
+ this.closeTimer = null;
1516
+
1517
+ const env = {
1518
+ ...process.env,
1519
+ TERM: "xterm-256color",
1520
+ ...(options.headerEnv || {})
1521
+ };
1522
+
1523
+ this.proc = Bun.spawn([options.command, ...options.argv], {
1524
+ cwd: process.cwd(),
1525
+ env,
1526
+ terminal: {
1527
+ cols: options.width || 80,
1528
+ rows: options.height || 24,
1529
+ name: "xterm-256color",
1530
+ data: (_terminal, data) => {
1531
+ const chunk = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
1532
+ for (const handler of this.dataHandlers) {
1533
+ handler(chunk);
1534
+ }
1535
+ },
1536
+ exit: () => {
1537
+ this.emitExit();
1538
+ }
1539
+ }
1540
+ });
1541
+
1542
+ this.terminal = this.proc.terminal;
1543
+ this.pid = this.proc.pid;
1544
+
1545
+ Promise.resolve(this.proc.exited)
1546
+ .catch(() => {})
1547
+ .finally(() => {
1548
+ this.emitExit();
1549
+ });
1550
+ }
1551
+
1552
+ onData(handler) {
1553
+ this.dataHandlers.push(handler);
1554
+ }
1555
+
1556
+ onExit(handler) {
1557
+ this.exitHandlers.push(handler);
1558
+ }
1559
+
1560
+ emitExit() {
1561
+ if (this.closed) {
1562
+ return;
1563
+ }
1564
+ this.closed = true;
1565
+ for (const handler of this.exitHandlers) {
1566
+ handler();
1567
+ }
1568
+ }
1569
+
1570
+ write(buffer) {
1571
+ this.terminal.write(buffer);
1572
+ }
1573
+
1574
+ writeBinary(buffer) {
1575
+ this.terminal.write(buffer);
1576
+ }
1577
+
1578
+ resize(columns, rows) {
1579
+ this.terminal.resize(columns, rows);
1580
+ }
1581
+
1582
+ kill(signal) {
1583
+ try {
1584
+ if (typeof this.proc.kill === "function") {
1585
+ this.proc.kill(signal);
1586
+ } else {
1587
+ this.terminal.close();
1588
+ }
1589
+ } catch (error) {
1590
+ try {
1591
+ this.terminal.close();
1592
+ } catch (closeError) {
1593
+ // ignore
1594
+ }
1595
+ }
1596
+ }
1597
+
1598
+ close() {
1599
+ if (this.closeTimer) {
1600
+ clearTimeout(this.closeTimer);
1601
+ this.closeTimer = null;
1602
+ }
1603
+
1604
+ this.kill(this.options.closeSignal);
1605
+ if (this.options.closeTimeout >= 0) {
1606
+ this.closeTimer = setTimeout(() => {
1607
+ this.kill("SIGKILL");
1608
+ }, this.options.closeTimeout * 1000);
1609
+ }
1610
+ }
1611
+ }
1612
+
1613
+ function createPtyBackend(options) {
1614
+ if (process.platform === "win32") {
1615
+ if (globalThis.Bun?.semver?.satisfies?.(
1616
+ globalThis.Bun?.version, ">=1.3.14"
1617
+ )) {
1618
+ return new BunPtyBackend(options);
1619
+ }
1620
+ return new NodePtyBackend(options);
1621
+ }
1622
+
1623
+ return new BunPtyBackend(options);
1624
+ }
1625
+
1626
+ class PtySession {
1627
+ constructor(options) {
1628
+ this.options = options;
1629
+ this.clients = new Map();
1630
+ this.writerWs = null;
1631
+ this.log = options.log;
1632
+ this.permitWrite = options.permitWrite;
1633
+ this.columns = options.width || 0;
1634
+ this.rows = options.height || 0;
1635
+ this.bufferSize = 64 * 1024;
1636
+ this.closed = false;
1637
+ this.backendExited = false;
1638
+ this.backendExitPromise = new Promise((resolve) => {
1639
+ this.resolveBackendExit = resolve;
1640
+ });
1641
+ this.backendTerminationPromise = null;
1642
+ this.reconnectTimer = null;
1643
+ this.reconnectToken = options.reconnect ? generateReconnectToken() : "";
1644
+ this.backend = createPtyBackend(options);
1645
+ this.supportsWindowsBridge = process.platform === "win32";
1646
+ this.cursorTracker = new CursorStateTracker();
1647
+ this.cursorTracker.setDimensions(this.columns || 80);
1648
+ this.kittyParser = new KittyGraphicsParser();
1649
+ this.windowsBridgeParser = this.supportsWindowsBridge ? new WindowsBridgeParser("incoming") : null;
1650
+ this.outgoingWindowsBridgeParser = this.supportsWindowsBridge ? new WindowsBridgeParser("outgoing") : null;
1651
+ this.windowsUploadRequests = new Map();
1652
+ this.windowsBridgeSeen = new Set();
1653
+ this.windowsBridgeSeenOrder = [];
1654
+ this.pendingEchoBytes = Buffer.alloc(0);
1655
+ this.zmodemBinaryBypass = false;
1656
+ this.zmodemDetectTail = Buffer.alloc(0);
1657
+ this.zmodemBinaryBytesSent = 0;
1658
+ this.zmodemBinaryBytesLogged = 0;
1659
+ this.zmodemOutputQueue = [];
1660
+ this.zmodemOutputInFlight = new Map();
1661
+ this.zmodemOutputSeq = 0;
1662
+ this.zmodemOutputStreamId = 0;
1663
+ this.zmodemOutputAckedSeq = -1;
1664
+ this.zmodemOutputWindow = 8;
1665
+ this.zmodemRetransmitTimer = null;
1666
+
1667
+ this.slaveVars = {
1668
+ command: options.command,
1669
+ argv: options.argv,
1670
+ pid: this.backend.pid
1671
+ };
1672
+ }
1673
+
1674
+ start() {
1675
+ this.backend.onData((raw) => {
1676
+ if (this.closed) {
1677
+ return;
1678
+ }
1679
+ if (this.shouldBypassOutputParsers(raw)) {
1680
+ this.sendChunk(MSG_OUTPUT, raw);
1681
+ return;
1682
+ }
1683
+ const parsed = this.kittyParser.consume(raw);
1684
+ for (const event of parsed.events) {
1685
+ if (event.kind === "kitty") {
1686
+ const cursorAtApc = this.cursorTracker.snapshot();
1687
+ const graphic = this.kittyParser.parsePacket(event.packet, cursorAtApc);
1688
+ if (!graphic) {
1689
+ continue;
1690
+ }
1691
+ kittyPlacementLog("server-kitty-event", {
1692
+ cursorAtApc,
1693
+ kind: graphic.kind,
1694
+ control: graphic.control || null,
1695
+ delete: graphic.delete || null,
1696
+ imageId: graphic.image?.id || null,
1697
+ });
1698
+ traceLog("graphics", JSON.stringify({
1699
+ kind: graphic.kind,
1700
+ control: graphic.control || null,
1701
+ imageId: graphic.image ? graphic.image.id : null
1702
+ }));
1703
+ if (graphic.kind === "placement") {
1704
+ this.cursorTracker.applyKittyPlacement(graphic.control);
1705
+ }
1706
+ this.send(MSG_KITTY_GRAPHICS, JSON.stringify(graphic));
1707
+ const syntheticCursorMotion = this.buildKittyCursorMotion(graphic);
1708
+ if (syntheticCursorMotion) {
1709
+ this.sendChunk(MSG_OUTPUT, Buffer.from(syntheticCursorMotion, "utf8"));
1710
+ }
1711
+ const ack = this.kittyParser.acknowledge(graphic);
1712
+ if (ack) {
1713
+ traceLog("graphics-ack", JSON.stringify(graphic.control || {}), JSON.stringify(ack));
1714
+ this.backend.write(Buffer.from(ack, "utf8"));
1715
+ }
1716
+ continue;
1717
+ }
1718
+
1719
+ const chunk = event.data;
1720
+ const filteredChunk = this.filterEchoedTerminalReplies(chunk);
1721
+ if (filteredChunk.length === 0) {
1722
+ continue;
1723
+ }
1724
+ if (!this.supportsWindowsBridge) {
1725
+ const cursorBefore = this.cursorTracker.snapshot();
1726
+ this.cursorTracker.consume(filteredChunk);
1727
+ const cursorAfter = this.cursorTracker.snapshot();
1728
+ const controls = cursorControls(filteredChunk);
1729
+ if (controls.length > 0) {
1730
+ kittyPlacementLog("server-plain-cursor-controls", {
1731
+ bytes: filteredChunk.length,
1732
+ cursorBefore,
1733
+ cursorAfter,
1734
+ controls,
1735
+ });
1736
+ }
1737
+ this.sendChunk(MSG_OUTPUT, filteredChunk);
1738
+ continue;
1739
+ }
1740
+ const bridged = this.windowsBridgeParser.consume(filteredChunk);
1741
+ if (bridged.plain.length > 0) {
1742
+ const cursorBefore = this.cursorTracker.snapshot();
1743
+ this.cursorTracker.consume(bridged.plain);
1744
+ const cursorAfter = this.cursorTracker.snapshot();
1745
+ const controls = cursorControls(bridged.plain);
1746
+ if (controls.length > 0) {
1747
+ kittyPlacementLog("server-plain-cursor-controls", {
1748
+ bytes: bridged.plain.length,
1749
+ cursorBefore,
1750
+ cursorAfter,
1751
+ controls,
1752
+ windowsBridge: true,
1753
+ });
1754
+ }
1755
+ this.sendChunk(MSG_OUTPUT, bridged.plain);
1756
+ }
1757
+ for (const control of bridged.controls) {
1758
+ this.handleWindowsBridgeControl(control);
1759
+ }
1760
+ }
1761
+ });
1762
+
1763
+ this.backend.onExit(() => {
1764
+ this.backendExited = true;
1765
+ this.resolveBackendExit();
1766
+ this.terminate(1000, "process exited", { skipBackendClose: true });
1767
+ });
1768
+
1769
+ this.attachWebSocket(
1770
+ this.options.ws,
1771
+ this.options.remoteAddr || "",
1772
+ this.options.readOnly === true
1773
+ );
1774
+ }
1775
+
1776
+ shouldBypassOutputParsers(raw) {
1777
+ if (this.zmodemBinaryBypass) {
1778
+ return true;
1779
+ }
1780
+
1781
+ const data = this.zmodemDetectTail.length > 0
1782
+ ? Buffer.concat([this.zmodemDetectTail, raw])
1783
+ : raw;
1784
+ this.zmodemDetectTail = data.subarray(Math.max(0, data.length - 8));
1785
+
1786
+ const binaryHeaderA = Buffer.from([0x2a, 0x18, 0x41]);
1787
+ const binaryHeaderC = Buffer.from([0x2a, 0x18, 0x43]);
1788
+ const hexHeader = Buffer.from([0x2a, 0x2a, 0x18, 0x42]);
1789
+ if (
1790
+ data.indexOf(hexHeader) !== -1 ||
1791
+ data.indexOf(binaryHeaderA) !== -1 ||
1792
+ data.indexOf(binaryHeaderC) !== -1
1793
+ ) {
1794
+ this.zmodemBinaryBypass = true;
1795
+ this.resetZmodemOutputFlow();
1796
+ this.log("ZMODEM detected: bypassing server-side output parsers for this session");
1797
+ return true;
1798
+ }
1799
+
1800
+ return false;
1801
+ }
1802
+
1803
+ attachWebSocket(ws, remoteAddr = "", readOnly = false) {
1804
+ if (!ws || this.closed) {
1805
+ return false;
1806
+ }
1807
+
1808
+ if (this.reconnectTimer) {
1809
+ clearTimeout(this.reconnectTimer);
1810
+ this.reconnectTimer = null;
1811
+ }
1812
+
1813
+ this.clients.set(ws, {
1814
+ remoteAddr,
1815
+ encoding: "null",
1816
+ readOnly,
1817
+ sendQueue: Promise.resolve()
1818
+ });
1819
+ if (this.permitWrite && !readOnly && !this.writerWs) {
1820
+ this.writerWs = ws;
1821
+ }
1822
+ this.sendSessionMetadata(ws, remoteAddr);
1823
+ this.startWebSocketHandlers(ws);
1824
+ this.notifyRoles();
1825
+ return true;
1826
+ }
1827
+
1828
+ canResume(token) {
1829
+ return Boolean(
1830
+ token &&
1831
+ this.options.reconnect &&
1832
+ !this.closed &&
1833
+ !this.backendExited &&
1834
+ token === this.reconnectToken
1835
+ );
1836
+ }
1837
+
1838
+ sendSessionMetadata(ws, remoteAddr = "") {
1839
+ const title = renderTemplate(
1840
+ this.options.titleFormat,
1841
+ buildTitleVariables(
1842
+ this.options.command,
1843
+ this.options.argv,
1844
+ this.options.hostname,
1845
+ remoteAddr,
1846
+ this.slaveVars
1847
+ )
1848
+ );
1849
+
1850
+ this.send(MSG_SET_WINDOW_TITLE, title, ws);
1851
+ this.send(MSG_SET_BUFFER_SIZE, JSON.stringify(this.bufferSize), ws);
1852
+
1853
+ if (this.options.reconnect) {
1854
+ this.send(MSG_SET_RECONNECT, JSON.stringify({
1855
+ time: this.options.reconnectTime,
1856
+ token: this.reconnectToken,
1857
+ pid: this.backend.pid
1858
+ }), ws);
1859
+ }
1860
+ }
1861
+
1862
+ isWriter(ws) {
1863
+ return this.writerSocket() === ws;
1864
+ }
1865
+
1866
+ writerSocket() {
1867
+ return this.writerWs && this.clients.has(this.writerWs)
1868
+ ? this.writerWs
1869
+ : null;
1870
+ }
1871
+
1872
+ notifyRoles() {
1873
+ for (const ws of this.clients.keys()) {
1874
+ const writable = this.isWriter(ws);
1875
+ this.send(MSG_SET_ROLE, JSON.stringify({
1876
+ role: writable ? "writer" : "viewer",
1877
+ writable
1878
+ }), ws);
1879
+ }
1880
+ }
1881
+
1882
+ armReconnectTimeout() {
1883
+ if (!this.options.reconnect || this.closed || this.backendExited) {
1884
+ this.terminate();
1885
+ return;
1886
+ }
1887
+
1888
+ if (this.options.reconnectTime < 0) {
1889
+ return;
1890
+ }
1891
+
1892
+ const timeoutMs = this.options.reconnectTime * 1000;
1893
+ if (timeoutMs === 0) {
1894
+ this.terminate(1001, "reconnect timeout");
1895
+ return;
1896
+ }
1897
+
1898
+ this.reconnectTimer = setTimeout(() => {
1899
+ this.reconnectTimer = null;
1900
+ this.terminate(1001, "reconnect timeout");
1901
+ }, timeoutMs);
1902
+ }
1903
+
1904
+ handleWindowsBridgeControl(control) {
1905
+ if (!control || typeof control !== "object") {
1906
+ return;
1907
+ }
1908
+
1909
+ if (this.shouldSkipWindowsBridgeControl(control)) {
1910
+ return;
1911
+ }
1912
+
1913
+ if (control.op === "image_file") {
1914
+ this.handleWindowsImageFile(control);
1915
+ return;
1916
+ }
1917
+
1918
+ if (control.op === "upload_request") {
1919
+ const requestId = String(control.requestId || "");
1920
+ if (this.windowsUploadRequests.has(requestId)) {
1921
+ traceLog("windows-upload-duplicate", JSON.stringify({ requestId }));
1922
+ return;
1923
+ }
1924
+ this.windowsUploadRequests.set(requestId, {
1925
+ requestId,
1926
+ targetDir: String(control.targetDir || ""),
1927
+ files: new Map()
1928
+ });
1929
+ this.send(MSG_WINDOWS_BRIDGE, JSON.stringify(control));
1930
+ return;
1931
+ }
1932
+
1933
+ if (control.op === "download_request") {
1934
+ void this.handleWindowsDownloadRequest(control);
1935
+ return;
1936
+ }
1937
+
1938
+ this.send(MSG_WINDOWS_BRIDGE, JSON.stringify(control));
1939
+ }
1940
+
1941
+ shouldSkipWindowsBridgeControl(control) {
1942
+ const key = this.getWindowsBridgeControlKey(control);
1943
+ if (!key) {
1944
+ return false;
1945
+ }
1946
+
1947
+ if (this.windowsBridgeSeen.has(key)) {
1948
+ traceLog("windows-bridge-duplicate", JSON.stringify({
1949
+ key,
1950
+ op: String(control.op || "")
1951
+ }));
1952
+ return true;
1953
+ }
1954
+
1955
+ this.windowsBridgeSeen.add(key);
1956
+ this.windowsBridgeSeenOrder.push(key);
1957
+ while (this.windowsBridgeSeenOrder.length > 2048) {
1958
+ const oldest = this.windowsBridgeSeenOrder.shift();
1959
+ if (oldest) {
1960
+ this.windowsBridgeSeen.delete(oldest);
1961
+ }
1962
+ }
1963
+ return false;
1964
+ }
1965
+
1966
+ getWindowsBridgeControlKey(control) {
1967
+ const op = String(control.op || "");
1968
+ if (!op) {
1969
+ return "";
1970
+ }
1971
+
1972
+ if (op === "upload_request" || op === "download_request") {
1973
+ const requestId = String(control.requestId || "");
1974
+ return requestId ? `${op}:${requestId}` : "";
1975
+ }
1976
+
1977
+ if (op === "image_file") {
1978
+ const imageId = String(control.id || "");
1979
+ const targetPath = String(control.path || "");
1980
+ if (imageId) {
1981
+ return `${op}:${imageId}`;
1982
+ }
1983
+ if (targetPath) {
1984
+ return `${op}:${targetPath}`;
1985
+ }
1986
+ return "";
1987
+ }
1988
+
1989
+ return "";
1990
+ }
1991
+
1992
+ handleWindowsImageFile(control) {
1993
+ const targetPath = String(control.path || "");
1994
+ if (!targetPath) {
1995
+ return;
1996
+ }
1997
+
1998
+ let binary;
1999
+ try {
2000
+ binary = fs.readFileSync(targetPath);
2001
+ } catch (error) {
2002
+ traceLog("windows-image-file-error", JSON.stringify({
2003
+ path: targetPath,
2004
+ message: error.message
2005
+ }));
2006
+ return;
2007
+ } finally {
2008
+ if (control.deleteAfterRead) {
2009
+ try {
2010
+ fs.unlinkSync(targetPath);
2011
+ } catch {}
2012
+ }
2013
+ }
2014
+
2015
+ this.emitWindowsImageGraphic({
2016
+ id: String(control.id || Date.now()),
2017
+ cols: control.cols,
2018
+ rows: control.rows,
2019
+ width: control.width,
2020
+ height: control.height,
2021
+ mime: control.mime,
2022
+ data: binary.toString("base64")
2023
+ });
2024
+ }
2025
+
2026
+ emitWindowsImageGraphic(image) {
2027
+ const imageId = String(image.id || Date.now());
2028
+ const cols = Math.max(1, Number.parseInt(String(image.cols || 0), 10) || 1);
2029
+ const rows = Math.max(1, Number.parseInt(String(image.rows || 0), 10) || 1);
2030
+ const width = Math.max(1, Number.parseInt(String(image.width || 0), 10) || 1);
2031
+ const height = Math.max(1, Number.parseInt(String(image.height || 0), 10) || 1);
2032
+ const graphic = {
2033
+ kind: "placement",
2034
+ cursor: this.cursorTracker.snapshot(),
2035
+ control: {
2036
+ i: imageId,
2037
+ c: String(cols),
2038
+ r: String(rows),
2039
+ U: image.mime || "image/png",
2040
+ q: "2"
2041
+ },
2042
+ image: {
2043
+ id: imageId,
2044
+ format: 100,
2045
+ width,
2046
+ height,
2047
+ data: String(image.data || "")
2048
+ }
2049
+ };
2050
+ this.send(MSG_KITTY_GRAPHICS, JSON.stringify(graphic));
2051
+ const syntheticCursorMotion = this.buildKittyCursorMotion(graphic);
2052
+ if (syntheticCursorMotion) {
2053
+ this.sendChunk(MSG_OUTPUT, Buffer.from(syntheticCursorMotion, "utf8"));
2054
+ }
2055
+ }
2056
+
2057
+ async handleWindowsDownloadRequest(control) {
2058
+ const requestId = String(control.requestId || "");
2059
+ const files = Array.isArray(control.files) ? control.files : [];
2060
+
2061
+ try {
2062
+ for (const file of files) {
2063
+ const fullPath = String(file.fullPath || "");
2064
+ const name = basenameSafe(file.name || path.basename(fullPath));
2065
+ const stats = fs.statSync(fullPath);
2066
+ if (!stats.isFile()) {
2067
+ throw new Error(`not a regular file: ${fullPath}`);
2068
+ }
2069
+
2070
+ this.send(MSG_WINDOWS_BRIDGE, JSON.stringify({
2071
+ op: "download_start",
2072
+ requestId,
2073
+ name,
2074
+ size: stats.size,
2075
+ mtime: stats.mtime.toISOString()
2076
+ }));
2077
+
2078
+ const fd = fs.openSync(fullPath, "r");
2079
+ const buffer = Buffer.allocUnsafe(48 * 1024);
2080
+ try {
2081
+ for (;;) {
2082
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
2083
+ if (bytesRead === 0) {
2084
+ break;
2085
+ }
2086
+ this.send(MSG_WINDOWS_BRIDGE, JSON.stringify({
2087
+ op: "download_chunk",
2088
+ requestId,
2089
+ name,
2090
+ data: buffer.subarray(0, bytesRead).toString("base64")
2091
+ }));
2092
+ }
2093
+ } finally {
2094
+ fs.closeSync(fd);
2095
+ }
2096
+
2097
+ this.send(MSG_WINDOWS_BRIDGE, JSON.stringify({
2098
+ op: "download_end",
2099
+ requestId,
2100
+ name
2101
+ }));
2102
+ }
2103
+
2104
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage({
2105
+ op: "download_finish",
2106
+ requestId
2107
+ }), "utf8"));
2108
+ } catch (error) {
2109
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage({
2110
+ op: "download_error",
2111
+ requestId,
2112
+ message: error.message
2113
+ }), "utf8"));
2114
+ }
2115
+ }
2116
+
2117
+ handleBrowserWindowsBridgeMessage(message) {
2118
+ const requestId = String(message && message.requestId || "");
2119
+ const request = this.windowsUploadRequests.get(requestId);
2120
+ if (!request) {
2121
+ return false;
2122
+ }
2123
+
2124
+ try {
2125
+ if (message.op === "upload_cancel") {
2126
+ this.windowsUploadRequests.delete(requestId);
2127
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage({
2128
+ op: "upload_cancel",
2129
+ requestId
2130
+ }), "utf8"));
2131
+ return true;
2132
+ }
2133
+
2134
+ if (message.op === "upload_start") {
2135
+ const name = basenameSafe(message.name);
2136
+ const outputPath = path.join(request.targetDir, name);
2137
+ const fd = fs.openSync(outputPath, "w", 0o644);
2138
+ request.files.set(name, {
2139
+ fd,
2140
+ outputPath,
2141
+ mtime: message.mtime
2142
+ });
2143
+ return true;
2144
+ }
2145
+
2146
+ if (message.op === "upload_chunk") {
2147
+ const name = basenameSafe(message.name);
2148
+ const entry = request.files.get(name);
2149
+ if (!entry) {
2150
+ throw new Error(`missing upload target for ${name}`);
2151
+ }
2152
+ fs.writeSync(entry.fd, Buffer.from(String(message.data || ""), "base64"));
2153
+ return true;
2154
+ }
2155
+
2156
+ if (message.op === "upload_end") {
2157
+ const name = basenameSafe(message.name);
2158
+ const entry = request.files.get(name);
2159
+ if (!entry) {
2160
+ throw new Error(`missing upload target for ${name}`);
2161
+ }
2162
+ fs.closeSync(entry.fd);
2163
+ entry.fd = undefined;
2164
+ if (entry.mtime) {
2165
+ const mtime = new Date(entry.mtime);
2166
+ if (!Number.isNaN(mtime.valueOf())) {
2167
+ fs.utimesSync(entry.outputPath, new Date(), mtime);
2168
+ }
2169
+ }
2170
+ return true;
2171
+ }
2172
+
2173
+ if (message.op === "upload_finish") {
2174
+ this.windowsUploadRequests.delete(requestId);
2175
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage({
2176
+ op: "upload_finish",
2177
+ requestId
2178
+ }), "utf8"));
2179
+ return true;
2180
+ }
2181
+ } catch (error) {
2182
+ this.windowsUploadRequests.delete(requestId);
2183
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage({
2184
+ op: "upload_error",
2185
+ requestId,
2186
+ message: error.message
2187
+ }), "utf8"));
2188
+ return true;
2189
+ }
2190
+
2191
+ return false;
2192
+ }
2193
+
2194
+ sendChunk(type, raw) {
2195
+ if (type === MSG_OUTPUT && this.zmodemBinaryBypass) {
2196
+ this.sendZmodemOutput(raw);
2197
+ return;
2198
+ }
2199
+
2200
+ if (type === MSG_OUTPUT && this.supportsWindowsBridge) {
2201
+ const bridged = this.outgoingWindowsBridgeParser.consume(raw);
2202
+ for (const control of bridged.controls) {
2203
+ this.handleWindowsBridgeControl(control);
2204
+ }
2205
+ raw = bridged.plain;
2206
+ if (raw.length === 0) {
2207
+ return;
2208
+ }
2209
+ }
2210
+
2211
+ const maxChunkSize = Math.floor((this.bufferSize - 1) / 4) * 3;
2212
+ for (let offset = 0; offset < raw.length; offset += maxChunkSize) {
2213
+ const chunk = raw.subarray(offset, Math.min(offset + maxChunkSize, raw.length));
2214
+ this.send(type, chunk.toString("base64"));
2215
+ }
2216
+ }
2217
+
2218
+ sendZmodemOutput(raw) {
2219
+ this.zmodemBinaryBytesSent += raw.length;
2220
+ if (this.zmodemBinaryBytesSent - this.zmodemBinaryBytesLogged >= 16 * 1024 * 1024) {
2221
+ this.zmodemBinaryBytesLogged = this.zmodemBinaryBytesSent;
2222
+ this.log(`ZMODEM text websocket output ${this.zmodemBinaryBytesSent.toLocaleString()} bytes`);
2223
+ }
2224
+
2225
+ const maxChunkSize = 3 * 1024;
2226
+ for (let offset = 0; offset < raw.length; offset += maxChunkSize) {
2227
+ const chunk = raw.subarray(offset, Math.min(offset + maxChunkSize, raw.length));
2228
+ this.zmodemOutputQueue.push({
2229
+ seq: this.zmodemOutputSeq,
2230
+ payload: chunk.toString("base64")
2231
+ });
2232
+ this.zmodemOutputSeq += 1;
2233
+ }
2234
+ this.flushZmodemOutput();
2235
+ }
2236
+
2237
+ resetZmodemOutputFlow() {
2238
+ this.zmodemOutputQueue = [];
2239
+ this.zmodemOutputInFlight = new Map();
2240
+ this.zmodemOutputSeq = 0;
2241
+ this.zmodemOutputStreamId += 1;
2242
+ this.zmodemOutputAckedSeq = -1;
2243
+ if (this.zmodemRetransmitTimer) {
2244
+ clearTimeout(this.zmodemRetransmitTimer);
2245
+ this.zmodemRetransmitTimer = null;
2246
+ }
2247
+ }
2248
+
2249
+ flushZmodemOutput() {
2250
+ const targetWs = this.writerWs && this.writerWs.readyState === WebSocket.OPEN
2251
+ ? this.writerWs
2252
+ : this.clients.keys().next().value;
2253
+ if (!targetWs || targetWs.readyState !== WebSocket.OPEN) {
2254
+ return;
2255
+ }
2256
+
2257
+ while (this.zmodemOutputInFlight.size < this.zmodemOutputWindow && this.zmodemOutputQueue.length > 0) {
2258
+ const item = this.zmodemOutputQueue.shift();
2259
+ this.zmodemOutputInFlight.set(item.seq, item);
2260
+ this.sendZmodemOutputItem(item, targetWs);
2261
+ }
2262
+ this.scheduleZmodemRetransmit();
2263
+ }
2264
+
2265
+ sendZmodemOutputItem(item, targetWs) {
2266
+ this.send(MSG_ZMODEM_OUTPUT, `${this.zmodemOutputStreamId}:${item.seq}:${item.payload}`, targetWs);
2267
+ }
2268
+
2269
+ scheduleZmodemRetransmit() {
2270
+ if (this.zmodemRetransmitTimer || this.zmodemOutputInFlight.size === 0) {
2271
+ return;
2272
+ }
2273
+ this.zmodemRetransmitTimer = setTimeout(() => {
2274
+ this.zmodemRetransmitTimer = null;
2275
+ if (!this.zmodemBinaryBypass || this.zmodemOutputInFlight.size === 0) {
2276
+ return;
2277
+ }
2278
+ const targetWs = this.writerWs && this.writerWs.readyState === WebSocket.OPEN
2279
+ ? this.writerWs
2280
+ : this.clients.keys().next().value;
2281
+ if (!targetWs || targetWs.readyState !== WebSocket.OPEN) {
2282
+ return;
2283
+ }
2284
+ for (const item of this.zmodemOutputInFlight.values()) {
2285
+ this.sendZmodemOutputItem(item, targetWs);
2286
+ }
2287
+ this.scheduleZmodemRetransmit();
2288
+ }, 750);
2289
+ }
2290
+
2291
+ filterEchoedTerminalReplies(raw) {
2292
+ const needle = Buffer.from("^[[?1;2c", "ascii");
2293
+ const combined = this.pendingEchoBytes.length > 0 ? Buffer.concat([this.pendingEchoBytes, raw]) : raw;
2294
+ let searchIndex = 0;
2295
+ const segments = [];
2296
+
2297
+ while (searchIndex < combined.length) {
2298
+ const matchIndex = combined.indexOf(needle, searchIndex);
2299
+ if (matchIndex === -1) {
2300
+ break;
2301
+ }
2302
+ if (matchIndex > searchIndex) {
2303
+ segments.push(combined.subarray(searchIndex, matchIndex));
2304
+ }
2305
+ searchIndex = matchIndex + needle.length;
2306
+ }
2307
+
2308
+ const remaining = combined.subarray(searchIndex);
2309
+ let suffixLength = 0;
2310
+ const maxSuffix = Math.min(needle.length - 1, remaining.length);
2311
+ for (let len = maxSuffix; len > 0; len -= 1) {
2312
+ if (needle.subarray(0, len).equals(remaining.subarray(remaining.length - len))) {
2313
+ suffixLength = len;
2314
+ break;
2315
+ }
2316
+ }
2317
+
2318
+ const flushable = suffixLength > 0 ? remaining.subarray(0, remaining.length - suffixLength) : remaining;
2319
+ if (flushable.length > 0) {
2320
+ segments.push(flushable);
2321
+ }
2322
+ this.pendingEchoBytes = suffixLength > 0 ? remaining.subarray(remaining.length - suffixLength) : Buffer.alloc(0);
2323
+
2324
+ if (segments.length === 0) {
2325
+ return Buffer.alloc(0);
2326
+ }
2327
+ if (segments.length === 1) {
2328
+ return Buffer.from(segments[0]);
2329
+ }
2330
+ return Buffer.concat(segments);
2331
+ }
2332
+
2333
+ buildKittyCursorMotion(graphic) {
2334
+ if (!graphic || graphic.kind !== "placement") {
2335
+ return "";
2336
+ }
2337
+ const control = graphic.control || {};
2338
+ const cursor = graphic.cursor || { col: 1 };
2339
+ if (control.C === "1") {
2340
+ return "";
2341
+ }
2342
+ const cols = Math.max(0, Number.parseInt(control.c || "0", 10) || 0);
2343
+ const rows = Math.max(0, Number.parseInt(control.r || "0", 10) || 0);
2344
+ const targetCol = Math.max(1, (Number.parseInt(cursor.col || "1", 10) || 1) + cols);
2345
+
2346
+ if (rows > 0) {
2347
+ return "\r\n".repeat(rows) + `\u001b[${targetCol}G`;
2348
+ }
2349
+
2350
+ if (cols > 0) {
2351
+ return `\u001b[${targetCol}G`;
2352
+ }
2353
+
2354
+ return "";
2355
+ }
2356
+
2357
+ startWebSocketHandlers(ws) {
2358
+ ws.on("message", (message, isBinary) => {
2359
+ if (this.closed || !this.clients.has(ws) || isBinary) {
2360
+ return;
2361
+ }
2362
+ try {
2363
+ this.handleClientMessage(ws, message.toString("utf8"));
2364
+ } catch (error) {
2365
+ this.log(`WS session error: ${error.message}`);
2366
+ try {
2367
+ ws.close(1011, error.message);
2368
+ } catch (closeError) {
2369
+ // The client socket is already gone.
2370
+ }
2371
+ }
2372
+ });
2373
+
2374
+ const handleSocketGone = () => {
2375
+ if (!this.clients.has(ws) || this.closed) {
2376
+ return;
2377
+ }
2378
+ if (this.writerWs === ws) {
2379
+ this.writerWs = null;
2380
+ }
2381
+ this.clients.delete(ws);
2382
+ if (this.clients.size > 0) {
2383
+ this.notifyRoles();
2384
+ return;
2385
+ }
2386
+ if (this.options.reconnect && !this.backendExited) {
2387
+ this.armReconnectTimeout();
2388
+ return;
2389
+ }
2390
+ this.terminate();
2391
+ };
2392
+
2393
+ ws.once("close", handleSocketGone);
2394
+ ws.once("error", handleSocketGone);
2395
+ }
2396
+
2397
+ handleClientMessage(ws, data) {
2398
+ if (!data || data.length === 0) {
2399
+ throw new Error("unexpected zero length read from master");
2400
+ }
2401
+
2402
+ const type = data[0];
2403
+ const payload = data.slice(1);
2404
+ const client = this.clients.get(ws);
2405
+ if (!client) {
2406
+ return;
2407
+ }
2408
+
2409
+ switch (type) {
2410
+ case MSG_INPUT:
2411
+ if (!this.isWriter(ws) || payload.length === 0) {
2412
+ return;
2413
+ }
2414
+ if (client.encoding === "base64") {
2415
+ this.backend.write(Buffer.from(payload, "base64"));
2416
+ } else {
2417
+ this.backend.write(Buffer.from(payload, "utf8"));
2418
+ }
2419
+ break;
2420
+ case MSG_INPUT_BINARY:
2421
+ if (!this.isWriter(ws) || payload.length === 0) {
2422
+ return;
2423
+ }
2424
+ this.backend.writeBinary(Buffer.from(payload, "base64"));
2425
+ break;
2426
+ case MSG_PING:
2427
+ this.send(MSG_PONG, "", ws);
2428
+ break;
2429
+ case MSG_KITTY_DEBUG:
2430
+ if (!KITTY_TRACE) {
2431
+ break;
2432
+ }
2433
+ try {
2434
+ kittyPlacementLog("browser", JSON.parse(payload));
2435
+ } catch (error) {
2436
+ kittyPlacementLog("browser-malformed", { payload });
2437
+ }
2438
+ break;
2439
+ case MSG_WINDOWS_BRIDGE: {
2440
+ if (!this.supportsWindowsBridge || !this.isWriter(ws) || payload.length === 0) {
2441
+ return;
2442
+ }
2443
+ let message;
2444
+ try {
2445
+ message = JSON.parse(payload);
2446
+ } catch (error) {
2447
+ throw new Error("received malformed data for windows bridge");
2448
+ }
2449
+ if (this.handleBrowserWindowsBridgeMessage(message)) {
2450
+ break;
2451
+ }
2452
+ this.backend.write(Buffer.from(encodeWindowsBridgeMessage(message), "utf8"));
2453
+ break;
2454
+ }
2455
+ case MSG_SET_ENCODING:
2456
+ if (payload === "base64" || payload === "null") {
2457
+ client.encoding = payload;
2458
+ }
2459
+ break;
2460
+ case MSG_ZMODEM_MODE:
2461
+ if (!this.isWriter(ws)) {
2462
+ return;
2463
+ }
2464
+ {
2465
+ const enabled = payload === "1";
2466
+ const wasEnabled = this.zmodemBinaryBypass;
2467
+ this.zmodemBinaryBypass = enabled;
2468
+ if (this.zmodemBinaryBypass && !wasEnabled) {
2469
+ this.zmodemBinaryBytesSent = 0;
2470
+ this.zmodemBinaryBytesLogged = 0;
2471
+ this.resetZmodemOutputFlow();
2472
+ } else if (!this.zmodemBinaryBypass) {
2473
+ this.resetZmodemOutputFlow();
2474
+ }
2475
+ this.log(`ZMODEM parser bypass ${this.zmodemBinaryBypass ? "enabled" : "disabled"} by browser`);
2476
+ }
2477
+ break;
2478
+ case MSG_ZMODEM_ACK: {
2479
+ if (!this.isWriter(ws)) {
2480
+ return;
2481
+ }
2482
+ const seq = Number.parseInt(payload, 10);
2483
+ if (!Number.isFinite(seq) || seq <= this.zmodemOutputAckedSeq) {
2484
+ return;
2485
+ }
2486
+ for (const key of this.zmodemOutputInFlight.keys()) {
2487
+ if (key <= seq) {
2488
+ this.zmodemOutputInFlight.delete(key);
2489
+ }
2490
+ }
2491
+ this.zmodemOutputAckedSeq = seq;
2492
+ this.flushZmodemOutput();
2493
+ break;
2494
+ }
2495
+ case MSG_RESIZE_TERMINAL: {
2496
+ if (!this.isWriter(ws) || (this.columns !== 0 && this.rows !== 0)) {
2497
+ return;
2498
+ }
2499
+ let args;
2500
+ try {
2501
+ args = JSON.parse(payload);
2502
+ } catch (error) {
2503
+ throw new Error("received malformed data for terminal resize");
2504
+ }
2505
+ const columns = this.columns || Number.parseInt(String(args.columns), 10);
2506
+ const rows = this.rows || Number.parseInt(String(args.rows), 10);
2507
+ if (Number.isFinite(columns) && Number.isFinite(rows)) {
2508
+ this.cursorTracker.setDimensions(columns);
2509
+ this.backend.resize(columns, rows);
2510
+ }
2511
+ break;
2512
+ }
2513
+ default:
2514
+ throw new Error(`unknown message type ${type}`);
2515
+ }
2516
+ }
2517
+
2518
+ send(type, payload, targetWs = null) {
2519
+ if (targetWs) {
2520
+ this.queueSend(targetWs, type + payload);
2521
+ return;
2522
+ }
2523
+
2524
+ for (const ws of this.clients.keys()) {
2525
+ this.queueSend(ws, type + payload);
2526
+ }
2527
+ }
2528
+
2529
+ queueSend(ws, payload, binary = false) {
2530
+ const client = this.clients.get(ws);
2531
+ if (!client || ws.readyState !== WebSocket.OPEN) {
2532
+ return;
2533
+ }
2534
+
2535
+ const sendOne = async () => {
2536
+ await new Promise((resolve, reject) => {
2537
+ try {
2538
+ if (binary) {
2539
+ ws.send(payload, { binary: true }, (error) => {
2540
+ if (error) {
2541
+ reject(error);
2542
+ return;
2543
+ }
2544
+ resolve();
2545
+ });
2546
+ } else {
2547
+ ws.send(payload, (error) => {
2548
+ if (error) {
2549
+ reject(error);
2550
+ return;
2551
+ }
2552
+ resolve();
2553
+ });
2554
+ }
2555
+ } catch (error) {
2556
+ reject(error);
2557
+ }
2558
+ });
2559
+ };
2560
+
2561
+ client.sendQueue = client.sendQueue.then(sendOne, sendOne);
2562
+ client.sendQueue.catch((error) => {
2563
+ this.log(`WebSocket send failed: ${error && error.message ? error.message : error}`);
2564
+ });
2565
+ }
2566
+
2567
+ terminateBackend(gracePeriodMs = 5000) {
2568
+ if (this.backendTerminationPromise) {
2569
+ return this.backendTerminationPromise;
2570
+ }
2571
+
2572
+ this.backendTerminationPromise = (async () => {
2573
+ if (this.backendExited) {
2574
+ return { forced: false };
2575
+ }
2576
+
2577
+ const waitForExit = async (timeoutMs) => {
2578
+ let timer = null;
2579
+ const timedOut = new Promise((resolve) => {
2580
+ timer = setTimeout(() => resolve(false), timeoutMs);
2581
+ });
2582
+ const exited = this.backendExitPromise.then(() => true);
2583
+ const result = await Promise.race([exited, timedOut]);
2584
+ if (timer) {
2585
+ clearTimeout(timer);
2586
+ }
2587
+ return result;
2588
+ };
2589
+
2590
+ if (process.platform === "win32") {
2591
+ this.backend.kill();
2592
+ if (await waitForExit(gracePeriodMs)) {
2593
+ return { forced: true };
2594
+ }
2595
+ throw new Error("process did not exit after PTY kill");
2596
+ }
2597
+
2598
+ this.backend.kill("SIGTERM");
2599
+ if (await waitForExit(gracePeriodMs)) {
2600
+ return { forced: false };
2601
+ }
2602
+
2603
+ this.backend.kill("SIGKILL");
2604
+ if (await waitForExit(gracePeriodMs)) {
2605
+ return { forced: true };
2606
+ }
2607
+
2608
+ throw new Error("process did not exit after SIGKILL");
2609
+ })();
2610
+
2611
+ return this.backendTerminationPromise;
2612
+ }
2613
+
2614
+ terminate(code, reason, { skipBackendClose = false } = {}) {
2615
+ if (this.closed) {
2616
+ return;
2617
+ }
2618
+ this.closed = true;
2619
+
2620
+ if (this.reconnectTimer) {
2621
+ clearTimeout(this.reconnectTimer);
2622
+ this.reconnectTimer = null;
2623
+ }
2624
+
2625
+ if (this.options.reconnectRegistry && this.reconnectToken) {
2626
+ this.options.reconnectRegistry.delete(this.reconnectToken);
2627
+ }
2628
+
2629
+ if (!skipBackendClose && !this.backendExited) {
2630
+ this.backend.close();
2631
+ }
2632
+
2633
+ const clients = [...this.clients.keys()];
2634
+ this.clients.clear();
2635
+ this.writerWs = null;
2636
+ for (const ws of clients) {
2637
+ try {
2638
+ ws.close(code || 1000, reason);
2639
+ } catch (error) {
2640
+ // ignore
2641
+ }
2642
+ }
2643
+
2644
+ if (typeof this.options.onTerminate === "function") {
2645
+ this.options.onTerminate(this);
2646
+ }
2647
+ }
2648
+
2649
+ close(code, reason) {
2650
+ this.terminate(code, reason);
2651
+ }
2652
+ }
2653
+
2654
+ function createServerRuntime(command, argv, options) {
2655
+ const log = logFactory(options.quiet);
2656
+ const hostname = os.hostname();
2657
+ const basePath = normalizeBasePath(
2658
+ options.randomUrl ? `/${randomPathSegment(options.randomUrlLength)}/` : options.path
2659
+ );
2660
+ const wsPath = `${basePath}ws`;
2661
+ const originMatcher = options.wsOrigin ? new RegExp(options.wsOrigin) : null;
2662
+ const indexTemplate = readTextFile(options.index ? expandHome(options.index) : DEFAULT_INDEX);
2663
+ const manifestTemplate = readTextFile(DEFAULT_MANIFEST);
2664
+ const activeSessions = new Set();
2665
+ const reconnectRegistry = new Map();
2666
+ let acceptedOnce = false;
2667
+ let shuttingDown = false;
2668
+
2669
+ const findSessionByIdentifier = (identifier) => {
2670
+ const value = String(identifier || "").trim();
2671
+ if (!value) {
2672
+ return null;
2673
+ }
2674
+
2675
+ const byToken = reconnectRegistry.get(value);
2676
+ if (byToken && byToken.canResume(byToken.reconnectToken)) {
2677
+ return byToken;
2678
+ }
2679
+
2680
+ for (const session of activeSessions) {
2681
+ if (!session || !session.backend) {
2682
+ continue;
2683
+ }
2684
+ if (String(session.backend.pid) === value) {
2685
+ return session;
2686
+ }
2687
+ if (session.reconnectToken && session.reconnectToken === value) {
2688
+ return session;
2689
+ }
2690
+ }
2691
+
2692
+ return null;
2693
+ };
2694
+
2695
+ const findReconnectSession = (identifier) => {
2696
+ const session = findSessionByIdentifier(identifier);
2697
+ if (session && session.canResume(session.reconnectToken)) {
2698
+ return session;
2699
+ }
2700
+ return null;
2701
+ };
2702
+
2703
+ const counter = new Counter(options.timeout, () => {
2704
+ log("Timeout reached without active connections, shutting down");
2705
+ shutdown(false);
2706
+ });
2707
+
2708
+ const requestHandler = (req, res) => {
2709
+ const requestPath = new URL(req.url, "http://localhost").pathname;
2710
+ if (requestPath === basePath.slice(0, -1)) {
2711
+ res.writeHead(302, {
2712
+ Location: basePath,
2713
+ "Server": "GoTTY"
2714
+ });
2715
+ res.end();
2716
+ return;
2717
+ }
2718
+
2719
+ if (!requestPath.startsWith(basePath)) {
2720
+ res.writeHead(404);
2721
+ res.end("Not Found");
2722
+ return;
2723
+ }
2724
+
2725
+ if (options.credential && requestPath !== wsPath) {
2726
+ if (!basicAuthAuthorized(req, options.credential)) {
2727
+ sendBasicAuthRequired(res);
2728
+ return;
2729
+ }
2730
+ }
2731
+
2732
+ const relativePath = requestPath.slice(basePath.length);
2733
+ if (relativePath === "") {
2734
+ const title = renderTemplate(
2735
+ options.titleFormat,
2736
+ buildTitleVariables(command, argv, hostname, req.socket.remoteAddress || "", {})
2737
+ );
2738
+ const html = renderTemplate(indexTemplate, { title });
2739
+ res.writeHead(200, {
2740
+ "Content-Type": "text/html; charset=utf-8",
2741
+ "Server": "GoTTY"
2742
+ });
2743
+ res.end(html);
2744
+ return;
2745
+ }
2746
+
2747
+ if (relativePath === "manifest.json") {
2748
+ const title = renderTemplate(
2749
+ options.titleFormat,
2750
+ buildTitleVariables(command, argv, hostname, req.socket.remoteAddress || "", {})
2751
+ );
2752
+ const body = renderTemplate(manifestTemplate, { title });
2753
+ res.writeHead(200, {
2754
+ "Content-Type": "application/json; charset=utf-8",
2755
+ "Server": "GoTTY"
2756
+ });
2757
+ res.end(body);
2758
+ return;
2759
+ }
2760
+
2761
+ if (relativePath === "auth_token.js") {
2762
+ const body = `var gotty_auth_token = ${JSON.stringify(options.credential)};`;
2763
+ res.writeHead(200, {
2764
+ "Content-Type": "application/javascript; charset=utf-8",
2765
+ "Server": "GoTTY"
2766
+ });
2767
+ res.end(body);
2768
+ return;
2769
+ }
2770
+
2771
+ if (relativePath === "config.js") {
2772
+ const lines = [
2773
+ "var gotty_term = 'xterm';",
2774
+ `var gotty_ws_query_args = ${JSON.stringify(options.wsQueryArgs)};`,
2775
+ `var gotty_enable_webgl = ${options.webgl ? "true" : "false"};`,
2776
+ `var gotty_kitty_trace = ${KITTY_TRACE ? "true" : "false"};`
2777
+ ];
2778
+ res.writeHead(200, {
2779
+ "Content-Type": "application/javascript; charset=utf-8",
2780
+ "Server": "GoTTY"
2781
+ });
2782
+ res.end(lines.join("\n"));
2783
+ return;
2784
+ }
2785
+
2786
+ if (relativePath === "help.md" || relativePath === "help") {
2787
+ const markdown = readTextFile(DEFAULT_HELP);
2788
+ const markdownToHtml = global.Bun?.markdown?.html;
2789
+ const renderHtml = relativePath === "help" && typeof markdownToHtml === "function";
2790
+ const body = renderHtml
2791
+ ? markdownToHtml(markdown)
2792
+ : markdown;
2793
+ res.writeHead(200, {
2794
+ "Content-Type": renderHtml
2795
+ ? "text/html; charset=utf-8"
2796
+ : "text/markdown; charset=utf-8",
2797
+ "Server": "GoTTY"
2798
+ });
2799
+ res.end(body);
2800
+ return;
2801
+ }
2802
+
2803
+ if (relativePath === "css.md" || relativePath === "css") {
2804
+ const sessions = [];
2805
+ for (const session of activeSessions) {
2806
+ if (!session || !session.backend) {
2807
+ continue;
2808
+ }
2809
+ const pid = session.backend.pid
2810
+ ? String(session.backend.pid)
2811
+ : "無";
2812
+ const command = session.options && session.options.command
2813
+ ? path.basename(String(session.options.command))
2814
+ : "";
2815
+ const name = command.replace(/[\r\n\s]+/g, " ").trim() || "無";
2816
+ const reconnectToken = session.reconnectToken
2817
+ ? String(session.reconnectToken).replace(/[\r\n\s]+/g, "")
2818
+ : "";
2819
+ sessions.push({ pid, name, token: reconnectToken });
2820
+ }
2821
+ sessions.sort((left, right) => Number(left.pid) - Number(right.pid));
2822
+ const scheme = options.tls ? "https" : "http";
2823
+ const requestHost = String(req.headers.host || "").trim();
2824
+ const fallbackAddress = server.address();
2825
+ const fallbackHost = fallbackAddress && typeof fallbackAddress !== "string"
2826
+ ? `${net.isIPv6(fallbackAddress.address) ? `[${fallbackAddress.address}]` : fallbackAddress.address}:${fallbackAddress.port}`
2827
+ : "127.0.0.1";
2828
+ const publicOrigin = `${scheme}://${requestHost || fallbackHost}`;
2829
+
2830
+ const lines = [
2831
+ "# Current Session(s)",
2832
+ ...(options.reconnect
2833
+ ? [
2834
+ "- Click on the links directly or with Ctrl+Click to reconnect to session",
2835
+ "- Or use bun client.js -r pid"
2836
+ ]
2837
+ : [
2838
+ "- Reconnect is disabled, so only PID and command name are shown"
2839
+ ]),
2840
+ "## PID SESSION",
2841
+ ...(sessions.length > 0
2842
+ ? sessions.map((session) => {
2843
+ const name = session.name.replace(/([\\[\]])/g, "\\$1");
2844
+ if (session.token) {
2845
+ const reconnectUrl = `${publicOrigin}${basePath}?reconnect-token=${encodeURIComponent(session.token)}`;
2846
+ return `- ${session.pid} [${name}](${reconnectUrl})`;
2847
+ }
2848
+ return `- ${session.pid} ${name}`;
2849
+ })
2850
+ : ["- 無 無"])
2851
+ ];
2852
+ const markdown = `${lines.join("\n")}\n`;
2853
+ const markdownToHtml = global.Bun?.markdown?.html;
2854
+ const renderHtml = relativePath === "css" && typeof markdownToHtml === "function";
2855
+ const body = renderHtml
2856
+ ? markdownToHtml(markdown)
2857
+ : markdown;
2858
+ res.writeHead(200, {
2859
+ "Content-Type": renderHtml
2860
+ ? "text/html; charset=utf-8"
2861
+ : "text/markdown; charset=utf-8",
2862
+ "Server": "GoTTY"
2863
+ });
2864
+ res.end(body);
2865
+ return;
2866
+ }
2867
+
2868
+ const assetPath = path.normalize(path.join(STATIC_ROOT, relativePath));
2869
+ if (!assetPath.startsWith(STATIC_ROOT)) {
2870
+ res.writeHead(403);
2871
+ res.end("Forbidden");
2872
+ return;
2873
+ }
2874
+
2875
+ const bundledAsset = helperReadInternalAssetBytes?.(internalAssetPathFor(assetPath));
2876
+ if (bundledAsset != null) {
2877
+ const body = bundledAsset instanceof Uint8Array ? bundledAsset : new Uint8Array(bundledAsset);
2878
+ res.writeHead(200, {
2879
+ "Content-Type": contentType(assetPath),
2880
+ "Content-Length": body.length,
2881
+ "Server": "GoTTY"
2882
+ });
2883
+ res.end(body);
2884
+ return;
2885
+ }
2886
+
2887
+ fs.stat(assetPath, (error, stats) => {
2888
+ if (error || !stats.isFile()) {
2889
+ res.writeHead(404, { "Server": "GoTTY" });
2890
+ res.end("Not Found");
2891
+ return;
2892
+ }
2893
+
2894
+ res.writeHead(200, {
2895
+ "Content-Type": contentType(assetPath),
2896
+ "Content-Length": stats.size,
2897
+ "Server": "GoTTY"
2898
+ });
2899
+ fs.createReadStream(assetPath).pipe(res);
2900
+ });
2901
+ };
2902
+
2903
+ let server;
2904
+ if (options.tls) {
2905
+ const certPath = expandHome(options.tlsCrt);
2906
+ const keyPath = expandHome(options.tlsKey);
2907
+
2908
+ try {
2909
+ server = https.createServer(
2910
+ {
2911
+ cert: fs.readFileSync(certPath),
2912
+ key: fs.readFileSync(keyPath)
2913
+ },
2914
+ requestHandler
2915
+ );
2916
+ } catch (error) {
2917
+ console.error("TLS is enabled, but the certificate or key could not be loaded.");
2918
+ console.error(`Certificate: ${certPath}`);
2919
+ console.error(`Private key: ${keyPath}`);
2920
+ console.error(`Reason: ${error.message}`);
2921
+ console.error("");
2922
+ console.error("You can generate a self-signed certificate for testing with:");
2923
+ console.error(`openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes -keyout ${keyPath} -out ${certPath} -subj "/CN=localhost"`);
2924
+ console.error("");
2925
+ console.error("Or use a certificate and private key issued by a trusted certificate authority.");
2926
+ process.exit(1);
2927
+ }
2928
+ } else {
2929
+ server = http.createServer(requestHandler);
2930
+ }
2931
+
2932
+ const wss = new WebSocketServer({
2933
+ noServer: true,
2934
+ clientTracking: false,
2935
+ perMessageDeflate: false,
2936
+ handleProtocols(protocols) {
2937
+ return protocols.has("webtty") ? "webtty" : false;
2938
+ }
2939
+ });
2940
+
2941
+ server.on("upgrade", (req, socket, head) => {
2942
+ const requestPath = new URL(req.url, "http://localhost").pathname;
2943
+ if (requestPath !== wsPath) {
2944
+ socket.destroy();
2945
+ return;
2946
+ }
2947
+
2948
+ if (shuttingDown) {
2949
+ socket.destroy();
2950
+ return;
2951
+ }
2952
+
2953
+ if (originMatcher) {
2954
+ const origin = req.headers.origin || "";
2955
+ if (!originMatcher.test(origin)) {
2956
+ socket.destroy();
2957
+ return;
2958
+ }
2959
+ }
2960
+
2961
+ if (options.once && acceptedOnce && !options.reconnect) {
2962
+ socket.destroy();
2963
+ return;
2964
+ }
2965
+
2966
+ const num = counter.add();
2967
+ if (options.maxConnection > 0 &&
2968
+ num > options.maxConnection)
2969
+ {
2970
+ counter.done();
2971
+ socket.destroy();
2972
+ return;
2973
+ }
2974
+
2975
+ wss.handleUpgrade(req, socket, head, (ws) => {
2976
+ wss.emit("connection", ws, req);
2977
+ });
2978
+ });
2979
+
2980
+ wss.on("connection", (ws, req) => {
2981
+ log(`\nNew client connected: ${req.socket.remoteAddress}`);
2982
+ log(` Connection count: ${counter.count()}`)
2983
+
2984
+ if(options.maxConnection>0)
2985
+ log(` Limit: ${options.maxConnection}`);
2986
+
2987
+ let finalized = false;
2988
+ let session = null;
2989
+
2990
+ const finish = (reason) => {
2991
+ if (finalized) {
2992
+ return;
2993
+ }
2994
+ finalized = true;
2995
+ const num = counter.done();
2996
+ log(`Connection closed by ${reason}: ${req.socket.remoteAddress}`)
2997
+
2998
+ log(` Connection count: ${num}`)
2999
+
3000
+ if(options.maxConnection>0)
3001
+ log(` Limit: ${options.maxConnection}`);
3002
+
3003
+ if (shuttingDown && num === 0) {
3004
+ process.exit(0);
3005
+ }
3006
+ };
3007
+
3008
+ ws.once("close", () => finish("client"));
3009
+ ws.once("error", (error) => finish(`an error: ${error.message}`));
3010
+
3011
+ ws.once("message", async (message, isBinary) => {
3012
+ if (isBinary) {
3013
+ ws.close(1008, "invalid message type");
3014
+ return;
3015
+ }
3016
+
3017
+ let init;
3018
+ try {
3019
+ init = parseInitMessage(message);
3020
+ } catch (error) {
3021
+ ws.close(1008, error.message);
3022
+ return;
3023
+ }
3024
+
3025
+ if (init.AuthToken !== options.credential) {
3026
+ ws.close(1008, "failed to authenticate websocket connection");
3027
+ return;
3028
+ }
3029
+
3030
+ if (init.DisconnectWriter) {
3031
+ const existing = options.reconnect
3032
+ ? findReconnectSession(init.ReconnectToken)
3033
+ : null;
3034
+ const writer = existing?.writerSocket();
3035
+ const result = existing && writer
3036
+ ? {
3037
+ ok: true,
3038
+ message: "writer disconnected",
3039
+ token: existing.reconnectToken,
3040
+ pid: existing.backend.pid
3041
+ }
3042
+ : {
3043
+ ok: false,
3044
+ message: existing
3045
+ ? "session has no active writer"
3046
+ : "reconnect session not found"
3047
+ };
3048
+
3049
+ if (writer) {
3050
+ try {
3051
+ writer.close(4001, "writer disconnected by remote request");
3052
+ } catch (error) {
3053
+ result.ok = false;
3054
+ result.message = `failed to disconnect writer: ${error.message}`;
3055
+ }
3056
+ }
3057
+
3058
+ ws.send(MSG_CONTROL_RESULT + JSON.stringify(result), () => {
3059
+ ws.close(result.ok ? 1000 : 1008, result.message);
3060
+ });
3061
+ return;
3062
+ }
3063
+
3064
+ if (init.TerminateSession) {
3065
+ const existing = findSessionByIdentifier(init.ReconnectToken);
3066
+ const token = existing?.reconnectToken || "";
3067
+ const pid = existing?.backend?.pid || "";
3068
+ const result = existing
3069
+ ? {
3070
+ ok: true,
3071
+ message: "session terminated",
3072
+ token,
3073
+ pid
3074
+ }
3075
+ : {
3076
+ ok: false,
3077
+ message: "session not found"
3078
+ };
3079
+
3080
+ if (existing) {
3081
+ try {
3082
+ const termination = await existing.terminateBackend(5000);
3083
+ if (termination.forced) {
3084
+ result.message = "session force killed";
3085
+ }
3086
+ } catch (error) {
3087
+ result.ok = false;
3088
+ result.message = `failed to terminate session: ${error.message}`;
3089
+ }
3090
+ }
3091
+
3092
+ ws.send(MSG_CONTROL_RESULT + JSON.stringify(result), () => {
3093
+ ws.close(result.ok ? 1000 : 1008, result.message);
3094
+ });
3095
+ return;
3096
+ }
3097
+
3098
+ if (options.reconnect && init.ReconnectToken) {
3099
+ const existing = findReconnectSession(init.ReconnectToken);
3100
+ if (existing) {
3101
+ const reconnectToken = existing.reconnectToken;
3102
+ log(` Reconnect token: (resume)
3103
+ ${formatReconnectTokenForLog(reconnectToken)}
3104
+ `);
3105
+ session = existing;
3106
+ session.attachWebSocket(
3107
+ ws,
3108
+ req.socket.remoteAddress || "",
3109
+ init.ReadOnly
3110
+ );
3111
+ return;
3112
+ }
3113
+ }
3114
+
3115
+ if (options.once && acceptedOnce) {
3116
+ ws.close(1008, "server already accepted a client");
3117
+ return;
3118
+ }
3119
+
3120
+ let clientArgs = [];
3121
+ if (options.permitArguments && init.Arguments) {
3122
+ const url = new URL(init.Arguments, "http://localhost");
3123
+ clientArgs = url.searchParams.getAll("arg");
3124
+ }
3125
+
3126
+ const headerEnv = options.passHeaders ? createHeaderEnvironment(req.headers) : {};
3127
+
3128
+ try {
3129
+ session = new PtySession({
3130
+ ws,
3131
+ log,
3132
+ command,
3133
+ argv: [...argv, ...clientArgs],
3134
+ headerEnv,
3135
+ remoteAddr: req.socket.remoteAddress || "",
3136
+ readOnly: init.ReadOnly,
3137
+ hostname,
3138
+ titleFormat: options.titleFormat,
3139
+ permitWrite: options.permitWrite,
3140
+ reconnect: options.reconnect,
3141
+ reconnectTime: options.reconnectTime,
3142
+ reconnectRegistry,
3143
+ width: options.width,
3144
+ height: options.height,
3145
+ closeSignal: options.closeSignal,
3146
+ closeTimeout: options.closeTimeout,
3147
+ onTerminate: (endedSession) => {
3148
+ activeSessions.delete(endedSession);
3149
+ if (options.once) {
3150
+ shutdown(false);
3151
+ setTimeout(()=>process.exit(0),3000)
3152
+ }
3153
+ }
3154
+ });
3155
+ } catch (error) {
3156
+ ws.close(1011, `failed to create backend: ${error.message}`);
3157
+ return;
3158
+ }
3159
+ acceptedOnce = acceptedOnce || options.once;
3160
+ activeSessions.add(session);
3161
+ if (session.reconnectToken) {
3162
+ reconnectRegistry.set(session.reconnectToken, session);
3163
+ }
3164
+ log(` Reconnect token:
3165
+ ${formatReconnectTokenForLog(session.reconnectToken)}
3166
+ `);
3167
+ session.start();
3168
+ });
3169
+ });
3170
+
3171
+ function shutdown(graceful) {
3172
+ if (shuttingDown) {
3173
+ return;
3174
+ }
3175
+ shuttingDown = true;
3176
+ if (graceful) {
3177
+ server.close();
3178
+ } else {
3179
+ for (const session of activeSessions) {
3180
+ session.close(1001, "server shutting down");
3181
+ }
3182
+ server.close();
3183
+ }
3184
+ }
3185
+
3186
+ function logUrls() {
3187
+ const scheme = options.tls ? "https" : "http";
3188
+ const accessMode = options.permitWrite ? "writable" : "read-only";
3189
+ const address = server.address();
3190
+ if (!address || typeof address === "string") {
3191
+ return;
3192
+ }
3193
+ const host = address.address === "::" ? "127.0.0.1" : address.address;
3194
+ const formattedHost = net.isIPv6(host) ? `[${host}]` : host;
3195
+ log(`HTTP server is listening at:
3196
+ ${scheme}://${formattedHost}:${address.port}${basePath}
3197
+ Access mode: ${accessMode}
3198
+ `);
3199
+ const hasCustomPath = normalizeBasePath(options.path) !== "/";
3200
+ const customPathValue = normalizeBasePath(options.path)
3201
+ .replace(/^\/+|\/+$/g, "");
3202
+ if (
3203
+ hasCustomPath &&
3204
+ !options.randomUrl &&
3205
+ customPathValue.length < 16
3206
+ ) {
3207
+ log(`SECURITY NOTICE:
3208
+ The custom path is only ${customPathValue.length} characters long.
3209
+ Use at least 16 randomly generated characters to make URL guessing harder.
3210
+ A custom path is not a substitute for --credential and TLS.
3211
+ `);
3212
+ }
3213
+ if (
3214
+ options.permitWrite &&
3215
+ !options.credential &&
3216
+ !options.randomUrl &&
3217
+ !hasCustomPath
3218
+ ) {
3219
+ log(`SECURITY WARNING:
3220
+ This writable terminal has no credential, random URL, or custom path.
3221
+ Anyone who can reach this server may execute arbitrary commands (ACE)
3222
+ with the same permissions as the GoTTY process.
3223
+ Enable --credential, --random-url, or at minimum use a non-default --path.
3224
+ `);
3225
+ }
3226
+ if (options.address === "0.0.0.0") {
3227
+ try {
3228
+ const interfaces = os.networkInterfaces();
3229
+ for (const values of Object.values(interfaces)) {
3230
+ for (const value of values || []) {
3231
+ if (value.family === "IPv4" && !value.internal) {
3232
+ log(`Alternative URL:
3233
+ ${scheme}://${value.address}:${address.port}${basePath}
3234
+ `);
3235
+ }
3236
+ }
3237
+ }
3238
+ } catch (error) {
3239
+ log(`Alternative URL:
3240
+ ${scheme}://127.0.0.1:${address.port}${basePath}
3241
+ `);
3242
+ log(`Alternative URL:
3243
+ ${scheme}://localhost:${address.port}${basePath}
3244
+ `);
3245
+ }
3246
+ }
3247
+ }
3248
+
3249
+ function start() {
3250
+ server.listen(Number.parseInt(options.port, 10), options.address, logUrls);
3251
+ }
3252
+
3253
+ process.on("SIGTERM", () => {
3254
+ shutdown(false);
3255
+ });
3256
+
3257
+ let gracefulInterrupt = false;
3258
+ process.on("SIGINT", () => {
3259
+ if (!gracefulInterrupt) {
3260
+ gracefulInterrupt = true;
3261
+ console.log("")
3262
+ console.log("Press Ctrl+C again to force close❌");
3263
+ console.log("再按一次Ctrl+C強制停止❌")
3264
+ shutdown(true);
3265
+ return;
3266
+ }
3267
+ console.log("")
3268
+ console.log("Force closing...");
3269
+ console.log("強制停止...");
3270
+
3271
+ shutdown(false);
3272
+ process.exit(130);
3273
+ });
3274
+
3275
+ server.on("close", () => {
3276
+ if (counter.count() === 0) {
3277
+ process.exit(0);
3278
+ }
3279
+ });
3280
+
3281
+ return { start };
3282
+ }
3283
+
3284
+ function main() {
3285
+ const { options, command, argv } = parseArgs(process.argv.slice(2));
3286
+ const runtime = createServerRuntime(command, argv, options);
3287
+ runtime.start();
3288
+ }
3289
+
3290
+ async function bootstrap() {
3291
+ try {
3292
+ const compiledHelper = await compiledHelperPromise;
3293
+ configureResourceRoot(compiledHelper);
3294
+ await compiledHelper?.buildEarlyExit?.(process.argv,'jsgt');
3295
+
3296
+ const assetsHelper = await assetsHelperPromise;
3297
+ if (assetsHelper) {
3298
+ helperAssetPath = assetsHelper.assetPath;
3299
+ helperReadInternalAssetText = assetsHelper.readInternalAssetText;
3300
+ helperReadInternalAssetBytes = assetsHelper.readInternalAssetBytes;
3301
+ }
3302
+ await globalThis.assetsLoaderPromise;
3303
+ } catch (error) {
3304
+ console.error(String(error && error.stack ? error.stack : error));
3305
+ }
3306
+
3307
+ if (!cliBootstrapPromise) {
3308
+ main();
3309
+ }
3310
+ }
3311
+
3312
+ module.exports.CursorStateTracker = CursorStateTracker;
3313
+ module.exports.KittyGraphicsParser = KittyGraphicsParser;
3314
+
3315
+ if (require.main === module) {
3316
+ if (KITTY_TRACE) {
3317
+ try {
3318
+ fs.writeFileSync(KITTY_PLACEMENT_LOG, "");
3319
+ } catch {}
3320
+ }
3321
+ bootstrap().catch((error) => {
3322
+ console.error(String(error && error.stack ? error.stack : error));
3323
+ process.exitCode = 1;
3324
+ });
3325
+ }