skybridge 0.0.0-dev.f76ccdc → 0.0.0-dev.f792261

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 (419) hide show
  1. package/LICENSE +21 -674
  2. package/README.md +97 -142
  3. package/bin/run.js +5 -0
  4. package/dist/cli/detect-port.d.ts +18 -0
  5. package/dist/cli/detect-port.js +61 -0
  6. package/dist/cli/detect-port.js.map +1 -0
  7. package/dist/cli/header.d.ts +4 -0
  8. package/dist/cli/header.js +6 -0
  9. package/dist/cli/header.js.map +1 -0
  10. package/dist/cli/run-command.d.ts +2 -0
  11. package/dist/cli/run-command.js +43 -0
  12. package/dist/cli/run-command.js.map +1 -0
  13. package/dist/cli/telemetry.d.ts +7 -0
  14. package/dist/cli/telemetry.js +123 -0
  15. package/dist/cli/telemetry.js.map +1 -0
  16. package/dist/cli/tunnel-control-server.d.ts +9 -0
  17. package/dist/cli/tunnel-control-server.js +31 -0
  18. package/dist/cli/tunnel-control-server.js.map +1 -0
  19. package/dist/cli/tunnel-control-server.test.js +39 -0
  20. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  21. package/dist/cli/tunnel-handler.d.ts +3 -0
  22. package/dist/cli/tunnel-handler.js +48 -0
  23. package/dist/cli/tunnel-handler.js.map +1 -0
  24. package/dist/cli/tunnel-handler.test.js +105 -0
  25. package/dist/cli/tunnel-handler.test.js.map +1 -0
  26. package/dist/cli/tunnel.d.ts +57 -0
  27. package/dist/cli/tunnel.js +154 -0
  28. package/dist/cli/tunnel.js.map +1 -0
  29. package/dist/cli/tunnel.test.js +190 -0
  30. package/dist/cli/tunnel.test.js.map +1 -0
  31. package/dist/cli/types.d.ts +5 -0
  32. package/dist/cli/types.js +2 -0
  33. package/dist/cli/types.js.map +1 -0
  34. package/dist/cli/use-execute-steps.d.ts +11 -0
  35. package/dist/cli/use-execute-steps.js +36 -0
  36. package/dist/cli/use-execute-steps.js.map +1 -0
  37. package/dist/cli/use-messages.d.ts +3 -0
  38. package/dist/cli/use-messages.js +11 -0
  39. package/dist/cli/use-messages.js.map +1 -0
  40. package/dist/cli/use-nodemon.d.ts +2 -0
  41. package/dist/cli/use-nodemon.js +73 -0
  42. package/dist/cli/use-nodemon.js.map +1 -0
  43. package/dist/cli/use-open-browser.d.ts +1 -0
  44. package/dist/cli/use-open-browser.js +44 -0
  45. package/dist/cli/use-open-browser.js.map +1 -0
  46. package/dist/cli/use-tunnel.d.ts +14 -0
  47. package/dist/cli/use-tunnel.js +131 -0
  48. package/dist/cli/use-tunnel.js.map +1 -0
  49. package/dist/cli/use-typescript-check.d.ts +9 -0
  50. package/dist/cli/use-typescript-check.js +94 -0
  51. package/dist/cli/use-typescript-check.js.map +1 -0
  52. package/dist/commands/build.d.ts +9 -0
  53. package/dist/commands/build.js +102 -0
  54. package/dist/commands/build.js.map +1 -0
  55. package/dist/commands/dev.d.ts +12 -0
  56. package/dist/commands/dev.js +80 -0
  57. package/dist/commands/dev.js.map +1 -0
  58. package/dist/commands/start.d.ts +9 -0
  59. package/dist/commands/start.js +49 -0
  60. package/dist/commands/start.js.map +1 -0
  61. package/dist/commands/telemetry/disable.d.ts +5 -0
  62. package/dist/commands/telemetry/disable.js +14 -0
  63. package/dist/commands/telemetry/disable.js.map +1 -0
  64. package/dist/commands/telemetry/enable.d.ts +5 -0
  65. package/dist/commands/telemetry/enable.js +14 -0
  66. package/dist/commands/telemetry/enable.js.map +1 -0
  67. package/dist/commands/telemetry/status.d.ts +5 -0
  68. package/dist/commands/telemetry/status.js +14 -0
  69. package/dist/commands/telemetry/status.js.map +1 -0
  70. package/dist/server/asset-base-url-transform-plugin.d.ts +10 -0
  71. package/dist/server/asset-base-url-transform-plugin.js +33 -0
  72. package/dist/server/asset-base-url-transform-plugin.js.map +1 -0
  73. package/dist/server/asset-base-url-transform-plugin.test.js +84 -0
  74. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -0
  75. package/dist/server/content-helpers.d.ts +27 -0
  76. package/dist/server/content-helpers.js +46 -0
  77. package/dist/server/content-helpers.js.map +1 -0
  78. package/dist/server/content-helpers.test.js +70 -0
  79. package/dist/server/content-helpers.test.js.map +1 -0
  80. package/dist/server/express.d.ts +11 -0
  81. package/dist/server/express.js +101 -0
  82. package/dist/server/express.js.map +1 -0
  83. package/dist/server/express.test.js +430 -0
  84. package/dist/server/express.test.js.map +1 -0
  85. package/dist/server/index.d.ts +6 -0
  86. package/dist/server/index.js +4 -0
  87. package/dist/server/index.js.map +1 -0
  88. package/dist/server/inferUtilityTypes.d.ts +64 -0
  89. package/dist/server/inferUtilityTypes.js +2 -0
  90. package/dist/server/inferUtilityTypes.js.map +1 -0
  91. package/dist/server/metric.d.ts +14 -0
  92. package/dist/server/metric.js +62 -0
  93. package/dist/server/metric.js.map +1 -0
  94. package/dist/server/middleware.d.ts +124 -0
  95. package/dist/server/middleware.js +93 -0
  96. package/dist/server/middleware.js.map +1 -0
  97. package/dist/server/middleware.test-d.js +75 -0
  98. package/dist/server/middleware.test-d.js.map +1 -0
  99. package/dist/server/middleware.test.js +493 -0
  100. package/dist/server/middleware.test.js.map +1 -0
  101. package/dist/server/server.d.ts +196 -0
  102. package/dist/server/server.js +468 -0
  103. package/dist/server/server.js.map +1 -0
  104. package/dist/{src/server → server}/templateHelper.d.ts +5 -4
  105. package/dist/server/templateHelper.js +11 -0
  106. package/dist/server/templateHelper.js.map +1 -0
  107. package/dist/server/templates.generated.d.ts +4 -0
  108. package/dist/server/templates.generated.js +47 -0
  109. package/dist/server/templates.generated.js.map +1 -0
  110. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  111. package/dist/server/tunnel-proxy-router.js +110 -0
  112. package/dist/server/tunnel-proxy-router.js.map +1 -0
  113. package/dist/server/tunnel-proxy-router.test.js +229 -0
  114. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  115. package/dist/server/viewsDevServer.d.ts +14 -0
  116. package/dist/server/viewsDevServer.js +45 -0
  117. package/dist/server/viewsDevServer.js.map +1 -0
  118. package/dist/test/utils.d.ts +127 -0
  119. package/dist/test/utils.js +247 -0
  120. package/dist/test/utils.js.map +1 -0
  121. package/dist/test/view.test.js +523 -0
  122. package/dist/test/view.test.js.map +1 -0
  123. package/dist/version.d.ts +1 -0
  124. package/dist/version.js +3 -0
  125. package/dist/version.js.map +1 -0
  126. package/dist/web/bridges/apps-sdk/adaptor.d.ts +24 -0
  127. package/dist/web/bridges/apps-sdk/adaptor.js +96 -0
  128. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -0
  129. package/dist/web/bridges/apps-sdk/bridge.d.ts +10 -0
  130. package/dist/web/bridges/apps-sdk/bridge.js +46 -0
  131. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -0
  132. package/dist/web/bridges/apps-sdk/index.d.ts +5 -0
  133. package/dist/web/bridges/apps-sdk/index.js +5 -0
  134. package/dist/web/bridges/apps-sdk/index.js.map +1 -0
  135. package/dist/web/bridges/apps-sdk/types.d.ts +131 -0
  136. package/dist/web/bridges/apps-sdk/types.js.map +1 -0
  137. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +2 -0
  138. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +7 -0
  139. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -0
  140. package/dist/web/bridges/get-adaptor.d.ts +2 -0
  141. package/dist/web/bridges/get-adaptor.js +8 -0
  142. package/dist/web/bridges/get-adaptor.js.map +1 -0
  143. package/dist/web/bridges/index.d.ts +5 -0
  144. package/dist/web/bridges/index.js +6 -0
  145. package/dist/web/bridges/index.js.map +1 -0
  146. package/dist/web/bridges/mcp-app/adaptor.d.ts +48 -0
  147. package/dist/web/bridges/mcp-app/adaptor.js +263 -0
  148. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -0
  149. package/dist/web/bridges/mcp-app/bridge.d.ts +26 -0
  150. package/dist/web/bridges/mcp-app/bridge.js +102 -0
  151. package/dist/web/bridges/mcp-app/bridge.js.map +1 -0
  152. package/dist/web/bridges/mcp-app/index.d.ts +4 -0
  153. package/dist/web/bridges/mcp-app/index.js +4 -0
  154. package/dist/web/bridges/mcp-app/index.js.map +1 -0
  155. package/dist/web/bridges/mcp-app/types.d.ts +8 -0
  156. package/dist/web/bridges/mcp-app/types.js +2 -0
  157. package/dist/web/bridges/mcp-app/types.js.map +1 -0
  158. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +7 -0
  159. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +7 -0
  160. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -0
  161. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +26 -0
  162. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -0
  163. package/dist/web/bridges/types.d.ts +111 -0
  164. package/dist/web/bridges/types.js +2 -0
  165. package/dist/web/bridges/types.js.map +1 -0
  166. package/dist/web/bridges/use-host-context.d.ts +2 -0
  167. package/dist/web/bridges/use-host-context.js +8 -0
  168. package/dist/web/bridges/use-host-context.js.map +1 -0
  169. package/dist/web/components/modal-provider.d.ts +4 -0
  170. package/dist/web/components/modal-provider.js +45 -0
  171. package/dist/web/components/modal-provider.js.map +1 -0
  172. package/dist/web/create-store.d.ts +3 -0
  173. package/dist/web/create-store.js +38 -0
  174. package/dist/web/create-store.js.map +1 -0
  175. package/dist/web/create-store.test.d.ts +1 -0
  176. package/dist/web/create-store.test.js +129 -0
  177. package/dist/web/create-store.test.js.map +1 -0
  178. package/dist/web/data-llm.d.ts +14 -0
  179. package/dist/web/data-llm.js +72 -0
  180. package/dist/web/data-llm.js.map +1 -0
  181. package/dist/web/data-llm.test.d.ts +1 -0
  182. package/dist/web/data-llm.test.js +142 -0
  183. package/dist/web/data-llm.test.js.map +1 -0
  184. package/dist/web/generate-helpers.d.ts +118 -0
  185. package/dist/web/generate-helpers.js +113 -0
  186. package/dist/web/generate-helpers.js.map +1 -0
  187. package/dist/web/generate-helpers.test-d.d.ts +1 -0
  188. package/dist/web/generate-helpers.test-d.js +209 -0
  189. package/dist/web/generate-helpers.test-d.js.map +1 -0
  190. package/dist/web/generate-helpers.test.d.ts +1 -0
  191. package/dist/web/generate-helpers.test.js +17 -0
  192. package/dist/web/generate-helpers.test.js.map +1 -0
  193. package/dist/web/helpers/state.d.ts +7 -0
  194. package/dist/web/helpers/state.js +45 -0
  195. package/dist/web/helpers/state.js.map +1 -0
  196. package/dist/web/helpers/state.test.d.ts +1 -0
  197. package/dist/web/helpers/state.test.js +53 -0
  198. package/dist/web/helpers/state.test.js.map +1 -0
  199. package/dist/web/hooks/index.d.ts +11 -0
  200. package/dist/web/hooks/index.js +12 -0
  201. package/dist/web/hooks/index.js.map +1 -0
  202. package/dist/web/hooks/test/utils.d.ts +16 -0
  203. package/dist/web/hooks/test/utils.js +64 -0
  204. package/dist/web/hooks/test/utils.js.map +1 -0
  205. package/dist/web/hooks/use-call-tool.d.ts +101 -0
  206. package/dist/web/hooks/use-call-tool.js +68 -0
  207. package/dist/web/hooks/use-call-tool.js.map +1 -0
  208. package/dist/web/hooks/use-call-tool.test-d.d.ts +1 -0
  209. package/dist/web/hooks/use-call-tool.test-d.js +104 -0
  210. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -0
  211. package/dist/web/hooks/use-call-tool.test.d.ts +1 -0
  212. package/dist/web/hooks/use-call-tool.test.js +186 -0
  213. package/dist/web/hooks/use-call-tool.test.js.map +1 -0
  214. package/dist/web/hooks/use-display-mode.d.ts +4 -0
  215. package/dist/web/hooks/use-display-mode.js +9 -0
  216. package/dist/web/hooks/use-display-mode.js.map +1 -0
  217. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  218. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  219. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  220. package/dist/web/hooks/use-display-mode.test.d.ts +1 -0
  221. package/dist/{src/web → web}/hooks/use-display-mode.test.js +3 -2
  222. package/dist/web/hooks/use-display-mode.test.js.map +1 -0
  223. package/dist/web/hooks/use-files.d.ts +7 -0
  224. package/dist/web/hooks/use-files.js +10 -0
  225. package/dist/web/hooks/use-files.js.map +1 -0
  226. package/dist/web/hooks/use-files.test.d.ts +1 -0
  227. package/dist/web/hooks/use-files.test.js +54 -0
  228. package/dist/web/hooks/use-files.test.js.map +1 -0
  229. package/dist/web/hooks/use-layout.d.ts +22 -0
  230. package/dist/web/hooks/use-layout.js +23 -0
  231. package/dist/web/hooks/use-layout.js.map +1 -0
  232. package/dist/web/hooks/use-layout.test.d.ts +1 -0
  233. package/dist/web/hooks/use-layout.test.js +96 -0
  234. package/dist/web/hooks/use-layout.test.js.map +1 -0
  235. package/dist/web/hooks/use-open-external.d.ts +3 -0
  236. package/dist/web/hooks/use-open-external.js +8 -0
  237. package/dist/web/hooks/use-open-external.js.map +1 -0
  238. package/dist/web/hooks/use-open-external.test.d.ts +1 -0
  239. package/dist/web/hooks/use-open-external.test.js +65 -0
  240. package/dist/web/hooks/use-open-external.test.js.map +1 -0
  241. package/dist/web/hooks/use-request-modal.d.ts +9 -0
  242. package/dist/web/hooks/use-request-modal.js +16 -0
  243. package/dist/web/hooks/use-request-modal.js.map +1 -0
  244. package/dist/web/hooks/use-request-modal.test.d.ts +1 -0
  245. package/dist/web/hooks/use-request-modal.test.js +61 -0
  246. package/dist/web/hooks/use-request-modal.test.js.map +1 -0
  247. package/dist/web/hooks/use-send-follow-up-message.d.ts +2 -0
  248. package/dist/web/hooks/use-send-follow-up-message.js +8 -0
  249. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -0
  250. package/dist/web/hooks/use-set-open-in-app-url.d.ts +1 -0
  251. package/dist/web/hooks/use-set-open-in-app-url.js +8 -0
  252. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -0
  253. package/dist/web/hooks/use-set-open-in-app-url.test.d.ts +1 -0
  254. package/dist/web/hooks/use-set-open-in-app-url.test.js +43 -0
  255. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -0
  256. package/dist/web/hooks/use-tool-info.d.ts +36 -0
  257. package/dist/web/hooks/use-tool-info.js +26 -0
  258. package/dist/web/hooks/use-tool-info.js.map +1 -0
  259. package/dist/web/hooks/use-tool-info.test-d.d.ts +1 -0
  260. package/dist/web/hooks/use-tool-info.test-d.js +109 -0
  261. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -0
  262. package/dist/web/hooks/use-tool-info.test.d.ts +1 -0
  263. package/dist/web/hooks/use-tool-info.test.js +130 -0
  264. package/dist/web/hooks/use-tool-info.test.js.map +1 -0
  265. package/dist/web/hooks/use-user.d.ts +18 -0
  266. package/dist/web/hooks/use-user.js +35 -0
  267. package/dist/web/hooks/use-user.js.map +1 -0
  268. package/dist/web/hooks/use-user.test.d.ts +1 -0
  269. package/dist/web/hooks/use-user.test.js +122 -0
  270. package/dist/web/hooks/use-user.test.js.map +1 -0
  271. package/dist/web/hooks/use-view-state.d.ts +4 -0
  272. package/dist/web/hooks/use-view-state.js +32 -0
  273. package/dist/web/hooks/use-view-state.js.map +1 -0
  274. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  275. package/dist/web/hooks/use-view-state.test.js +177 -0
  276. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  277. package/dist/web/index.d.ts +7 -0
  278. package/dist/web/index.js +8 -0
  279. package/dist/web/index.js.map +1 -0
  280. package/dist/web/mount-view.d.ts +1 -0
  281. package/dist/web/mount-view.js +27 -0
  282. package/dist/web/mount-view.js.map +1 -0
  283. package/dist/web/plugin/data-llm.test.d.ts +1 -0
  284. package/dist/web/plugin/data-llm.test.js +81 -0
  285. package/dist/web/plugin/data-llm.test.js.map +1 -0
  286. package/dist/web/plugin/plugin.d.ts +5 -0
  287. package/dist/web/plugin/plugin.js +156 -0
  288. package/dist/web/plugin/plugin.js.map +1 -0
  289. package/dist/web/plugin/scan-views.d.ts +16 -0
  290. package/dist/web/plugin/scan-views.js +88 -0
  291. package/dist/web/plugin/scan-views.js.map +1 -0
  292. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  293. package/dist/web/plugin/scan-views.test.js +99 -0
  294. package/dist/web/plugin/scan-views.test.js.map +1 -0
  295. package/dist/web/plugin/transform-data-llm.d.ts +12 -0
  296. package/dist/web/plugin/transform-data-llm.js +96 -0
  297. package/dist/web/plugin/transform-data-llm.js.map +1 -0
  298. package/dist/web/plugin/transform-data-llm.test.d.ts +1 -0
  299. package/dist/web/plugin/transform-data-llm.test.js +81 -0
  300. package/dist/web/plugin/transform-data-llm.test.js.map +1 -0
  301. package/dist/web/plugin/validate-view.d.ts +1 -0
  302. package/dist/web/plugin/validate-view.js +9 -0
  303. package/dist/web/plugin/validate-view.js.map +1 -0
  304. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  305. package/dist/web/plugin/validate-view.test.js +24 -0
  306. package/dist/web/plugin/validate-view.test.js.map +1 -0
  307. package/dist/web/proxy.d.ts +1 -0
  308. package/dist/web/proxy.js +52 -0
  309. package/dist/web/proxy.js.map +1 -0
  310. package/dist/web/types.d.ts +16 -0
  311. package/dist/web/types.js +2 -0
  312. package/dist/web/types.js.map +1 -0
  313. package/package.json +82 -35
  314. package/tsconfig.base.json +33 -0
  315. package/dist/src/server/index.d.ts +0 -2
  316. package/dist/src/server/index.js +0 -3
  317. package/dist/src/server/index.js.map +0 -1
  318. package/dist/src/server/server.d.ts +0 -13
  319. package/dist/src/server/server.js +0 -54
  320. package/dist/src/server/server.js.map +0 -1
  321. package/dist/src/server/templateHelper.js +0 -29
  322. package/dist/src/server/templateHelper.js.map +0 -1
  323. package/dist/src/server/templates/development.hbs +0 -12
  324. package/dist/src/server/templates/production.hbs +0 -6
  325. package/dist/src/server/widgetsDevServer.d.ts +0 -12
  326. package/dist/src/server/widgetsDevServer.js +0 -39
  327. package/dist/src/server/widgetsDevServer.js.map +0 -1
  328. package/dist/src/test/setup.js +0 -9
  329. package/dist/src/test/setup.js.map +0 -1
  330. package/dist/src/test/utils.d.ts +0 -28
  331. package/dist/src/test/utils.js +0 -43
  332. package/dist/src/test/utils.js.map +0 -1
  333. package/dist/src/test/widget.test.js +0 -90
  334. package/dist/src/test/widget.test.js.map +0 -1
  335. package/dist/src/web/hooks/index.d.ts +0 -13
  336. package/dist/src/web/hooks/index.js +0 -14
  337. package/dist/src/web/hooks/index.js.map +0 -1
  338. package/dist/src/web/hooks/use-call-tool.d.ts +0 -54
  339. package/dist/src/web/hooks/use-call-tool.js +0 -44
  340. package/dist/src/web/hooks/use-call-tool.js.map +0 -1
  341. package/dist/src/web/hooks/use-call-tool.test.js +0 -66
  342. package/dist/src/web/hooks/use-call-tool.test.js.map +0 -1
  343. package/dist/src/web/hooks/use-display-mode.d.ts +0 -4
  344. package/dist/src/web/hooks/use-display-mode.js +0 -7
  345. package/dist/src/web/hooks/use-display-mode.js.map +0 -1
  346. package/dist/src/web/hooks/use-display-mode.test.js.map +0 -1
  347. package/dist/src/web/hooks/use-locale.d.ts +0 -1
  348. package/dist/src/web/hooks/use-locale.js +0 -5
  349. package/dist/src/web/hooks/use-locale.js.map +0 -1
  350. package/dist/src/web/hooks/use-locale.test.js +0 -21
  351. package/dist/src/web/hooks/use-locale.test.js.map +0 -1
  352. package/dist/src/web/hooks/use-open-external.d.ts +0 -1
  353. package/dist/src/web/hooks/use-open-external.js +0 -6
  354. package/dist/src/web/hooks/use-open-external.js.map +0 -1
  355. package/dist/src/web/hooks/use-open-external.test.js +0 -24
  356. package/dist/src/web/hooks/use-open-external.test.js.map +0 -1
  357. package/dist/src/web/hooks/use-openai-global.d.ts +0 -2
  358. package/dist/src/web/hooks/use-openai-global.js +0 -21
  359. package/dist/src/web/hooks/use-openai-global.js.map +0 -1
  360. package/dist/src/web/hooks/use-request-modal.d.ts +0 -5
  361. package/dist/src/web/hooks/use-request-modal.js +0 -9
  362. package/dist/src/web/hooks/use-request-modal.js.map +0 -1
  363. package/dist/src/web/hooks/use-request-modal.test.js +0 -24
  364. package/dist/src/web/hooks/use-request-modal.test.js.map +0 -1
  365. package/dist/src/web/hooks/use-send-follow-up-message.d.ts +0 -1
  366. package/dist/src/web/hooks/use-send-follow-up-message.js +0 -11
  367. package/dist/src/web/hooks/use-send-follow-up-message.js.map +0 -1
  368. package/dist/src/web/hooks/use-theme.d.ts +0 -1
  369. package/dist/src/web/hooks/use-theme.js +0 -5
  370. package/dist/src/web/hooks/use-theme.js.map +0 -1
  371. package/dist/src/web/hooks/use-theme.test.js +0 -26
  372. package/dist/src/web/hooks/use-theme.test.js.map +0 -1
  373. package/dist/src/web/hooks/use-tool-info.d.ts +0 -5
  374. package/dist/src/web/hooks/use-tool-info.js +0 -9
  375. package/dist/src/web/hooks/use-tool-info.js.map +0 -1
  376. package/dist/src/web/hooks/use-tool-info.test.js +0 -38
  377. package/dist/src/web/hooks/use-tool-info.test.js.map +0 -1
  378. package/dist/src/web/hooks/use-tool-output.d.ts +0 -4
  379. package/dist/src/web/hooks/use-tool-output.js +0 -9
  380. package/dist/src/web/hooks/use-tool-output.js.map +0 -1
  381. package/dist/src/web/hooks/use-tool-response-metadata.d.ts +0 -4
  382. package/dist/src/web/hooks/use-tool-response-metadata.js +0 -8
  383. package/dist/src/web/hooks/use-tool-response-metadata.js.map +0 -1
  384. package/dist/src/web/hooks/use-user-agent.d.ts +0 -1
  385. package/dist/src/web/hooks/use-user-agent.js +0 -5
  386. package/dist/src/web/hooks/use-user-agent.js.map +0 -1
  387. package/dist/src/web/hooks/use-user-agent.test.js +0 -31
  388. package/dist/src/web/hooks/use-user-agent.test.js.map +0 -1
  389. package/dist/src/web/hooks/use-widget-state.d.ts +0 -4
  390. package/dist/src/web/hooks/use-widget-state.js +0 -30
  391. package/dist/src/web/hooks/use-widget-state.js.map +0 -1
  392. package/dist/src/web/hooks/use-widget-state.test.js +0 -61
  393. package/dist/src/web/hooks/use-widget-state.test.js.map +0 -1
  394. package/dist/src/web/index.d.ts +0 -4
  395. package/dist/src/web/index.js +0 -5
  396. package/dist/src/web/index.js.map +0 -1
  397. package/dist/src/web/mount-widget.d.ts +0 -1
  398. package/dist/src/web/mount-widget.js +0 -14
  399. package/dist/src/web/mount-widget.js.map +0 -1
  400. package/dist/src/web/plugin.d.ts +0 -2
  401. package/dist/src/web/plugin.js +0 -28
  402. package/dist/src/web/plugin.js.map +0 -1
  403. package/dist/src/web/types.d.ts +0 -109
  404. package/dist/src/web/types.js.map +0 -1
  405. package/dist/vitest.config.d.ts +0 -2
  406. package/dist/vitest.config.js +0 -9
  407. package/dist/vitest.config.js.map +0 -1
  408. /package/dist/{src/test/setup.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  409. /package/dist/{src/test/widget.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
  410. /package/dist/{src/web/hooks/use-call-tool.test.d.ts → cli/tunnel.test.d.ts} +0 -0
  411. /package/dist/{src/web/hooks/use-display-mode.test.d.ts → server/asset-base-url-transform-plugin.test.d.ts} +0 -0
  412. /package/dist/{src/web/hooks/use-locale.test.d.ts → server/content-helpers.test.d.ts} +0 -0
  413. /package/dist/{src/web/hooks/use-open-external.test.d.ts → server/express.test.d.ts} +0 -0
  414. /package/dist/{src/web/hooks/use-request-modal.test.d.ts → server/middleware.test-d.d.ts} +0 -0
  415. /package/dist/{src/web/hooks/use-theme.test.d.ts → server/middleware.test.d.ts} +0 -0
  416. /package/dist/{src/web/hooks/use-tool-info.test.d.ts → server/tunnel-proxy-router.test.d.ts} +0 -0
  417. /package/dist/{src/web/hooks/use-user-agent.test.d.ts → test/view.test.d.ts} +0 -0
  418. /package/dist/{src/web → web/bridges/apps-sdk}/types.js +0 -0
  419. /package/dist/{src/web/hooks/use-widget-state.test.d.ts → web/bridges/mcp-app/use-mcp-app-context.test.d.ts} +0 -0
@@ -0,0 +1,31 @@
1
+ import http from "node:http";
2
+ import { TunnelManager } from "./tunnel.js";
3
+ import { createTunnelHandler } from "./tunnel-handler.js";
4
+ export async function startTunnelControlServer(getPort, options) {
5
+ const manager = new TunnelManager({ getPort, spawn: options?.spawn });
6
+ const server = http.createServer(createTunnelHandler(manager));
7
+ await new Promise((resolve, reject) => {
8
+ server.once("error", reject);
9
+ server.listen(0, "127.0.0.1", () => {
10
+ server.off("error", reject);
11
+ resolve();
12
+ });
13
+ });
14
+ const address = server.address();
15
+ if (typeof address === "string" || address === null) {
16
+ server.close();
17
+ throw new Error("tunnel control server has no address");
18
+ }
19
+ return {
20
+ port: address.port,
21
+ manager,
22
+ close: () => new Promise((resolve, reject) => {
23
+ manager.stop();
24
+ // Force any in-flight SSE connections to drop so server.close()
25
+ // doesn't hang indefinitely on subscribers that never end on their own.
26
+ server.closeAllConnections();
27
+ server.close((err) => (err ? reject(err) : resolve()));
28
+ }),
29
+ };
30
+ }
31
+ //# sourceMappingURL=tunnel-control-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-control-server.js","sourceRoot":"","sources":["../../src/cli/tunnel-control-server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAgB,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAQ1D,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAqB,EACrB,OAA6B;IAE7B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IACjC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO;QACP,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpC,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,gEAAgE;YAChE,wEAAwE;YACxE,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC;KACL,CAAC;AACJ,CAAC","sourcesContent":["import http from \"node:http\";\nimport { type SpawnFn, TunnelManager } from \"./tunnel.js\";\nimport { createTunnelHandler } from \"./tunnel-handler.js\";\n\nexport type TunnelControlServer = {\n port: number;\n manager: TunnelManager;\n close: () => Promise<void>;\n};\n\nexport async function startTunnelControlServer(\n getPort: () => number,\n options?: { spawn?: SpawnFn },\n): Promise<TunnelControlServer> {\n const manager = new TunnelManager({ getPort, spawn: options?.spawn });\n const server = http.createServer(createTunnelHandler(manager));\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n if (typeof address === \"string\" || address === null) {\n server.close();\n throw new Error(\"tunnel control server has no address\");\n }\n return {\n port: address.port,\n manager,\n close: () =>\n new Promise<void>((resolve, reject) => {\n manager.stop();\n // Force any in-flight SSE connections to drop so server.close()\n // doesn't hang indefinitely on subscribers that never end on their own.\n server.closeAllConnections();\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { startTunnelControlServer } from "./tunnel-control-server.js";
3
+ let openControl;
4
+ afterEach(async () => {
5
+ await openControl?.close();
6
+ openControl = undefined;
7
+ });
8
+ describe("startTunnelControlServer", () => {
9
+ it("listens on a random loopback port and serves /__skybridge/tunnel/events", async () => {
10
+ const control = await startTunnelControlServer(() => 3000);
11
+ openControl = control;
12
+ expect(control.port).toBeGreaterThan(0);
13
+ const res = await fetch(`http://127.0.0.1:${control.port}/__skybridge/tunnel/events`);
14
+ expect(res.headers.get("content-type")).toMatch(/text\/event-stream/);
15
+ const reader = res.body.getReader();
16
+ const { value } = await reader.read();
17
+ const chunk = new TextDecoder().decode(value);
18
+ expect(chunk).toContain("event: state");
19
+ expect(chunk).toContain('"status":"idle"');
20
+ await reader.cancel();
21
+ });
22
+ it("two concurrent control servers get different ports", async () => {
23
+ const a = await startTunnelControlServer(() => 3000);
24
+ const b = await startTunnelControlServer(() => 4000);
25
+ try {
26
+ expect(a.port).not.toBe(b.port);
27
+ }
28
+ finally {
29
+ await a.close();
30
+ await b.close();
31
+ }
32
+ });
33
+ it("close() shuts the listener", async () => {
34
+ const control = await startTunnelControlServer(() => 3000);
35
+ await control.close();
36
+ await expect(fetch(`http://127.0.0.1:${control.port}/__skybridge/tunnel/events`)).rejects.toThrow();
37
+ });
38
+ });
39
+ //# sourceMappingURL=tunnel-control-server.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-control-server.test.js","sourceRoot":"","sources":["../../src/cli/tunnel-control-server.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,IAAI,WAAuD,CAAC;AAC5D,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3B,WAAW,GAAG,SAAS,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3D,WAAW,GAAG,OAAO,CAAC;QAEtB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAExC,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,oBAAoB,OAAO,CAAC,IAAI,4BAA4B,CAC7D,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAEtE,MAAM,MAAM,GAAI,GAAG,CAAC,IAAmC,CAAC,SAAS,EAAE,CAAC;QACpE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC3C,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,CAAC,GAAG,MAAM,wBAAwB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,MAAM,wBAAwB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,MAAM,CACV,KAAK,CAAC,oBAAoB,OAAO,CAAC,IAAI,4BAA4B,CAAC,CACpE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { afterEach, describe, expect, it } from \"vitest\";\nimport { startTunnelControlServer } from \"./tunnel-control-server.js\";\n\nlet openControl: { close: () => Promise<void> } | undefined;\nafterEach(async () => {\n await openControl?.close();\n openControl = undefined;\n});\n\ndescribe(\"startTunnelControlServer\", () => {\n it(\"listens on a random loopback port and serves /__skybridge/tunnel/events\", async () => {\n const control = await startTunnelControlServer(() => 3000);\n openControl = control;\n\n expect(control.port).toBeGreaterThan(0);\n\n const res = await fetch(\n `http://127.0.0.1:${control.port}/__skybridge/tunnel/events`,\n );\n expect(res.headers.get(\"content-type\")).toMatch(/text\\/event-stream/);\n\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const { value } = await reader.read();\n const chunk = new TextDecoder().decode(value);\n expect(chunk).toContain(\"event: state\");\n expect(chunk).toContain('\"status\":\"idle\"');\n await reader.cancel();\n });\n\n it(\"two concurrent control servers get different ports\", async () => {\n const a = await startTunnelControlServer(() => 3000);\n const b = await startTunnelControlServer(() => 4000);\n try {\n expect(a.port).not.toBe(b.port);\n } finally {\n await a.close();\n await b.close();\n }\n });\n\n it(\"close() shuts the listener\", async () => {\n const control = await startTunnelControlServer(() => 3000);\n await control.close();\n await expect(\n fetch(`http://127.0.0.1:${control.port}/__skybridge/tunnel/events`),\n ).rejects.toThrow();\n });\n});\n"]}
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { TunnelManager } from "./tunnel.js";
3
+ export declare function createTunnelHandler(manager: TunnelManager): (req: IncomingMessage, res: ServerResponse) => void;
@@ -0,0 +1,48 @@
1
+ export function createTunnelHandler(manager) {
2
+ return (req, res) => {
3
+ if (req.url === "/__skybridge/tunnel" && req.method === "POST") {
4
+ manager.start();
5
+ sendJson(res, 200, manager.getState());
6
+ return;
7
+ }
8
+ if (req.url === "/__skybridge/tunnel" && req.method === "DELETE") {
9
+ manager.stop();
10
+ sendJson(res, 200, manager.getState());
11
+ return;
12
+ }
13
+ if (req.url === "/__skybridge/tunnel/events" && req.method === "GET") {
14
+ writeSseHead(res);
15
+ writeSse(res, "state", manager.getState());
16
+ const onState = (s) => {
17
+ writeSse(res, "state", s);
18
+ };
19
+ const onActivity = (a) => {
20
+ writeSse(res, "activity", a);
21
+ };
22
+ manager.on("state", onState);
23
+ manager.on("activity", onActivity);
24
+ req.on("close", () => {
25
+ manager.off("state", onState);
26
+ manager.off("activity", onActivity);
27
+ });
28
+ return;
29
+ }
30
+ res.writeHead(404).end();
31
+ };
32
+ }
33
+ function sendJson(res, status, data) {
34
+ res.writeHead(status, { "Content-Type": "application/json" });
35
+ res.end(JSON.stringify(data));
36
+ }
37
+ function writeSseHead(res) {
38
+ res.writeHead(200, {
39
+ "Content-Type": "text/event-stream",
40
+ "Cache-Control": "no-cache, no-transform",
41
+ Connection: "keep-alive",
42
+ });
43
+ res.flushHeaders?.();
44
+ }
45
+ function writeSse(res, event, data) {
46
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
47
+ }
48
+ //# sourceMappingURL=tunnel-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-handler.js","sourceRoot":"","sources":["../../src/cli/tunnel-handler.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,OAAO,CAAC,GAAoB,EAAE,GAAmB,EAAQ,EAAE;QACzD,IAAI,GAAG,CAAC,GAAG,KAAK,qBAAqB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC/D,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,KAAK,qBAAqB,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjE,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,KAAK,4BAA4B,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YACrE,YAAY,CAAC,GAAG,CAAC,CAAC;YAClB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC3C,MAAM,OAAO,GAAG,CAAC,CAAc,EAAE,EAAE;gBACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YAC5B,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,CAAC,CAAiB,EAAE,EAAE;gBACvC,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;YAC/B,CAAC,CAAC;YACF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YACnC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACnB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC3B,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAClE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,YAAY,CAAC,GAAmB;IACvC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,wBAAwB;QACzC,UAAU,EAAE,YAAY;KACzB,CAAC,CAAC;IACH,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC;AACvB,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,KAAa,EAAE,IAAa;IACjE,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClE,CAAC","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { TunnelActivity, TunnelManager, TunnelState } from \"./tunnel.js\";\n\nexport function createTunnelHandler(manager: TunnelManager) {\n return (req: IncomingMessage, res: ServerResponse): void => {\n if (req.url === \"/__skybridge/tunnel\" && req.method === \"POST\") {\n manager.start();\n sendJson(res, 200, manager.getState());\n return;\n }\n if (req.url === \"/__skybridge/tunnel\" && req.method === \"DELETE\") {\n manager.stop();\n sendJson(res, 200, manager.getState());\n return;\n }\n if (req.url === \"/__skybridge/tunnel/events\" && req.method === \"GET\") {\n writeSseHead(res);\n writeSse(res, \"state\", manager.getState());\n const onState = (s: TunnelState) => {\n writeSse(res, \"state\", s);\n };\n const onActivity = (a: TunnelActivity) => {\n writeSse(res, \"activity\", a);\n };\n manager.on(\"state\", onState);\n manager.on(\"activity\", onActivity);\n req.on(\"close\", () => {\n manager.off(\"state\", onState);\n manager.off(\"activity\", onActivity);\n });\n return;\n }\n res.writeHead(404).end();\n };\n}\n\nfunction sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(data));\n}\n\nfunction writeSseHead(res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache, no-transform\",\n Connection: \"keep-alive\",\n });\n res.flushHeaders?.();\n}\n\nfunction writeSse(res: ServerResponse, event: string, data: unknown): void {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n}\n"]}
@@ -0,0 +1,105 @@
1
+ import { EventEmitter } from "node:events";
2
+ import http from "node:http";
3
+ import { Readable } from "node:stream";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { TunnelManager } from "./tunnel.js";
6
+ import { createTunnelHandler } from "./tunnel-handler.js";
7
+ let openServer;
8
+ afterEach(() => openServer?.close());
9
+ function makeFakeChild() {
10
+ const child = new EventEmitter();
11
+ child.stdout = new Readable({ read() { } });
12
+ child.stderr = new Readable({ read() { } });
13
+ child.kill = vi.fn(() => true);
14
+ return child;
15
+ }
16
+ async function listen(handler) {
17
+ const server = http.createServer(handler);
18
+ await new Promise((resolve) => server.listen(0, resolve));
19
+ const port = server.address().port;
20
+ return { port, server };
21
+ }
22
+ async function listenWithHandler() {
23
+ const child = makeFakeChild();
24
+ const manager = new TunnelManager({
25
+ getPort: () => 3000,
26
+ spawn: () => child,
27
+ });
28
+ const { port, server } = await listen(createTunnelHandler(manager));
29
+ openServer = server;
30
+ return { port, child, manager };
31
+ }
32
+ describe("createTunnelHandler", () => {
33
+ it("POST /__skybridge/tunnel starts the tunnel and returns the current state", async () => {
34
+ const { port, child } = await listenWithHandler();
35
+ const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
36
+ method: "POST",
37
+ });
38
+ expect(res.status).toBe(200);
39
+ expect(await res.json()).toEqual({
40
+ status: "starting",
41
+ message: "Starting tunnel…",
42
+ });
43
+ expect(child.kill).not.toHaveBeenCalled();
44
+ });
45
+ it("POST /__skybridge/tunnel is idempotent — second call does not respawn", async () => {
46
+ const { port, manager } = await listenWithHandler();
47
+ const startSpy = vi.spyOn(manager, "start");
48
+ await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
49
+ method: "POST",
50
+ });
51
+ await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
52
+ method: "POST",
53
+ });
54
+ expect(startSpy).toHaveBeenCalledTimes(2);
55
+ // Manager.start() is internally idempotent (verified in tunnel.test.ts).
56
+ });
57
+ it("DELETE /__skybridge/tunnel stops the tunnel", async () => {
58
+ const { port, child } = await listenWithHandler();
59
+ await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
60
+ method: "POST",
61
+ });
62
+ const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
63
+ method: "DELETE",
64
+ });
65
+ expect(res.status).toBe(200);
66
+ expect(await res.json()).toEqual({ status: "idle" });
67
+ expect(child.kill).toHaveBeenCalled();
68
+ });
69
+ it("GET /__skybridge/tunnel/events streams the current state on connect", async () => {
70
+ const { port, child } = await listenWithHandler();
71
+ await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
72
+ method: "POST",
73
+ });
74
+ child.stdout.emit("data", Buffer.from("Forwarding: https://abc.tunnel.example -> http://localhost:3000\n"));
75
+ const res = await fetch(`http://localhost:${port}/__skybridge/tunnel/events`);
76
+ expect(res.headers.get("content-type")).toMatch(/text\/event-stream/);
77
+ expect(res.body).toBeTruthy();
78
+ const reader = res.body.getReader();
79
+ const { value } = await reader.read();
80
+ const chunk = new TextDecoder().decode(value);
81
+ expect(chunk).toContain("event: state");
82
+ expect(chunk).toContain('"status":"connected"');
83
+ expect(chunk).toContain('"url":"https://abc.tunnel.example"');
84
+ await reader.cancel();
85
+ });
86
+ it("GET /__skybridge/tunnel/events sends the current error state on connect", async () => {
87
+ const { port, child } = await listenWithHandler();
88
+ await fetch(`http://localhost:${port}/__skybridge/tunnel`, {
89
+ method: "POST",
90
+ });
91
+ child.stderr.emit("data", Buffer.from("boom: tunnel auth failed\n"));
92
+ child.emit("close", 1);
93
+ const res = await fetch(`http://localhost:${port}/__skybridge/tunnel/events`);
94
+ expect(res.headers.get("content-type")).toMatch(/text\/event-stream/);
95
+ expect(res.body).toBeTruthy();
96
+ const reader = res.body.getReader();
97
+ const { value } = await reader.read();
98
+ const chunk = new TextDecoder().decode(value);
99
+ expect(chunk).toContain("event: state");
100
+ expect(chunk).toContain('"status":"error"');
101
+ expect(chunk).toContain("boom: tunnel auth failed");
102
+ await reader.cancel();
103
+ });
104
+ });
105
+ //# sourceMappingURL=tunnel-handler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-handler.test.js","sourceRoot":"","sources":["../../src/cli/tunnel-handler.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"node:events\";\nimport http from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport { afterEach, describe, expect, it, vi } from \"vitest\";\nimport { TunnelManager } from \"./tunnel.js\";\nimport { createTunnelHandler } from \"./tunnel-handler.js\";\n\nlet openServer: http.Server | undefined;\nafterEach(() => openServer?.close());\n\ntype FakeChild = EventEmitter & {\n stdout: Readable;\n stderr: Readable;\n kill: ReturnType<typeof vi.fn<() => boolean>>;\n};\n\nfunction makeFakeChild(): FakeChild {\n const child = new EventEmitter() as FakeChild;\n child.stdout = new Readable({ read() {} });\n child.stderr = new Readable({ read() {} });\n child.kill = vi.fn<() => boolean>(() => true);\n return child;\n}\n\nasync function listen(handler: http.RequestListener) {\n const server = http.createServer(handler);\n await new Promise<void>((resolve) => server.listen(0, resolve));\n const port = (server.address() as { port: number }).port;\n return { port, server };\n}\n\nasync function listenWithHandler() {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n const { port, server } = await listen(createTunnelHandler(manager));\n openServer = server;\n return { port, child, manager };\n}\n\ndescribe(\"createTunnelHandler\", () => {\n it(\"POST /__skybridge/tunnel starts the tunnel and returns the current state\", async () => {\n const { port, child } = await listenWithHandler();\n\n const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n expect(res.status).toBe(200);\n expect(await res.json()).toEqual({\n status: \"starting\",\n message: \"Starting tunnel…\",\n });\n expect(child.kill).not.toHaveBeenCalled();\n });\n\n it(\"POST /__skybridge/tunnel is idempotent — second call does not respawn\", async () => {\n const { port, manager } = await listenWithHandler();\n const startSpy = vi.spyOn(manager, \"start\");\n\n await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n\n expect(startSpy).toHaveBeenCalledTimes(2);\n // Manager.start() is internally idempotent (verified in tunnel.test.ts).\n });\n\n it(\"DELETE /__skybridge/tunnel stops the tunnel\", async () => {\n const { port, child } = await listenWithHandler();\n await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n\n const res = await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"DELETE\",\n });\n expect(res.status).toBe(200);\n expect(await res.json()).toEqual({ status: \"idle\" });\n expect(child.kill).toHaveBeenCalled();\n });\n\n it(\"GET /__skybridge/tunnel/events streams the current state on connect\", async () => {\n const { port, child } = await listenWithHandler();\n await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n child.stdout.emit(\n \"data\",\n Buffer.from(\n \"Forwarding: https://abc.tunnel.example -> http://localhost:3000\\n\",\n ),\n );\n\n const res = await fetch(\n `http://localhost:${port}/__skybridge/tunnel/events`,\n );\n expect(res.headers.get(\"content-type\")).toMatch(/text\\/event-stream/);\n expect(res.body).toBeTruthy();\n\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const { value } = await reader.read();\n const chunk = new TextDecoder().decode(value);\n\n expect(chunk).toContain(\"event: state\");\n expect(chunk).toContain('\"status\":\"connected\"');\n expect(chunk).toContain('\"url\":\"https://abc.tunnel.example\"');\n\n await reader.cancel();\n });\n\n it(\"GET /__skybridge/tunnel/events sends the current error state on connect\", async () => {\n const { port, child } = await listenWithHandler();\n await fetch(`http://localhost:${port}/__skybridge/tunnel`, {\n method: \"POST\",\n });\n child.stderr.emit(\"data\", Buffer.from(\"boom: tunnel auth failed\\n\"));\n child.emit(\"close\", 1);\n\n const res = await fetch(\n `http://localhost:${port}/__skybridge/tunnel/events`,\n );\n expect(res.headers.get(\"content-type\")).toMatch(/text\\/event-stream/);\n expect(res.body).toBeTruthy();\n\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const { value } = await reader.read();\n const chunk = new TextDecoder().decode(value);\n\n expect(chunk).toContain(\"event: state\");\n expect(chunk).toContain('\"status\":\"error\"');\n expect(chunk).toContain(\"boom: tunnel auth failed\");\n\n await reader.cancel();\n });\n});\n"]}
@@ -0,0 +1,57 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Readable } from "node:stream";
3
+ export type TunnelState = {
4
+ status: "idle";
5
+ } | {
6
+ status: "starting";
7
+ message: string;
8
+ } | {
9
+ status: "connected";
10
+ url: string;
11
+ } | {
12
+ status: "error";
13
+ message: string;
14
+ };
15
+ export type ParsedStdoutEvent = {
16
+ kind: "connected";
17
+ url: string;
18
+ } | {
19
+ kind: "starting";
20
+ message: string;
21
+ };
22
+ export declare function parseStdoutLine(line: string): ParsedStdoutEvent | null;
23
+ export type TunnelActivity = {
24
+ time: string;
25
+ text: string;
26
+ level: "log" | "error";
27
+ };
28
+ export type TunnelChildProcess = {
29
+ stdout: Pick<Readable, "on"> | null;
30
+ stderr: Pick<Readable, "on"> | null;
31
+ kill: (signal?: NodeJS.Signals | number) => boolean;
32
+ on(event: "error", listener: (err: Error) => void): unknown;
33
+ on(event: "close", listener: (code: number | null) => void): unknown;
34
+ };
35
+ export type SpawnFn = (port: number) => TunnelChildProcess;
36
+ export declare class TunnelManager extends EventEmitter {
37
+ private state;
38
+ private child;
39
+ private timeout;
40
+ private stderrBuffer;
41
+ private connected;
42
+ private readonly getPort;
43
+ private readonly spawnFn;
44
+ constructor(opts: {
45
+ getPort: () => number;
46
+ spawn?: SpawnFn;
47
+ });
48
+ getState(): TunnelState;
49
+ subscribe(listener: (state: TunnelState) => void): () => void;
50
+ start(): void;
51
+ stop(): void;
52
+ private handleStdout;
53
+ private handleStderr;
54
+ private setState;
55
+ private emitActivity;
56
+ private clearConnectTimeout;
57
+ }
@@ -0,0 +1,154 @@
1
+ import { EventEmitter } from "node:events";
2
+ import spawn from "cross-spawn";
3
+ const FORWARDING_RE = /Forwarding:\s+(https?:\/\/\S+)\s*->\s*(\S+)/;
4
+ export function parseStdoutLine(line) {
5
+ const trimmed = line.trim();
6
+ if (!trimmed) {
7
+ return null;
8
+ }
9
+ const match = trimmed.match(FORWARDING_RE);
10
+ if (match?.[1]) {
11
+ return { kind: "connected", url: match[1].replace(/\/$/, "") };
12
+ }
13
+ return { kind: "starting", message: trimmed };
14
+ }
15
+ const CONNECT_TIMEOUT_MS = 60_000;
16
+ const STDERR_BUFFER_BYTES = 1024;
17
+ const defaultSpawn = (port) => spawn("npx", ["--yes", "alpic", "tunnel", "--port", String(port), "--plain"], {
18
+ stdio: ["ignore", "pipe", "pipe"],
19
+ });
20
+ export class TunnelManager extends EventEmitter {
21
+ state = { status: "idle" };
22
+ child = null;
23
+ timeout = null;
24
+ stderrBuffer = "";
25
+ connected = false;
26
+ getPort;
27
+ spawnFn;
28
+ constructor(opts) {
29
+ super();
30
+ this.getPort = opts.getPort;
31
+ this.spawnFn = opts.spawn ?? defaultSpawn;
32
+ // Multiple SSE subscribers (CLI, devtools, ad-hoc curl) can each register
33
+ // state + activity listeners; the default cap of 10 is easy to hit.
34
+ this.setMaxListeners(0);
35
+ }
36
+ getState() {
37
+ return this.state;
38
+ }
39
+ subscribe(listener) {
40
+ listener(this.state);
41
+ this.on("state", listener);
42
+ return () => {
43
+ this.off("state", listener);
44
+ };
45
+ }
46
+ start() {
47
+ if (this.state.status === "starting" || this.state.status === "connected") {
48
+ return;
49
+ }
50
+ this.connected = false;
51
+ this.stderrBuffer = "";
52
+ this.setState({ status: "starting", message: "Starting tunnel…" });
53
+ const child = this.spawnFn(this.getPort());
54
+ this.child = child;
55
+ this.timeout = setTimeout(() => {
56
+ if (!this.connected) {
57
+ this.setState({
58
+ status: "error",
59
+ message: "Tunnel connection timed out after one minute",
60
+ });
61
+ // Detach before killing so the imminent `close` event is treated as
62
+ // stale and does not overwrite the timeout error message.
63
+ this.child = null;
64
+ child.kill();
65
+ }
66
+ }, CONNECT_TIMEOUT_MS);
67
+ child.stdout?.on("data", (data) => {
68
+ this.handleStdout(data);
69
+ });
70
+ child.stderr?.on("data", (data) => {
71
+ this.handleStderr(data);
72
+ });
73
+ child.on("error", (err) => {
74
+ // Stale event from a child we've already replaced via stop()+start().
75
+ if (child !== this.child) {
76
+ return;
77
+ }
78
+ this.clearConnectTimeout();
79
+ this.setState({ status: "error", message: err.message });
80
+ });
81
+ child.on("close", (code) => {
82
+ // Stale event from a child we've already replaced via stop()+start().
83
+ if (child !== this.child) {
84
+ return;
85
+ }
86
+ this.clearConnectTimeout();
87
+ if (code !== 0 && code !== null) {
88
+ const detail = this.stderrBuffer.trim() || `exited with code ${code}`;
89
+ this.setState({ status: "error", message: detail });
90
+ }
91
+ else {
92
+ this.setState({ status: "idle" });
93
+ }
94
+ this.child = null;
95
+ });
96
+ }
97
+ stop() {
98
+ this.clearConnectTimeout();
99
+ if (this.child) {
100
+ this.child.kill();
101
+ this.child = null;
102
+ }
103
+ this.setState({ status: "idle" });
104
+ }
105
+ handleStdout(data) {
106
+ const lines = data.toString().split("\n");
107
+ for (const raw of lines) {
108
+ const parsed = parseStdoutLine(raw);
109
+ if (!parsed) {
110
+ continue;
111
+ }
112
+ if (parsed.kind === "connected") {
113
+ this.connected = true;
114
+ this.clearConnectTimeout();
115
+ this.setState({ status: "connected", url: parsed.url });
116
+ }
117
+ else if (this.connected) {
118
+ this.emitActivity(parsed.message, "log");
119
+ }
120
+ else {
121
+ this.setState({ status: "starting", message: parsed.message });
122
+ }
123
+ }
124
+ }
125
+ handleStderr(data) {
126
+ const text = data.toString().trim();
127
+ if (!text) {
128
+ return;
129
+ }
130
+ this.stderrBuffer = (this.stderrBuffer + text).slice(-STDERR_BUFFER_BYTES);
131
+ for (const line of text.split("\n").filter(Boolean)) {
132
+ this.emitActivity(line, "error");
133
+ }
134
+ }
135
+ setState(next) {
136
+ this.state = next;
137
+ this.emit("state", next);
138
+ }
139
+ emitActivity(text, level) {
140
+ const activity = {
141
+ time: new Date().toISOString(),
142
+ text,
143
+ level,
144
+ };
145
+ this.emit("activity", activity);
146
+ }
147
+ clearConnectTimeout() {
148
+ if (this.timeout) {
149
+ clearTimeout(this.timeout);
150
+ this.timeout = null;
151
+ }
152
+ }
153
+ }
154
+ //# sourceMappingURL=tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../../src/cli/tunnel.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"node:events\";\nimport type { Readable } from \"node:stream\";\nimport spawn from \"cross-spawn\";\n\nexport type TunnelState =\n | { status: \"idle\" }\n | { status: \"starting\"; message: string }\n | { status: \"connected\"; url: string }\n | { status: \"error\"; message: string };\n\nexport type ParsedStdoutEvent =\n | { kind: \"connected\"; url: string }\n | { kind: \"starting\"; message: string };\n\nconst FORWARDING_RE = /Forwarding:\\s+(https?:\\/\\/\\S+)\\s*->\\s*(\\S+)/;\n\nexport function parseStdoutLine(line: string): ParsedStdoutEvent | null {\n const trimmed = line.trim();\n if (!trimmed) {\n return null;\n }\n\n const match = trimmed.match(FORWARDING_RE);\n if (match?.[1]) {\n return { kind: \"connected\", url: match[1].replace(/\\/$/, \"\") };\n }\n return { kind: \"starting\", message: trimmed };\n}\n\nconst CONNECT_TIMEOUT_MS = 60_000;\nconst STDERR_BUFFER_BYTES = 1024;\n\nexport type TunnelActivity = {\n time: string;\n text: string;\n level: \"log\" | \"error\";\n};\n\nexport type TunnelChildProcess = {\n stdout: Pick<Readable, \"on\"> | null;\n stderr: Pick<Readable, \"on\"> | null;\n kill: (signal?: NodeJS.Signals | number) => boolean;\n on(event: \"error\", listener: (err: Error) => void): unknown;\n on(event: \"close\", listener: (code: number | null) => void): unknown;\n};\n\nexport type SpawnFn = (port: number) => TunnelChildProcess;\n\nconst defaultSpawn: SpawnFn = (port) =>\n spawn(\n \"npx\",\n [\"--yes\", \"alpic\", \"tunnel\", \"--port\", String(port), \"--plain\"],\n {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n\nexport class TunnelManager extends EventEmitter {\n private state: TunnelState = { status: \"idle\" };\n private child: ReturnType<SpawnFn> | null = null;\n private timeout: NodeJS.Timeout | null = null;\n private stderrBuffer = \"\";\n private connected = false;\n private readonly getPort: () => number;\n private readonly spawnFn: SpawnFn;\n\n constructor(opts: { getPort: () => number; spawn?: SpawnFn }) {\n super();\n this.getPort = opts.getPort;\n this.spawnFn = opts.spawn ?? defaultSpawn;\n // Multiple SSE subscribers (CLI, devtools, ad-hoc curl) can each register\n // state + activity listeners; the default cap of 10 is easy to hit.\n this.setMaxListeners(0);\n }\n\n getState(): TunnelState {\n return this.state;\n }\n\n subscribe(listener: (state: TunnelState) => void): () => void {\n listener(this.state);\n this.on(\"state\", listener);\n return () => {\n this.off(\"state\", listener);\n };\n }\n\n start(): void {\n if (this.state.status === \"starting\" || this.state.status === \"connected\") {\n return;\n }\n\n this.connected = false;\n this.stderrBuffer = \"\";\n this.setState({ status: \"starting\", message: \"Starting tunnel…\" });\n\n const child = this.spawnFn(this.getPort());\n this.child = child;\n\n this.timeout = setTimeout(() => {\n if (!this.connected) {\n this.setState({\n status: \"error\",\n message: \"Tunnel connection timed out after one minute\",\n });\n // Detach before killing so the imminent `close` event is treated as\n // stale and does not overwrite the timeout error message.\n this.child = null;\n child.kill();\n }\n }, CONNECT_TIMEOUT_MS);\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n this.handleStdout(data);\n });\n child.stderr?.on(\"data\", (data: Buffer) => {\n this.handleStderr(data);\n });\n\n child.on(\"error\", (err: Error) => {\n // Stale event from a child we've already replaced via stop()+start().\n if (child !== this.child) {\n return;\n }\n this.clearConnectTimeout();\n this.setState({ status: \"error\", message: err.message });\n });\n\n child.on(\"close\", (code: number | null) => {\n // Stale event from a child we've already replaced via stop()+start().\n if (child !== this.child) {\n return;\n }\n this.clearConnectTimeout();\n if (code !== 0 && code !== null) {\n const detail = this.stderrBuffer.trim() || `exited with code ${code}`;\n this.setState({ status: \"error\", message: detail });\n } else {\n this.setState({ status: \"idle\" });\n }\n this.child = null;\n });\n }\n\n stop(): void {\n this.clearConnectTimeout();\n if (this.child) {\n this.child.kill();\n this.child = null;\n }\n this.setState({ status: \"idle\" });\n }\n\n private handleStdout(data: Buffer): void {\n const lines = data.toString().split(\"\\n\");\n for (const raw of lines) {\n const parsed = parseStdoutLine(raw);\n if (!parsed) {\n continue;\n }\n if (parsed.kind === \"connected\") {\n this.connected = true;\n this.clearConnectTimeout();\n this.setState({ status: \"connected\", url: parsed.url });\n } else if (this.connected) {\n this.emitActivity(parsed.message, \"log\");\n } else {\n this.setState({ status: \"starting\", message: parsed.message });\n }\n }\n }\n\n private handleStderr(data: Buffer): void {\n const text = data.toString().trim();\n if (!text) {\n return;\n }\n this.stderrBuffer = (this.stderrBuffer + text).slice(-STDERR_BUFFER_BYTES);\n for (const line of text.split(\"\\n\").filter(Boolean)) {\n this.emitActivity(line, \"error\");\n }\n }\n\n private setState(next: TunnelState): void {\n this.state = next;\n this.emit(\"state\", next);\n }\n\n private emitActivity(text: string, level: \"log\" | \"error\"): void {\n const activity: TunnelActivity = {\n time: new Date().toISOString(),\n text,\n level,\n };\n this.emit(\"activity\", activity);\n }\n\n private clearConnectTimeout(): void {\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n}\n"]}