remote-claude-code 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +193 -0
  2. package/README.zh-CN.md +193 -0
  3. package/cli/commands/logs.ts +68 -0
  4. package/cli/commands/setup.ts +43 -0
  5. package/cli/commands/start.ts +68 -0
  6. package/cli/commands/status.ts +41 -0
  7. package/cli/commands/stop.ts +14 -0
  8. package/cli/constants.ts +1 -0
  9. package/cli/index.ts +18 -0
  10. package/cli/utils/process-manager.ts +77 -0
  11. package/cli/utils/server.ts +45 -0
  12. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  13. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  14. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  15. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  16. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  17. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  18. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  19. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  20. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  21. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  22. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  23. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  24. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  25. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  26. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  27. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  28. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  29. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  30. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  31. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  32. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  33. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  34. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  35. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  36. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  37. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  38. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  39. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  40. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  41. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  42. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  43. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  44. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  45. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  46. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  47. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  48. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  49. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  50. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  51. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  52. package/dist/client/assets/code-highlighter-D_0rJlW2.js +153 -0
  53. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  54. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  55. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  56. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  57. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  58. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  59. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  60. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  61. package/dist/client/assets/css-DPfMkruS.js +1 -0
  62. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  63. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  64. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  65. package/dist/client/assets/d-85-TOEBH.js +1 -0
  66. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  67. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  68. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  69. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  70. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  71. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  72. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  73. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  74. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  75. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  76. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  77. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  78. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  79. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  80. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  81. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  82. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  83. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  84. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  85. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  86. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  87. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  88. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  89. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  90. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  91. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  92. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  93. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  94. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  95. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  96. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  97. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  98. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  99. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  100. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  101. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  102. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  103. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  104. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  105. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  106. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  107. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  108. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  109. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  110. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  111. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  112. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  113. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  114. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  115. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  116. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  117. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  118. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  119. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  120. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  121. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  122. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  123. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  124. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  125. package/dist/client/assets/highlighted-body-TPN3WLV5-Bbf4VBqH.js +1 -0
  126. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  127. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  128. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  129. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  130. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  131. package/dist/client/assets/html-GMplVEZG.js +1 -0
  132. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  133. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  134. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  135. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  136. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  137. package/dist/client/assets/imba-DGztddWO.js +1 -0
  138. package/dist/client/assets/index-D7x3ypuO.css +32 -0
  139. package/dist/client/assets/index-DLfciZbe.js +477 -0
  140. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  141. package/dist/client/assets/java-CylS5w8V.js +1 -0
  142. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  143. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  144. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  145. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  146. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  147. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  148. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  149. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  150. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  151. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  152. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  153. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  154. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  155. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  156. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  157. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  158. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  159. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  160. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  161. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  162. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  163. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  164. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  165. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  166. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  167. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  168. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  169. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  170. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  171. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  172. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  173. package/dist/client/assets/markdown-ksT5_TvX.js +146 -0
  174. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  175. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  176. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  177. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  178. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  179. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  180. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  181. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  182. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  183. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  184. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  185. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  186. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  187. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  188. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  189. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  190. package/dist/client/assets/move-IF9eRakj.js +1 -0
  191. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  192. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  193. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  194. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  195. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  196. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  197. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  198. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  199. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  200. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  201. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  202. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  203. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  204. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  205. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  206. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  207. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  208. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  209. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  210. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  211. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  212. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  213. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  214. package/dist/client/assets/po-BTJTHyun.js +1 -0
  215. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  216. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  217. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  218. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  219. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  220. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  221. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  222. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  223. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  224. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  225. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  226. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  227. package/dist/client/assets/qml-3beO22l8.js +1 -0
  228. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  229. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  230. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  231. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  232. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  233. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  234. package/dist/client/assets/red-bN70gL4F.js +1 -0
  235. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  236. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  237. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  238. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  239. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  240. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  241. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  242. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  243. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  244. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  245. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  246. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  247. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  248. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  249. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  250. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  251. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  252. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  253. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  254. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  255. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  256. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  257. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  258. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  259. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  260. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  261. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  262. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  263. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  264. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  265. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  266. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  267. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  268. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  269. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  270. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  271. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  272. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  273. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  274. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  275. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  276. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  277. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  278. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  279. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  280. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  281. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  282. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  283. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  284. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  285. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  286. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  287. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  288. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  289. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  290. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  291. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  292. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  293. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  294. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  295. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  296. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  297. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  298. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  299. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  300. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  301. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  302. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  303. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  304. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  305. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  306. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  307. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  308. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  309. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  310. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  311. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  312. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  313. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  314. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  315. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  316. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  317. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  318. package/dist/client/images/logo.png +0 -0
  319. package/dist/client/index.html +23 -0
  320. package/dist/client/manifest.webmanifest +1 -0
  321. package/dist/client/registerSW.js +1 -0
  322. package/dist/client/sw.js +1 -0
  323. package/dist/client/workbox-1ef09536.js +1 -0
  324. package/package.json +61 -0
  325. package/public/images/logo.png +0 -0
  326. package/server/auth-cookie.ts +25 -0
  327. package/server/auth.ts +92 -0
  328. package/server/claude.ts +759 -0
  329. package/server/db.ts +134 -0
  330. package/server/index.ts +177 -0
  331. package/server/preview.ts +93 -0
  332. package/server/projects.ts +401 -0
  333. package/server/tailscale.ts +87 -0
  334. package/server/terminal.ts +411 -0
  335. package/server/threads.ts +39 -0
  336. package/server/tunnel.ts +121 -0
  337. package/server/upload.ts +109 -0
  338. package/server/ws.ts +785 -0
@@ -0,0 +1,411 @@
1
+ export interface TerminalInfo {
2
+ id: string;
3
+ shell: string;
4
+ cwd: string;
5
+ cols: number;
6
+ rows: number;
7
+ createdAt: number;
8
+ }
9
+
10
+ interface TerminalCreateOptions {
11
+ cwd?: string;
12
+ shell?: string;
13
+ cols?: number;
14
+ rows?: number;
15
+ onData: (data: string) => void;
16
+ onExit: (code: number) => void;
17
+ onPortDetected: (port: number, url: string) => void;
18
+ }
19
+
20
+ interface TerminalProcessAdapter {
21
+ write(data: string): void;
22
+ resize(cols: number, rows: number): void;
23
+ kill(): void;
24
+ }
25
+
26
+ interface SpawnResult {
27
+ shell: string;
28
+ proc: TerminalProcessAdapter;
29
+ }
30
+
31
+ interface TerminalSession {
32
+ id: string;
33
+ proc: TerminalProcessAdapter;
34
+ shell: string;
35
+ cwd: string;
36
+ cols: number;
37
+ rows: number;
38
+ createdAt: number;
39
+ onData: (data: string) => void;
40
+ onExit: (code: number) => void;
41
+ onPortDetected: (port: number, url: string) => void;
42
+ outputBuffer: string;
43
+ flushTimer: ReturnType<typeof setTimeout> | null;
44
+ windowsPipeInputBuffer: string[];
45
+ }
46
+
47
+ const MAX_TERMINALS = 5;
48
+ const OUTPUT_FLUSH_INTERVAL = 16;
49
+
50
+ const PORT_PATTERNS = [
51
+ /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)/g,
52
+ /(?:on|at|listening)\s+(?:port\s+)?(\d{4,5})/gi,
53
+ ];
54
+
55
+ function detectPorts(text: string): Array<{ port: number; url: string }> {
56
+ const results: Array<{ port: number; url: string }> = [];
57
+ const seen = new Set<number>();
58
+
59
+ for (const pattern of PORT_PATTERNS) {
60
+ pattern.lastIndex = 0;
61
+ let match: RegExpExecArray | null;
62
+ while ((match = pattern.exec(text)) !== null) {
63
+ const port = parseInt(match[1], 10);
64
+ if (port > 0 && port <= 65535 && !seen.has(port)) {
65
+ seen.add(port);
66
+ results.push({
67
+ port,
68
+ url: `http://localhost:${port}`,
69
+ });
70
+ }
71
+ }
72
+ }
73
+
74
+ return results;
75
+ }
76
+
77
+ export class TerminalManager {
78
+ private terminals = new Map<string, TerminalSession>();
79
+ private detectedPorts = new Set<string>();
80
+
81
+ create(id: string, options: TerminalCreateOptions): TerminalInfo {
82
+ if (this.terminals.size >= MAX_TERMINALS) {
83
+ throw new Error(`Maximum ${MAX_TERMINALS} terminals allowed`);
84
+ }
85
+
86
+ if (this.terminals.has(id)) {
87
+ throw new Error(`Terminal ${id} already exists`);
88
+ }
89
+
90
+ const cwd = options.cwd || process.cwd();
91
+ const cols = options.cols || 80;
92
+ const rows = options.rows || 24;
93
+ const shell = options.shell || this.getDefaultShell();
94
+
95
+ const session: TerminalSession = {
96
+ id,
97
+ proc: null as unknown as TerminalProcessAdapter,
98
+ shell,
99
+ cwd,
100
+ cols,
101
+ rows,
102
+ createdAt: Date.now(),
103
+ onData: options.onData,
104
+ onExit: options.onExit,
105
+ onPortDetected: options.onPortDetected,
106
+ outputBuffer: "",
107
+ flushTimer: null,
108
+ windowsPipeInputBuffer: [],
109
+ };
110
+
111
+ const spawned = this.spawnTerminal(session, options.shell);
112
+ session.proc = spawned.proc;
113
+ session.shell = spawned.shell;
114
+ this.terminals.set(id, session);
115
+
116
+ return {
117
+ id,
118
+ shell: session.shell,
119
+ cwd,
120
+ cols,
121
+ rows,
122
+ createdAt: session.createdAt,
123
+ };
124
+ }
125
+
126
+ write(id: string, data: string): void {
127
+ const session = this.terminals.get(id);
128
+ if (!session) throw new Error(`Terminal ${id} not found`);
129
+ session.proc.write(data);
130
+ }
131
+
132
+ resize(id: string, cols: number, rows: number): void {
133
+ const session = this.terminals.get(id);
134
+ if (!session) throw new Error(`Terminal ${id} not found`);
135
+ session.cols = cols;
136
+ session.rows = rows;
137
+ session.proc.resize(cols, rows);
138
+ }
139
+
140
+ destroy(id: string): void {
141
+ const session = this.terminals.get(id);
142
+ if (!session) return;
143
+
144
+ if (session.flushTimer) {
145
+ clearTimeout(session.flushTimer);
146
+ session.flushTimer = null;
147
+ }
148
+
149
+ try {
150
+ session.proc.kill();
151
+ } catch {
152
+ // Some backends can throw if the process is already closed.
153
+ }
154
+ this.terminals.delete(id);
155
+ this.clearDetectedPorts(id);
156
+ }
157
+
158
+ destroyAll(): void {
159
+ for (const id of [...this.terminals.keys()]) {
160
+ this.destroy(id);
161
+ }
162
+ }
163
+
164
+ list(): TerminalInfo[] {
165
+ return [...this.terminals.values()].map(({ id, shell, cwd, cols, rows, createdAt }) => ({
166
+ id,
167
+ shell,
168
+ cwd,
169
+ cols,
170
+ rows,
171
+ createdAt,
172
+ }));
173
+ }
174
+
175
+ has(id: string): boolean {
176
+ return this.terminals.has(id);
177
+ }
178
+
179
+ private getDefaultShell(): string {
180
+ if (process.platform === "win32") {
181
+ return this.getWindowsShellCandidates()[0] ?? "cmd.exe";
182
+ }
183
+ return process.env.SHELL || "bash";
184
+ }
185
+
186
+ private getWindowsShellCandidates(preferredShell?: string): string[] {
187
+ if (preferredShell) return [preferredShell];
188
+
189
+ const candidates = ["pwsh.exe", "powershell.exe", "cmd.exe", process.env.COMSPEC].filter(
190
+ (value): value is string => Boolean(value && value.trim())
191
+ );
192
+
193
+ const seen = new Set<string>();
194
+ const result: string[] = [];
195
+ for (const candidate of candidates) {
196
+ const key = candidate.toLowerCase();
197
+ if (seen.has(key)) continue;
198
+ seen.add(key);
199
+ result.push(candidate);
200
+ }
201
+ return result;
202
+ }
203
+
204
+ private spawnTerminal(session: TerminalSession, requestedShell?: string): SpawnResult {
205
+ if (process.platform === "win32") {
206
+ return this.spawnWindowsTerminal(session, requestedShell);
207
+ }
208
+ return this.spawnPosixTerminal(session, requestedShell);
209
+ }
210
+
211
+ private spawnPosixTerminal(session: TerminalSession, requestedShell?: string): SpawnResult {
212
+ const shell = requestedShell || process.env.SHELL || "bash";
213
+
214
+ const proc = Bun.spawn([shell], {
215
+ cwd: session.cwd,
216
+ env: { ...process.env, TERM: "xterm-256color" },
217
+ terminal: {
218
+ cols: session.cols,
219
+ rows: session.rows,
220
+ data: (_terminal: unknown, data: Buffer | Uint8Array) => {
221
+ const text = Buffer.from(data).toString("utf-8");
222
+ this.handleTerminalOutput(session, text);
223
+ },
224
+ },
225
+ });
226
+
227
+ proc.exited.then((code) => {
228
+ this.handleTerminalExit(session, code ?? 0);
229
+ });
230
+
231
+ return {
232
+ shell,
233
+ proc: {
234
+ write: (data) => {
235
+ proc.terminal?.write(data);
236
+ },
237
+ resize: (cols, rows) => {
238
+ proc.terminal?.resize(cols, rows);
239
+ },
240
+ kill: () => {
241
+ proc.kill();
242
+ },
243
+ },
244
+ };
245
+ }
246
+
247
+ private spawnWindowsTerminal(session: TerminalSession, requestedShell?: string): SpawnResult {
248
+ const shells = this.getWindowsShellCandidates(requestedShell);
249
+ let lastError: unknown = null;
250
+
251
+ for (const shell of shells) {
252
+ try {
253
+ const proc = Bun.spawn([shell], {
254
+ cwd: session.cwd,
255
+ env: { ...process.env, TERM: "xterm-256color" },
256
+ stdin: "pipe",
257
+ stdout: "pipe",
258
+ stderr: "pipe",
259
+ });
260
+
261
+ this.attachReadable(proc.stdout, session);
262
+ this.attachReadable(proc.stderr, session);
263
+ proc.exited.then((code) => {
264
+ this.handleTerminalExit(session, code ?? 0);
265
+ });
266
+
267
+ return {
268
+ shell,
269
+ proc: {
270
+ write: (data) => {
271
+ if (!proc.stdin || typeof proc.stdin === "number") {
272
+ throw new Error("Terminal stdin is not available");
273
+ }
274
+ proc.stdin.write(this.prepareWindowsPipeInput(session, data));
275
+ proc.stdin.flush();
276
+ },
277
+ resize: () => {
278
+ // No PTY on Windows in Bun currently; keep API compatible with no-op resize.
279
+ },
280
+ kill: () => {
281
+ proc.kill();
282
+ },
283
+ },
284
+ };
285
+ } catch (err) {
286
+ lastError = err;
287
+ }
288
+ }
289
+
290
+ const reason =
291
+ lastError instanceof Error && lastError.message
292
+ ? `Last error: ${lastError.message}`
293
+ : "No shell executable could be started.";
294
+ throw new Error(`Failed to create Windows terminal. ${reason}`);
295
+ }
296
+
297
+ private attachReadable(
298
+ readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | null | undefined,
299
+ session: TerminalSession
300
+ ): void {
301
+ if (!readable || typeof readable === "number") return;
302
+
303
+ const decoder = new TextDecoder();
304
+ void (async () => {
305
+ const reader = readable.getReader();
306
+ try {
307
+ while (true) {
308
+ const { done, value } = await reader.read();
309
+ if (done) break;
310
+ if (value && value.length > 0) {
311
+ this.handleTerminalOutput(session, decoder.decode(value, { stream: true }));
312
+ }
313
+ }
314
+ const tail = decoder.decode();
315
+ if (tail) this.handleTerminalOutput(session, tail);
316
+ } catch {
317
+ // Stream shutdown errors are expected when process exits.
318
+ } finally {
319
+ reader.releaseLock();
320
+ }
321
+ })();
322
+ }
323
+
324
+ private handleTerminalOutput(session: TerminalSession, text: string): void {
325
+ const ports = detectPorts(text);
326
+ for (const { port, url } of ports) {
327
+ const key = `${session.id}:${port}`;
328
+ if (!this.detectedPorts.has(key)) {
329
+ this.detectedPorts.add(key);
330
+ session.onPortDetected(port, url);
331
+ }
332
+ }
333
+
334
+ const renderedText =
335
+ process.platform === "win32" ? this.normalizeWindowsPipeOutput(text) : text;
336
+
337
+ session.outputBuffer += renderedText;
338
+ if (!session.flushTimer) {
339
+ session.flushTimer = setTimeout(() => {
340
+ if (session.outputBuffer) {
341
+ session.onData(session.outputBuffer);
342
+ session.outputBuffer = "";
343
+ }
344
+ session.flushTimer = null;
345
+ }, OUTPUT_FLUSH_INTERVAL);
346
+ }
347
+ }
348
+
349
+ private prepareWindowsPipeInput(session: TerminalSession, data: string): string {
350
+ // xterm.js sends DEL (\x7f) for Backspace by default.
351
+ // Windows shell stdin in pipe mode expects BS (\b) to edit the current line.
352
+ const normalized = data.replace(/\x7f/g, "\b");
353
+ let result = "";
354
+
355
+ for (const char of Array.from(normalized)) {
356
+ if (char === "\b") {
357
+ if (session.windowsPipeInputBuffer.length === 0) continue;
358
+ session.windowsPipeInputBuffer.pop();
359
+ result += char;
360
+ continue;
361
+ }
362
+
363
+ if (char === "\r" || char === "\n" || char === "\x03") {
364
+ session.windowsPipeInputBuffer = [];
365
+ result += char;
366
+ continue;
367
+ }
368
+
369
+ const codePoint = char.codePointAt(0) ?? 0;
370
+ if (codePoint < 0x20 || codePoint === 0x7f) {
371
+ result += char;
372
+ continue;
373
+ }
374
+
375
+ session.windowsPipeInputBuffer.push(char);
376
+ result += char;
377
+ }
378
+
379
+ return result;
380
+ }
381
+
382
+ private normalizeWindowsPipeOutput(data: string): string {
383
+ // PowerShell in stdin/stdout pipe mode echoes bare BS (\b) on backspace.
384
+ // xterm.js moves the cursor left for BS but does not erase the previous glyph,
385
+ // so expand it to the classic erase sequence for correct visual behavior.
386
+ return data.replace(/\x08/g, "\b \b");
387
+ }
388
+
389
+ private handleTerminalExit(session: TerminalSession, code: number): void {
390
+ if (session.flushTimer) {
391
+ clearTimeout(session.flushTimer);
392
+ session.flushTimer = null;
393
+ }
394
+ if (session.outputBuffer) {
395
+ session.onData(session.outputBuffer);
396
+ session.outputBuffer = "";
397
+ }
398
+
399
+ session.onExit(code);
400
+ this.terminals.delete(session.id);
401
+ this.clearDetectedPorts(session.id);
402
+ }
403
+
404
+ private clearDetectedPorts(terminalId: string): void {
405
+ for (const key of this.detectedPorts) {
406
+ if (key.startsWith(`${terminalId}:`)) {
407
+ this.detectedPorts.delete(key);
408
+ }
409
+ }
410
+ }
411
+ }
@@ -0,0 +1,39 @@
1
+ import { Hono } from "hono";
2
+ import { jwt } from "hono/jwt";
3
+ import {
4
+ listSessions,
5
+ getSessionMessages,
6
+ } from "@anthropic-ai/claude-agent-sdk";
7
+
8
+ export function createThreadRoutes(jwtSecret: string) {
9
+ const threads = new Hono();
10
+
11
+ threads.use("*", jwt({ secret: jwtSecret, alg: "HS256" }));
12
+
13
+ // GET /api/threads — thread 列表
14
+ threads.get("/", async (c) => {
15
+ const limit = parseInt(c.req.query("limit") || "30");
16
+ const dir = c.req.query("dir");
17
+ const sessions = await listSessions({ limit, ...(dir ? { dir } : {}) });
18
+
19
+ return c.json({
20
+ threads: sessions.map((s) => ({
21
+ id: s.sessionId,
22
+ title: s.customTitle || s.summary || s.firstPrompt?.slice(0, 60) || "New Thread",
23
+ firstPrompt: s.firstPrompt,
24
+ lastModified: s.lastModified,
25
+ cwd: s.cwd,
26
+ })),
27
+ });
28
+ });
29
+
30
+ // GET /api/threads/:id/messages — thread 消息历史
31
+ threads.get("/:id/messages", async (c) => {
32
+ const sessionId = c.req.param("id");
33
+ const messages = await getSessionMessages(sessionId);
34
+
35
+ return c.json({ messages });
36
+ });
37
+
38
+ return threads;
39
+ }
@@ -0,0 +1,121 @@
1
+ import { tunnel as startTunnel } from "cloudflared";
2
+
3
+ interface TunnelInfo {
4
+ url: string | null;
5
+ isRunning: boolean;
6
+ protocol: "quic" | "http2" | null;
7
+ }
8
+
9
+ let tunnelInfo: TunnelInfo = { url: null, isRunning: false, protocol: null };
10
+ let stopFn: (() => void) | null = null;
11
+
12
+ const QUIC_FAIL_SIGNAL = "failed to dial to edge with quic";
13
+ const REGISTERED_SIGNAL = "Registered tunnel connection";
14
+
15
+ export async function launchTunnel(port: number): Promise<string> {
16
+ const forced = process.env.RCC_TUNNEL_PROTOCOL as
17
+ | "quic"
18
+ | "http2"
19
+ | undefined;
20
+
21
+ if (forced) {
22
+ console.log(`[Tunnel] Protocol forced to ${forced} via env`);
23
+ return startWithProtocol(port, forced);
24
+ }
25
+
26
+ try {
27
+ return await startWithProtocol(port, "quic");
28
+ } catch {
29
+ console.log("[Tunnel] QUIC failed, falling back to HTTP/2...");
30
+ return startWithProtocol(port, "http2");
31
+ }
32
+ }
33
+
34
+ function startWithProtocol(
35
+ port: number,
36
+ protocol: "quic" | "http2"
37
+ ): Promise<string> {
38
+ console.log(`[Tunnel] Starting Cloudflare Tunnel (${protocol})...`);
39
+
40
+ const { url, child, stop } = startTunnel({
41
+ "--url": `localhost:${port}`,
42
+ "--protocol": protocol,
43
+ });
44
+
45
+ return new Promise<string>((resolve, reject) => {
46
+ let settled = false;
47
+ let timer: ReturnType<typeof setTimeout> | undefined;
48
+ let tunnelUrl: string | null = null;
49
+
50
+ const cleanup = () => {
51
+ clearTimeout(timer);
52
+ child.stderr?.removeListener("data", onStderr);
53
+ child.removeListener("exit", onExit);
54
+ };
55
+
56
+ const settle = (url: string | null, error?: Error) => {
57
+ if (settled) return;
58
+ settled = true;
59
+ cleanup();
60
+ if (url) {
61
+ console.log(`[Tunnel] Connected via ${protocol}: ${url}`);
62
+ tunnelInfo = { url, isRunning: true, protocol };
63
+ stopFn = stop;
64
+ child.on("exit", (code: number) => {
65
+ console.log(`[Tunnel] Process exited with code ${code}`);
66
+ tunnelInfo = { url: null, isRunning: false, protocol: null };
67
+ });
68
+ resolve(url);
69
+ } else {
70
+ stop();
71
+ reject(error);
72
+ }
73
+ };
74
+
75
+ // Buffer.includes(string) 做字节级搜索,避免每次 toString 分配
76
+ const onStderr = (data: Buffer) => {
77
+ if (settled || protocol !== "quic") return;
78
+ if (data.includes(QUIC_FAIL_SIGNAL)) {
79
+ settle(null, new Error("QUIC connection failed"));
80
+ } else if (tunnelUrl && data.includes(REGISTERED_SIGNAL)) {
81
+ settle(tunnelUrl);
82
+ }
83
+ };
84
+
85
+ const onExit = () =>
86
+ settle(null, new Error("cloudflared exited unexpectedly"));
87
+
88
+ if (protocol === "quic") {
89
+ child.stderr?.on("data", onStderr);
90
+ timer = setTimeout(
91
+ () => settle(null, new Error("QUIC connection timeout")),
92
+ 15_000
93
+ );
94
+ }
95
+ child.on("exit", onExit);
96
+
97
+ url
98
+ .then((resolved) => {
99
+ if (settled) return;
100
+ if (protocol === "quic") {
101
+ tunnelUrl = resolved;
102
+ } else {
103
+ settle(resolved);
104
+ }
105
+ })
106
+ .catch((err) => settle(null, err));
107
+ });
108
+ }
109
+
110
+ export function getTunnelInfo(): TunnelInfo {
111
+ return tunnelInfo;
112
+ }
113
+
114
+ export function stopTunnel() {
115
+ if (stopFn) {
116
+ stopFn();
117
+ tunnelInfo = { url: null, isRunning: false, protocol: null };
118
+ stopFn = null;
119
+ console.log("[Tunnel] Stopped");
120
+ }
121
+ }
@@ -0,0 +1,109 @@
1
+ import { Hono } from "hono";
2
+ import { verify } from "hono/jwt";
3
+ import { join } from "path";
4
+ import { mkdir, readdir, unlink, stat } from "fs/promises";
5
+ import { existsSync } from "fs";
6
+ import { homedir } from "node:os";
7
+
8
+ const UPLOAD_DIR = join(
9
+ homedir(),
10
+ ".remote-claude-code",
11
+ "uploads"
12
+ );
13
+ const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB
14
+ const FILE_TTL = 24 * 60 * 60 * 1000; // 24h
15
+
16
+ // 启动时清理过期文件
17
+ async function cleanupExpiredFiles() {
18
+ if (!existsSync(UPLOAD_DIR)) return;
19
+ try {
20
+ const files = await readdir(UPLOAD_DIR);
21
+ const now = Date.now();
22
+ for (const file of files) {
23
+ const filePath = join(UPLOAD_DIR, file);
24
+ const s = await stat(filePath);
25
+ if (now - s.mtimeMs > FILE_TTL) {
26
+ await unlink(filePath);
27
+ }
28
+ }
29
+ } catch {
30
+ // ignore cleanup errors
31
+ }
32
+ }
33
+
34
+ // 启动时清理 + 每 6 小时周期清理
35
+ cleanupExpiredFiles();
36
+ setInterval(cleanupExpiredFiles, 6 * 60 * 60 * 1000);
37
+
38
+ export function createUploadRoutes(jwtSecret: string) {
39
+ const app = new Hono();
40
+
41
+ app.post("/upload", async (c) => {
42
+ // JWT 鉴权
43
+ const authHeader = c.req.header("authorization");
44
+ if (!authHeader?.startsWith("Bearer ")) {
45
+ return c.json({ error: "Unauthorized" }, 401);
46
+ }
47
+ try {
48
+ await verify(authHeader.slice(7), jwtSecret, "HS256");
49
+ } catch {
50
+ return c.json({ error: "Invalid token" }, 401);
51
+ }
52
+
53
+ // 解析 multipart
54
+ const body = await c.req.parseBody();
55
+ const file = body["file"];
56
+ if (!(file instanceof File)) {
57
+ return c.json({ error: "No file provided" }, 400);
58
+ }
59
+
60
+ if (file.size > MAX_FILE_SIZE) {
61
+ return c.json({ error: `File too large (max ${MAX_FILE_SIZE / 1024 / 1024}MB)` }, 413);
62
+ }
63
+
64
+ // 确保上传目录存在
65
+ await mkdir(UPLOAD_DIR, { recursive: true });
66
+
67
+ // 保存文件
68
+ const fileId = crypto.randomUUID();
69
+ const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
70
+ const fileName = `${fileId}-${safeName}`;
71
+ const filePath = join(UPLOAD_DIR, fileName);
72
+
73
+ await Bun.write(filePath, file);
74
+
75
+ return c.json({
76
+ fileId,
77
+ filePath,
78
+ fileName: file.name,
79
+ mimeType: file.type,
80
+ size: file.size,
81
+ });
82
+ });
83
+
84
+ // 文件服务(用于前端图片预览等)
85
+ app.get("/uploads/:filename", async (c) => {
86
+ const filename = c.req.param("filename");
87
+
88
+ // 防止路径穿越
89
+ if (!filename || filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
90
+ return c.json({ error: "Invalid filename" }, 400);
91
+ }
92
+
93
+ const filePath = join(UPLOAD_DIR, filename);
94
+ const file = Bun.file(filePath);
95
+
96
+ if (!(await file.exists())) {
97
+ return c.json({ error: "File not found" }, 404);
98
+ }
99
+
100
+ return new Response(file.stream(), {
101
+ headers: {
102
+ "Content-Type": file.type || "application/octet-stream",
103
+ "Cache-Control": "private, max-age=3600",
104
+ },
105
+ });
106
+ });
107
+
108
+ return app;
109
+ }