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