enpilink 1.0.2

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 (477) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/bin/run.js +5 -0
  4. package/dist/cli/build-helpers.d.ts +8 -0
  5. package/dist/cli/build-helpers.js +105 -0
  6. package/dist/cli/build-helpers.js.map +1 -0
  7. package/dist/cli/build-helpers.test.d.ts +1 -0
  8. package/dist/cli/build-helpers.test.js +100 -0
  9. package/dist/cli/build-helpers.test.js.map +1 -0
  10. package/dist/cli/detect-port.d.ts +18 -0
  11. package/dist/cli/detect-port.js +50 -0
  12. package/dist/cli/detect-port.js.map +1 -0
  13. package/dist/cli/ensure-ssh-key.d.ts +17 -0
  14. package/dist/cli/ensure-ssh-key.js +45 -0
  15. package/dist/cli/ensure-ssh-key.js.map +1 -0
  16. package/dist/cli/ensure-ssh-key.test.d.ts +1 -0
  17. package/dist/cli/ensure-ssh-key.test.js +68 -0
  18. package/dist/cli/ensure-ssh-key.test.js.map +1 -0
  19. package/dist/cli/header.d.ts +4 -0
  20. package/dist/cli/header.js +6 -0
  21. package/dist/cli/header.js.map +1 -0
  22. package/dist/cli/resolve-views-dir.d.ts +1 -0
  23. package/dist/cli/resolve-views-dir.js +17 -0
  24. package/dist/cli/resolve-views-dir.js.map +1 -0
  25. package/dist/cli/run-command.d.ts +2 -0
  26. package/dist/cli/run-command.js +43 -0
  27. package/dist/cli/run-command.js.map +1 -0
  28. package/dist/cli/telemetry.d.ts +14 -0
  29. package/dist/cli/telemetry.js +24 -0
  30. package/dist/cli/telemetry.js.map +1 -0
  31. package/dist/cli/tunnel-control-server.d.ts +11 -0
  32. package/dist/cli/tunnel-control-server.js +35 -0
  33. package/dist/cli/tunnel-control-server.js.map +1 -0
  34. package/dist/cli/tunnel-control-server.test.d.ts +1 -0
  35. package/dist/cli/tunnel-control-server.test.js +39 -0
  36. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  37. package/dist/cli/tunnel-handler.d.ts +3 -0
  38. package/dist/cli/tunnel-handler.js +48 -0
  39. package/dist/cli/tunnel-handler.js.map +1 -0
  40. package/dist/cli/tunnel-handler.test.d.ts +1 -0
  41. package/dist/cli/tunnel-handler.test.js +107 -0
  42. package/dist/cli/tunnel-handler.test.js.map +1 -0
  43. package/dist/cli/tunnel-providers/index.d.ts +5 -0
  44. package/dist/cli/tunnel-providers/index.js +5 -0
  45. package/dist/cli/tunnel-providers/index.js.map +1 -0
  46. package/dist/cli/tunnel-providers/srv-us.d.ts +18 -0
  47. package/dist/cli/tunnel-providers/srv-us.js +66 -0
  48. package/dist/cli/tunnel-providers/srv-us.js.map +1 -0
  49. package/dist/cli/tunnel-providers/srv-us.test.d.ts +1 -0
  50. package/dist/cli/tunnel-providers/srv-us.test.js +74 -0
  51. package/dist/cli/tunnel-providers/srv-us.test.js.map +1 -0
  52. package/dist/cli/tunnel-providers/types.d.ts +49 -0
  53. package/dist/cli/tunnel-providers/types.js +2 -0
  54. package/dist/cli/tunnel-providers/types.js.map +1 -0
  55. package/dist/cli/tunnel.d.ts +75 -0
  56. package/dist/cli/tunnel.js +254 -0
  57. package/dist/cli/tunnel.js.map +1 -0
  58. package/dist/cli/tunnel.test.d.ts +1 -0
  59. package/dist/cli/tunnel.test.js +255 -0
  60. package/dist/cli/tunnel.test.js.map +1 -0
  61. package/dist/cli/types.d.ts +5 -0
  62. package/dist/cli/types.js +2 -0
  63. package/dist/cli/types.js.map +1 -0
  64. package/dist/cli/use-execute-steps.d.ts +11 -0
  65. package/dist/cli/use-execute-steps.js +36 -0
  66. package/dist/cli/use-execute-steps.js.map +1 -0
  67. package/dist/cli/use-messages.d.ts +3 -0
  68. package/dist/cli/use-messages.js +11 -0
  69. package/dist/cli/use-messages.js.map +1 -0
  70. package/dist/cli/use-nodemon.d.ts +2 -0
  71. package/dist/cli/use-nodemon.js +73 -0
  72. package/dist/cli/use-nodemon.js.map +1 -0
  73. package/dist/cli/use-open-browser.d.ts +1 -0
  74. package/dist/cli/use-open-browser.js +44 -0
  75. package/dist/cli/use-open-browser.js.map +1 -0
  76. package/dist/cli/use-open-tunnel-browser.d.ts +6 -0
  77. package/dist/cli/use-open-tunnel-browser.js +19 -0
  78. package/dist/cli/use-open-tunnel-browser.js.map +1 -0
  79. package/dist/cli/use-tunnel.d.ts +17 -0
  80. package/dist/cli/use-tunnel.js +131 -0
  81. package/dist/cli/use-tunnel.js.map +1 -0
  82. package/dist/cli/use-typescript-check.d.ts +9 -0
  83. package/dist/cli/use-typescript-check.js +94 -0
  84. package/dist/cli/use-typescript-check.js.map +1 -0
  85. package/dist/commands/build.d.ts +8 -0
  86. package/dist/commands/build.js +97 -0
  87. package/dist/commands/build.js.map +1 -0
  88. package/dist/commands/create.d.ts +9 -0
  89. package/dist/commands/create.js +30 -0
  90. package/dist/commands/create.js.map +1 -0
  91. package/dist/commands/dev.d.ts +13 -0
  92. package/dist/commands/dev.js +112 -0
  93. package/dist/commands/dev.js.map +1 -0
  94. package/dist/commands/start.d.ts +10 -0
  95. package/dist/commands/start.js +76 -0
  96. package/dist/commands/start.js.map +1 -0
  97. package/dist/commands/telemetry/disable.d.ts +5 -0
  98. package/dist/commands/telemetry/disable.js +12 -0
  99. package/dist/commands/telemetry/disable.js.map +1 -0
  100. package/dist/commands/telemetry/enable.d.ts +5 -0
  101. package/dist/commands/telemetry/enable.js +12 -0
  102. package/dist/commands/telemetry/enable.js.map +1 -0
  103. package/dist/commands/telemetry/status.d.ts +5 -0
  104. package/dist/commands/telemetry/status.js +12 -0
  105. package/dist/commands/telemetry/status.js.map +1 -0
  106. package/dist/server/admin.d.ts +79 -0
  107. package/dist/server/admin.js +239 -0
  108. package/dist/server/admin.js.map +1 -0
  109. package/dist/server/admin.test.d.ts +1 -0
  110. package/dist/server/admin.test.js +226 -0
  111. package/dist/server/admin.test.js.map +1 -0
  112. package/dist/server/analytics.d.ts +60 -0
  113. package/dist/server/analytics.js +168 -0
  114. package/dist/server/analytics.js.map +1 -0
  115. package/dist/server/analytics.test.d.ts +1 -0
  116. package/dist/server/analytics.test.js +179 -0
  117. package/dist/server/analytics.test.js.map +1 -0
  118. package/dist/server/asset-base-url-transform-plugin.d.ts +11 -0
  119. package/dist/server/asset-base-url-transform-plugin.js +48 -0
  120. package/dist/server/asset-base-url-transform-plugin.js.map +1 -0
  121. package/dist/server/asset-base-url-transform-plugin.test.d.ts +1 -0
  122. package/dist/server/asset-base-url-transform-plugin.test.js +134 -0
  123. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -0
  124. package/dist/server/auth.d.ts +20 -0
  125. package/dist/server/auth.js +28 -0
  126. package/dist/server/auth.js.map +1 -0
  127. package/dist/server/build-manifest.test.d.ts +1 -0
  128. package/dist/server/build-manifest.test.js +27 -0
  129. package/dist/server/build-manifest.test.js.map +1 -0
  130. package/dist/server/config/config.test.d.ts +1 -0
  131. package/dist/server/config/config.test.js +214 -0
  132. package/dist/server/config/config.test.js.map +1 -0
  133. package/dist/server/config/index.d.ts +3 -0
  134. package/dist/server/config/index.js +4 -0
  135. package/dist/server/config/index.js.map +1 -0
  136. package/dist/server/config/resolve.d.ts +73 -0
  137. package/dist/server/config/resolve.js +167 -0
  138. package/dist/server/config/resolve.js.map +1 -0
  139. package/dist/server/config/router.d.ts +23 -0
  140. package/dist/server/config/router.js +119 -0
  141. package/dist/server/config/router.js.map +1 -0
  142. package/dist/server/config/schema.d.ts +78 -0
  143. package/dist/server/config/schema.js +158 -0
  144. package/dist/server/config/schema.js.map +1 -0
  145. package/dist/server/content-helpers.d.ts +67 -0
  146. package/dist/server/content-helpers.js +79 -0
  147. package/dist/server/content-helpers.js.map +1 -0
  148. package/dist/server/content-helpers.test.d.ts +1 -0
  149. package/dist/server/content-helpers.test.js +70 -0
  150. package/dist/server/content-helpers.test.js.map +1 -0
  151. package/dist/server/express.d.ts +11 -0
  152. package/dist/server/express.js +129 -0
  153. package/dist/server/express.js.map +1 -0
  154. package/dist/server/express.test.d.ts +1 -0
  155. package/dist/server/express.test.js +464 -0
  156. package/dist/server/express.test.js.map +1 -0
  157. package/dist/server/file-ref.d.ts +28 -0
  158. package/dist/server/file-ref.js +27 -0
  159. package/dist/server/file-ref.js.map +1 -0
  160. package/dist/server/index.d.ts +17 -0
  161. package/dist/server/index.js +14 -0
  162. package/dist/server/index.js.map +1 -0
  163. package/dist/server/inferUtilityTypes.d.ts +64 -0
  164. package/dist/server/inferUtilityTypes.js +2 -0
  165. package/dist/server/inferUtilityTypes.js.map +1 -0
  166. package/dist/server/log-sink.d.ts +16 -0
  167. package/dist/server/log-sink.js +66 -0
  168. package/dist/server/log-sink.js.map +1 -0
  169. package/dist/server/metric.d.ts +12 -0
  170. package/dist/server/metric.js +13 -0
  171. package/dist/server/metric.js.map +1 -0
  172. package/dist/server/middleware.d.ts +137 -0
  173. package/dist/server/middleware.js +93 -0
  174. package/dist/server/middleware.js.map +1 -0
  175. package/dist/server/middleware.test-d.d.ts +1 -0
  176. package/dist/server/middleware.test-d.js +75 -0
  177. package/dist/server/middleware.test-d.js.map +1 -0
  178. package/dist/server/middleware.test.d.ts +1 -0
  179. package/dist/server/middleware.test.js +493 -0
  180. package/dist/server/middleware.test.js.map +1 -0
  181. package/dist/server/mock-seed.d.ts +62 -0
  182. package/dist/server/mock-seed.js +251 -0
  183. package/dist/server/mock-seed.js.map +1 -0
  184. package/dist/server/mock-seed.test.d.ts +1 -0
  185. package/dist/server/mock-seed.test.js +122 -0
  186. package/dist/server/mock-seed.test.js.map +1 -0
  187. package/dist/server/observability.d.ts +149 -0
  188. package/dist/server/observability.js +340 -0
  189. package/dist/server/observability.js.map +1 -0
  190. package/dist/server/observability.test.d.ts +1 -0
  191. package/dist/server/observability.test.js +251 -0
  192. package/dist/server/observability.test.js.map +1 -0
  193. package/dist/server/otel.d.ts +45 -0
  194. package/dist/server/otel.js +117 -0
  195. package/dist/server/otel.js.map +1 -0
  196. package/dist/server/otel.test.d.ts +1 -0
  197. package/dist/server/otel.test.js +122 -0
  198. package/dist/server/otel.test.js.map +1 -0
  199. package/dist/server/server.d.ts +422 -0
  200. package/dist/server/server.js +684 -0
  201. package/dist/server/server.js.map +1 -0
  202. package/dist/server/storage/index.d.ts +23 -0
  203. package/dist/server/storage/index.js +46 -0
  204. package/dist/server/storage/index.js.map +1 -0
  205. package/dist/server/storage/memory.d.ts +30 -0
  206. package/dist/server/storage/memory.js +98 -0
  207. package/dist/server/storage/memory.js.map +1 -0
  208. package/dist/server/storage/memory.test.d.ts +1 -0
  209. package/dist/server/storage/memory.test.js +81 -0
  210. package/dist/server/storage/memory.test.js.map +1 -0
  211. package/dist/server/storage/postgres.d.ts +65 -0
  212. package/dist/server/storage/postgres.js +242 -0
  213. package/dist/server/storage/postgres.js.map +1 -0
  214. package/dist/server/storage/postgres.test.d.ts +1 -0
  215. package/dist/server/storage/postgres.test.js +182 -0
  216. package/dist/server/storage/postgres.test.js.map +1 -0
  217. package/dist/server/storage/sqlite.d.ts +33 -0
  218. package/dist/server/storage/sqlite.js +250 -0
  219. package/dist/server/storage/sqlite.js.map +1 -0
  220. package/dist/server/storage/sqlite.test.d.ts +1 -0
  221. package/dist/server/storage/sqlite.test.js +133 -0
  222. package/dist/server/storage/sqlite.test.js.map +1 -0
  223. package/dist/server/storage/types.d.ts +119 -0
  224. package/dist/server/storage/types.js +11 -0
  225. package/dist/server/storage/types.js.map +1 -0
  226. package/dist/server/templateHelper.d.ts +16 -0
  227. package/dist/server/templateHelper.js +11 -0
  228. package/dist/server/templateHelper.js.map +1 -0
  229. package/dist/server/templates.generated.d.ts +4 -0
  230. package/dist/server/templates.generated.js +47 -0
  231. package/dist/server/templates.generated.js.map +1 -0
  232. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  233. package/dist/server/tunnel-proxy-router.js +110 -0
  234. package/dist/server/tunnel-proxy-router.js.map +1 -0
  235. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  236. package/dist/server/tunnel-proxy-router.test.js +229 -0
  237. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  238. package/dist/server/viewsDevServer.d.ts +14 -0
  239. package/dist/server/viewsDevServer.js +45 -0
  240. package/dist/server/viewsDevServer.js.map +1 -0
  241. package/dist/test/utils.d.ts +127 -0
  242. package/dist/test/utils.js +247 -0
  243. package/dist/test/utils.js.map +1 -0
  244. package/dist/test/view.test.d.ts +1 -0
  245. package/dist/test/view.test.js +568 -0
  246. package/dist/test/view.test.js.map +1 -0
  247. package/dist/version.d.ts +1 -0
  248. package/dist/version.js +3 -0
  249. package/dist/version.js.map +1 -0
  250. package/dist/web/bridges/apps-sdk/adaptor.d.ts +54 -0
  251. package/dist/web/bridges/apps-sdk/adaptor.js +164 -0
  252. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -0
  253. package/dist/web/bridges/apps-sdk/bridge.d.ts +11 -0
  254. package/dist/web/bridges/apps-sdk/bridge.js +47 -0
  255. package/dist/web/bridges/apps-sdk/bridge.js.map +1 -0
  256. package/dist/web/bridges/apps-sdk/index.d.ts +5 -0
  257. package/dist/web/bridges/apps-sdk/index.js +5 -0
  258. package/dist/web/bridges/apps-sdk/index.js.map +1 -0
  259. package/dist/web/bridges/apps-sdk/types.d.ts +147 -0
  260. package/dist/web/bridges/apps-sdk/types.js +10 -0
  261. package/dist/web/bridges/apps-sdk/types.js.map +1 -0
  262. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +13 -0
  263. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +18 -0
  264. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -0
  265. package/dist/web/bridges/get-adaptor.d.ts +9 -0
  266. package/dist/web/bridges/get-adaptor.js +15 -0
  267. package/dist/web/bridges/get-adaptor.js.map +1 -0
  268. package/dist/web/bridges/index.d.ts +5 -0
  269. package/dist/web/bridges/index.js +6 -0
  270. package/dist/web/bridges/index.js.map +1 -0
  271. package/dist/web/bridges/mcp-app/adaptor.d.ts +81 -0
  272. package/dist/web/bridges/mcp-app/adaptor.js +346 -0
  273. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -0
  274. package/dist/web/bridges/mcp-app/bridge.d.ts +28 -0
  275. package/dist/web/bridges/mcp-app/bridge.js +124 -0
  276. package/dist/web/bridges/mcp-app/bridge.js.map +1 -0
  277. package/dist/web/bridges/mcp-app/index.d.ts +4 -0
  278. package/dist/web/bridges/mcp-app/index.js +4 -0
  279. package/dist/web/bridges/mcp-app/index.js.map +1 -0
  280. package/dist/web/bridges/mcp-app/types.d.ts +8 -0
  281. package/dist/web/bridges/mcp-app/types.js +2 -0
  282. package/dist/web/bridges/mcp-app/types.js.map +1 -0
  283. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +19 -0
  284. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +19 -0
  285. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -0
  286. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.d.ts +1 -0
  287. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +26 -0
  288. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -0
  289. package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
  290. package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
  291. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
  292. package/dist/web/bridges/types.d.ts +243 -0
  293. package/dist/web/bridges/types.js +2 -0
  294. package/dist/web/bridges/types.js.map +1 -0
  295. package/dist/web/bridges/use-host-context.d.ts +7 -0
  296. package/dist/web/bridges/use-host-context.js +13 -0
  297. package/dist/web/bridges/use-host-context.js.map +1 -0
  298. package/dist/web/components/modal-provider.d.ts +4 -0
  299. package/dist/web/components/modal-provider.js +45 -0
  300. package/dist/web/components/modal-provider.js.map +1 -0
  301. package/dist/web/create-store.d.ts +29 -0
  302. package/dist/web/create-store.js +64 -0
  303. package/dist/web/create-store.js.map +1 -0
  304. package/dist/web/create-store.test.d.ts +1 -0
  305. package/dist/web/create-store.test.js +129 -0
  306. package/dist/web/create-store.test.js.map +1 -0
  307. package/dist/web/data-llm.d.ts +47 -0
  308. package/dist/web/data-llm.js +100 -0
  309. package/dist/web/data-llm.js.map +1 -0
  310. package/dist/web/data-llm.test.d.ts +1 -0
  311. package/dist/web/data-llm.test.js +142 -0
  312. package/dist/web/data-llm.test.js.map +1 -0
  313. package/dist/web/generate-helpers.d.ts +120 -0
  314. package/dist/web/generate-helpers.js +115 -0
  315. package/dist/web/generate-helpers.js.map +1 -0
  316. package/dist/web/generate-helpers.test-d.d.ts +1 -0
  317. package/dist/web/generate-helpers.test-d.js +211 -0
  318. package/dist/web/generate-helpers.test-d.js.map +1 -0
  319. package/dist/web/generate-helpers.test.d.ts +1 -0
  320. package/dist/web/generate-helpers.test.js +17 -0
  321. package/dist/web/generate-helpers.test.js.map +1 -0
  322. package/dist/web/helpers/state.d.ts +7 -0
  323. package/dist/web/helpers/state.js +45 -0
  324. package/dist/web/helpers/state.js.map +1 -0
  325. package/dist/web/helpers/state.test.d.ts +1 -0
  326. package/dist/web/helpers/state.test.js +53 -0
  327. package/dist/web/helpers/state.test.js.map +1 -0
  328. package/dist/web/hooks/index.d.ts +17 -0
  329. package/dist/web/hooks/index.js +18 -0
  330. package/dist/web/hooks/index.js.map +1 -0
  331. package/dist/web/hooks/test/utils.d.ts +20 -0
  332. package/dist/web/hooks/test/utils.js +75 -0
  333. package/dist/web/hooks/test/utils.js.map +1 -0
  334. package/dist/web/hooks/use-call-tool.d.ts +146 -0
  335. package/dist/web/hooks/use-call-tool.js +96 -0
  336. package/dist/web/hooks/use-call-tool.js.map +1 -0
  337. package/dist/web/hooks/use-call-tool.test-d.d.ts +1 -0
  338. package/dist/web/hooks/use-call-tool.test-d.js +104 -0
  339. package/dist/web/hooks/use-call-tool.test-d.js.map +1 -0
  340. package/dist/web/hooks/use-call-tool.test.d.ts +1 -0
  341. package/dist/web/hooks/use-call-tool.test.js +211 -0
  342. package/dist/web/hooks/use-call-tool.test.js.map +1 -0
  343. package/dist/web/hooks/use-display-mode.d.ts +24 -0
  344. package/dist/web/hooks/use-display-mode.js +29 -0
  345. package/dist/web/hooks/use-display-mode.js.map +1 -0
  346. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  347. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  348. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  349. package/dist/web/hooks/use-display-mode.test.d.ts +1 -0
  350. package/dist/web/hooks/use-display-mode.test.js +41 -0
  351. package/dist/web/hooks/use-display-mode.test.js.map +1 -0
  352. package/dist/web/hooks/use-download.d.ts +5 -0
  353. package/dist/web/hooks/use-download.js +8 -0
  354. package/dist/web/hooks/use-download.js.map +1 -0
  355. package/dist/web/hooks/use-download.test.d.ts +1 -0
  356. package/dist/web/hooks/use-download.test.js +95 -0
  357. package/dist/web/hooks/use-download.test.js.map +1 -0
  358. package/dist/web/hooks/use-files.d.ts +39 -0
  359. package/dist/web/hooks/use-files.js +42 -0
  360. package/dist/web/hooks/use-files.js.map +1 -0
  361. package/dist/web/hooks/use-files.test.d.ts +1 -0
  362. package/dist/web/hooks/use-files.test.js +54 -0
  363. package/dist/web/hooks/use-files.test.js.map +1 -0
  364. package/dist/web/hooks/use-intent.d.ts +30 -0
  365. package/dist/web/hooks/use-intent.js +34 -0
  366. package/dist/web/hooks/use-intent.js.map +1 -0
  367. package/dist/web/hooks/use-intent.test.d.ts +1 -0
  368. package/dist/web/hooks/use-intent.test.js +85 -0
  369. package/dist/web/hooks/use-intent.test.js.map +1 -0
  370. package/dist/web/hooks/use-layout.d.ts +24 -0
  371. package/dist/web/hooks/use-layout.js +25 -0
  372. package/dist/web/hooks/use-layout.js.map +1 -0
  373. package/dist/web/hooks/use-layout.test.d.ts +1 -0
  374. package/dist/web/hooks/use-layout.test.js +96 -0
  375. package/dist/web/hooks/use-layout.test.js.map +1 -0
  376. package/dist/web/hooks/use-notify.d.ts +29 -0
  377. package/dist/web/hooks/use-notify.js +33 -0
  378. package/dist/web/hooks/use-notify.js.map +1 -0
  379. package/dist/web/hooks/use-notify.test.d.ts +1 -0
  380. package/dist/web/hooks/use-notify.test.js +105 -0
  381. package/dist/web/hooks/use-notify.test.js.map +1 -0
  382. package/dist/web/hooks/use-open-external.d.ts +20 -0
  383. package/dist/web/hooks/use-open-external.js +24 -0
  384. package/dist/web/hooks/use-open-external.js.map +1 -0
  385. package/dist/web/hooks/use-open-external.test.d.ts +1 -0
  386. package/dist/web/hooks/use-open-external.test.js +65 -0
  387. package/dist/web/hooks/use-open-external.test.js.map +1 -0
  388. package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
  389. package/dist/web/hooks/use-register-view-tool.js +50 -0
  390. package/dist/web/hooks/use-register-view-tool.js.map +1 -0
  391. package/dist/web/hooks/use-request-close.d.ts +16 -0
  392. package/dist/web/hooks/use-request-close.js +21 -0
  393. package/dist/web/hooks/use-request-close.js.map +1 -0
  394. package/dist/web/hooks/use-request-close.test.d.ts +1 -0
  395. package/dist/web/hooks/use-request-close.test.js +52 -0
  396. package/dist/web/hooks/use-request-close.test.js.map +1 -0
  397. package/dist/web/hooks/use-request-modal.d.ts +24 -0
  398. package/dist/web/hooks/use-request-modal.js +31 -0
  399. package/dist/web/hooks/use-request-modal.js.map +1 -0
  400. package/dist/web/hooks/use-request-modal.test.d.ts +1 -0
  401. package/dist/web/hooks/use-request-modal.test.js +61 -0
  402. package/dist/web/hooks/use-request-modal.test.js.map +1 -0
  403. package/dist/web/hooks/use-request-size.d.ts +20 -0
  404. package/dist/web/hooks/use-request-size.js +24 -0
  405. package/dist/web/hooks/use-request-size.js.map +1 -0
  406. package/dist/web/hooks/use-request-size.test.d.ts +1 -0
  407. package/dist/web/hooks/use-request-size.test.js +65 -0
  408. package/dist/web/hooks/use-request-size.test.js.map +1 -0
  409. package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -0
  410. package/dist/web/hooks/use-send-follow-up-message.js +25 -0
  411. package/dist/web/hooks/use-send-follow-up-message.js.map +1 -0
  412. package/dist/web/hooks/use-set-open-in-app-url.d.ts +18 -0
  413. package/dist/web/hooks/use-set-open-in-app-url.js +25 -0
  414. package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -0
  415. package/dist/web/hooks/use-set-open-in-app-url.test.d.ts +1 -0
  416. package/dist/web/hooks/use-set-open-in-app-url.test.js +43 -0
  417. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -0
  418. package/dist/web/hooks/use-tool-info.d.ts +87 -0
  419. package/dist/web/hooks/use-tool-info.js +49 -0
  420. package/dist/web/hooks/use-tool-info.js.map +1 -0
  421. package/dist/web/hooks/use-tool-info.test-d.d.ts +1 -0
  422. package/dist/web/hooks/use-tool-info.test-d.js +91 -0
  423. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -0
  424. package/dist/web/hooks/use-tool-info.test.d.ts +1 -0
  425. package/dist/web/hooks/use-tool-info.test.js +130 -0
  426. package/dist/web/hooks/use-tool-info.test.js.map +1 -0
  427. package/dist/web/hooks/use-user.d.ts +20 -0
  428. package/dist/web/hooks/use-user.js +37 -0
  429. package/dist/web/hooks/use-user.js.map +1 -0
  430. package/dist/web/hooks/use-user.test.d.ts +1 -0
  431. package/dist/web/hooks/use-user.test.js +122 -0
  432. package/dist/web/hooks/use-user.test.js.map +1 -0
  433. package/dist/web/hooks/use-view-state.d.ts +25 -0
  434. package/dist/web/hooks/use-view-state.js +32 -0
  435. package/dist/web/hooks/use-view-state.js.map +1 -0
  436. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  437. package/dist/web/hooks/use-view-state.test.js +177 -0
  438. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  439. package/dist/web/index.d.ts +7 -0
  440. package/dist/web/index.js +8 -0
  441. package/dist/web/index.js.map +1 -0
  442. package/dist/web/mount-view.d.ts +20 -0
  443. package/dist/web/mount-view.js +46 -0
  444. package/dist/web/mount-view.js.map +1 -0
  445. package/dist/web/plugin/data-llm.test.d.ts +1 -0
  446. package/dist/web/plugin/data-llm.test.js +81 -0
  447. package/dist/web/plugin/data-llm.test.js.map +1 -0
  448. package/dist/web/plugin/plugin.d.ts +33 -0
  449. package/dist/web/plugin/plugin.js +189 -0
  450. package/dist/web/plugin/plugin.js.map +1 -0
  451. package/dist/web/plugin/scan-views.d.ts +16 -0
  452. package/dist/web/plugin/scan-views.js +88 -0
  453. package/dist/web/plugin/scan-views.js.map +1 -0
  454. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  455. package/dist/web/plugin/scan-views.test.js +99 -0
  456. package/dist/web/plugin/scan-views.test.js.map +1 -0
  457. package/dist/web/plugin/transform-data-llm.d.ts +12 -0
  458. package/dist/web/plugin/transform-data-llm.js +96 -0
  459. package/dist/web/plugin/transform-data-llm.js.map +1 -0
  460. package/dist/web/plugin/transform-data-llm.test.d.ts +1 -0
  461. package/dist/web/plugin/transform-data-llm.test.js +81 -0
  462. package/dist/web/plugin/transform-data-llm.test.js.map +1 -0
  463. package/dist/web/plugin/validate-view.d.ts +1 -0
  464. package/dist/web/plugin/validate-view.js +9 -0
  465. package/dist/web/plugin/validate-view.js.map +1 -0
  466. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  467. package/dist/web/plugin/validate-view.test.js +24 -0
  468. package/dist/web/plugin/validate-view.test.js.map +1 -0
  469. package/dist/web/proxy.d.ts +1 -0
  470. package/dist/web/proxy.js +52 -0
  471. package/dist/web/proxy.js.map +1 -0
  472. package/dist/web/types.d.ts +20 -0
  473. package/dist/web/types.js +2 -0
  474. package/dist/web/types.js.map +1 -0
  475. package/package.json +125 -0
  476. package/scripts/postinstall.mjs +45 -0
  477. package/tsconfig.base.json +36 -0
@@ -0,0 +1,254 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { defaultProvider, } from "./tunnel-providers/index.js";
3
+ /**
4
+ * Parse a single stdout line using the default (srv.us) provider. Kept as a
5
+ * standalone export for tests and callers that want the default parser.
6
+ */
7
+ export function parseStdoutLine(line) {
8
+ return defaultProvider.parseLine(line);
9
+ }
10
+ const CONNECT_TIMEOUT_MS = 60_000;
11
+ const STDERR_BUFFER_BYTES = 1024;
12
+ /** Backoff before respawning after an unexpected drop. */
13
+ const RECONNECT_BASE_DELAY_MS = 1_000;
14
+ const RECONNECT_MAX_DELAY_MS = 30_000;
15
+ function providerFromSpawn(spawnFn) {
16
+ return {
17
+ name: "custom",
18
+ spawn: spawnFn,
19
+ parseLine: defaultProvider.parseLine,
20
+ };
21
+ }
22
+ export class TunnelManager extends EventEmitter {
23
+ state = { status: "idle" };
24
+ child = null;
25
+ timeout = null;
26
+ reconnectTimer = null;
27
+ stderrBuffer = "";
28
+ connected = false;
29
+ /** True between start() and stop(): governs whether drops trigger reconnect. */
30
+ wantUp = false;
31
+ reconnectAttempts = 0;
32
+ ensured = false;
33
+ getPort;
34
+ provider;
35
+ constructor(opts) {
36
+ super();
37
+ this.getPort = opts.getPort;
38
+ this.provider =
39
+ opts.provider ??
40
+ (opts.spawn ? providerFromSpawn(opts.spawn) : defaultProvider);
41
+ // Multiple SSE subscribers (CLI, devtools, ad-hoc curl) can each register
42
+ // state + activity listeners; the default cap of 10 is easy to hit.
43
+ this.setMaxListeners(0);
44
+ }
45
+ getState() {
46
+ return this.state;
47
+ }
48
+ subscribe(listener) {
49
+ listener(this.state);
50
+ this.on("state", listener);
51
+ return () => {
52
+ this.off("state", listener);
53
+ };
54
+ }
55
+ start() {
56
+ if (this.state.status === "starting" ||
57
+ this.state.status === "connected" ||
58
+ this.state.status === "reconnecting") {
59
+ return;
60
+ }
61
+ this.wantUp = true;
62
+ this.reconnectAttempts = 0;
63
+ void this.spawnChild({ status: "starting", message: "Starting tunnel…" });
64
+ }
65
+ /**
66
+ * Spawn (or respawn) the tunnel child. `initialState` is what we broadcast
67
+ * while waiting for the URL — "starting" on first launch, "reconnecting" after
68
+ * a drop. Ensures provider prerequisites (e.g. SSH key) once, lazily.
69
+ */
70
+ async spawnChild(initialState) {
71
+ this.connected = false;
72
+ this.stderrBuffer = "";
73
+ this.setState(initialState);
74
+ if (!this.ensured && this.provider.ensure) {
75
+ try {
76
+ await this.provider.ensure();
77
+ this.ensured = true;
78
+ }
79
+ catch (err) {
80
+ if (!this.wantUp) {
81
+ return;
82
+ }
83
+ this.setState({
84
+ status: "error",
85
+ message: err instanceof Error ? err.message : String(err),
86
+ });
87
+ return;
88
+ }
89
+ }
90
+ // A stop() may have raced the await above.
91
+ if (!this.wantUp) {
92
+ return;
93
+ }
94
+ let child;
95
+ try {
96
+ child = this.provider.spawn(this.getPort());
97
+ }
98
+ catch (err) {
99
+ this.setState({
100
+ status: "error",
101
+ message: err instanceof Error ? err.message : String(err),
102
+ });
103
+ return;
104
+ }
105
+ this.child = child;
106
+ this.timeout = setTimeout(() => {
107
+ if (!this.connected) {
108
+ this.setState({
109
+ status: "error",
110
+ message: "Tunnel connection timed out after one minute",
111
+ });
112
+ // Detach before killing so the imminent `close` event is treated as
113
+ // stale and does not overwrite the timeout error message / reconnect.
114
+ this.child = null;
115
+ this.wantUp = false;
116
+ child.kill();
117
+ }
118
+ }, CONNECT_TIMEOUT_MS);
119
+ child.stdout?.on("data", (data) => {
120
+ this.handleStdout(data);
121
+ });
122
+ child.stderr?.on("data", (data) => {
123
+ this.handleStderr(data);
124
+ });
125
+ child.on("error", (err) => {
126
+ // Stale event from a child we've already replaced via stop()+start().
127
+ if (child !== this.child) {
128
+ return;
129
+ }
130
+ this.clearConnectTimeout();
131
+ this.child = null;
132
+ if (this.wantUp) {
133
+ this.scheduleReconnect(err.message);
134
+ }
135
+ else {
136
+ this.setState({ status: "error", message: err.message });
137
+ }
138
+ });
139
+ child.on("close", (code) => {
140
+ // Stale event from a child we've already replaced via stop()+start().
141
+ if (child !== this.child) {
142
+ return;
143
+ }
144
+ this.clearConnectTimeout();
145
+ this.child = null;
146
+ // Wanted up but the process exited → unexpected drop, reconnect.
147
+ if (this.wantUp) {
148
+ const detail = this.stderrBuffer.trim() ||
149
+ (code !== null ? `tunnel exited with code ${code}` : "tunnel closed");
150
+ this.scheduleReconnect(detail);
151
+ return;
152
+ }
153
+ if (code !== 0 && code !== null) {
154
+ const detail = this.stderrBuffer.trim() || `exited with code ${code}`;
155
+ this.setState({ status: "error", message: detail });
156
+ }
157
+ else {
158
+ this.setState({ status: "idle" });
159
+ }
160
+ });
161
+ }
162
+ /**
163
+ * After an unexpected drop, broadcast "reconnecting" and respawn after an
164
+ * exponential backoff (capped). Reset attempts once a connection succeeds.
165
+ */
166
+ scheduleReconnect(reason) {
167
+ if (!this.wantUp) {
168
+ this.setState({ status: "idle" });
169
+ return;
170
+ }
171
+ const delay = Math.min(RECONNECT_BASE_DELAY_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_DELAY_MS);
172
+ this.reconnectAttempts += 1;
173
+ this.setState({
174
+ status: "reconnecting",
175
+ message: `Tunnel dropped (${reason}); reconnecting…`,
176
+ });
177
+ this.reconnectTimer = setTimeout(() => {
178
+ this.reconnectTimer = null;
179
+ if (!this.wantUp) {
180
+ return;
181
+ }
182
+ void this.spawnChild({
183
+ status: "reconnecting",
184
+ message: "Reconnecting tunnel…",
185
+ });
186
+ }, delay);
187
+ }
188
+ stop() {
189
+ this.wantUp = false;
190
+ this.clearConnectTimeout();
191
+ this.clearReconnectTimer();
192
+ if (this.child) {
193
+ this.child.kill();
194
+ this.child = null;
195
+ }
196
+ this.setState({ status: "idle" });
197
+ }
198
+ handleStdout(data) {
199
+ const lines = data.toString().split("\n");
200
+ for (const raw of lines) {
201
+ const parsed = this.provider.parseLine(raw);
202
+ if (!parsed) {
203
+ continue;
204
+ }
205
+ if (parsed.kind === "connected") {
206
+ this.connected = true;
207
+ this.reconnectAttempts = 0;
208
+ this.clearConnectTimeout();
209
+ this.setState({ status: "connected", url: parsed.url });
210
+ }
211
+ else if (this.connected) {
212
+ this.emitActivity(parsed.message, "log");
213
+ }
214
+ else {
215
+ this.setState({ status: "starting", message: parsed.message });
216
+ }
217
+ }
218
+ }
219
+ handleStderr(data) {
220
+ const text = data.toString().trim();
221
+ if (!text) {
222
+ return;
223
+ }
224
+ this.stderrBuffer = (this.stderrBuffer + text).slice(-STDERR_BUFFER_BYTES);
225
+ for (const line of text.split("\n").filter(Boolean)) {
226
+ this.emitActivity(line, "error");
227
+ }
228
+ }
229
+ setState(next) {
230
+ this.state = next;
231
+ this.emit("state", next);
232
+ }
233
+ emitActivity(text, level) {
234
+ const activity = {
235
+ time: new Date().toISOString(),
236
+ text,
237
+ level,
238
+ };
239
+ this.emit("activity", activity);
240
+ }
241
+ clearConnectTimeout() {
242
+ if (this.timeout) {
243
+ clearTimeout(this.timeout);
244
+ this.timeout = null;
245
+ }
246
+ }
247
+ clearReconnectTimer() {
248
+ if (this.reconnectTimer) {
249
+ clearTimeout(this.reconnectTimer);
250
+ this.reconnectTimer = null;
251
+ }
252
+ }
253
+ }
254
+ //# 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 {\n defaultProvider,\n type ParsedStdoutEvent,\n type TunnelChildProcess,\n type TunnelProvider,\n} from \"./tunnel-providers/index.js\";\n\nexport type {\n ParsedStdoutEvent,\n TunnelChildProcess,\n TunnelProvider,\n} from \"./tunnel-providers/index.js\";\n\nexport type TunnelState =\n | { status: \"idle\" }\n | { status: \"starting\"; message: string }\n | { status: \"connected\"; url: string }\n | { status: \"reconnecting\"; message: string }\n | { status: \"error\"; message: string };\n\n/**\n * Parse a single stdout line using the default (srv.us) provider. Kept as a\n * standalone export for tests and callers that want the default parser.\n */\nexport function parseStdoutLine(line: string): ParsedStdoutEvent | null {\n return defaultProvider.parseLine(line);\n}\n\nconst CONNECT_TIMEOUT_MS = 60_000;\nconst STDERR_BUFFER_BYTES = 1024;\n/** Backoff before respawning after an unexpected drop. */\nconst RECONNECT_BASE_DELAY_MS = 1_000;\nconst RECONNECT_MAX_DELAY_MS = 30_000;\n\nexport type TunnelActivity = {\n time: string;\n text: string;\n level: \"log\" | \"error\";\n};\n\n/**\n * Legacy spawn injection. A bare `(port) => child` function; when provided it is\n * adapted into a provider that uses the default (srv.us) line parser. New code\n * should pass a full `provider` instead.\n */\nexport type SpawnFn = (port: number) => TunnelChildProcess;\n\nfunction providerFromSpawn(spawnFn: SpawnFn): TunnelProvider {\n return {\n name: \"custom\",\n spawn: spawnFn,\n parseLine: defaultProvider.parseLine,\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 reconnectTimer: NodeJS.Timeout | null = null;\n private stderrBuffer = \"\";\n private connected = false;\n /** True between start() and stop(): governs whether drops trigger reconnect. */\n private wantUp = false;\n private reconnectAttempts = 0;\n private ensured = false;\n private readonly getPort: () => number;\n private readonly provider: TunnelProvider;\n\n constructor(opts: {\n getPort: () => number;\n provider?: TunnelProvider;\n /** @deprecated pass `provider` instead. */\n spawn?: SpawnFn;\n }) {\n super();\n this.getPort = opts.getPort;\n this.provider =\n opts.provider ??\n (opts.spawn ? providerFromSpawn(opts.spawn) : defaultProvider);\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 (\n this.state.status === \"starting\" ||\n this.state.status === \"connected\" ||\n this.state.status === \"reconnecting\"\n ) {\n return;\n }\n this.wantUp = true;\n this.reconnectAttempts = 0;\n void this.spawnChild({ status: \"starting\", message: \"Starting tunnel…\" });\n }\n\n /**\n * Spawn (or respawn) the tunnel child. `initialState` is what we broadcast\n * while waiting for the URL — \"starting\" on first launch, \"reconnecting\" after\n * a drop. Ensures provider prerequisites (e.g. SSH key) once, lazily.\n */\n private async spawnChild(initialState: TunnelState): Promise<void> {\n this.connected = false;\n this.stderrBuffer = \"\";\n this.setState(initialState);\n\n if (!this.ensured && this.provider.ensure) {\n try {\n await this.provider.ensure();\n this.ensured = true;\n } catch (err) {\n if (!this.wantUp) {\n return;\n }\n this.setState({\n status: \"error\",\n message: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n }\n // A stop() may have raced the await above.\n if (!this.wantUp) {\n return;\n }\n\n let child: TunnelChildProcess;\n try {\n child = this.provider.spawn(this.getPort());\n } catch (err) {\n this.setState({\n status: \"error\",\n message: err instanceof Error ? err.message : String(err),\n });\n return;\n }\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 / reconnect.\n this.child = null;\n this.wantUp = false;\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.child = null;\n if (this.wantUp) {\n this.scheduleReconnect(err.message);\n } else {\n this.setState({ status: \"error\", message: err.message });\n }\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 this.child = null;\n\n // Wanted up but the process exited → unexpected drop, reconnect.\n if (this.wantUp) {\n const detail =\n this.stderrBuffer.trim() ||\n (code !== null ? `tunnel exited with code ${code}` : \"tunnel closed\");\n this.scheduleReconnect(detail);\n return;\n }\n\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 });\n }\n\n /**\n * After an unexpected drop, broadcast \"reconnecting\" and respawn after an\n * exponential backoff (capped). Reset attempts once a connection succeeds.\n */\n private scheduleReconnect(reason: string): void {\n if (!this.wantUp) {\n this.setState({ status: \"idle\" });\n return;\n }\n const delay = Math.min(\n RECONNECT_BASE_DELAY_MS * 2 ** this.reconnectAttempts,\n RECONNECT_MAX_DELAY_MS,\n );\n this.reconnectAttempts += 1;\n this.setState({\n status: \"reconnecting\",\n message: `Tunnel dropped (${reason}); reconnecting…`,\n });\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (!this.wantUp) {\n return;\n }\n void this.spawnChild({\n status: \"reconnecting\",\n message: \"Reconnecting tunnel…\",\n });\n }, delay);\n }\n\n stop(): void {\n this.wantUp = false;\n this.clearConnectTimeout();\n this.clearReconnectTimer();\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 = this.provider.parseLine(raw);\n if (!parsed) {\n continue;\n }\n if (parsed.kind === \"connected\") {\n this.connected = true;\n this.reconnectAttempts = 0;\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 private clearReconnectTimer(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,255 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { Readable } from "node:stream";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { parseStdoutLine, TunnelManager } from "./tunnel.js";
5
+ describe("parseStdoutLine (delegates to the default srv.us provider)", () => {
6
+ it("returns a connected event when a srv.us URL is seen", () => {
7
+ const result = parseStdoutLine("https://qp556ma755ktlag5b2xyt334ae.srv.us/");
8
+ expect(result).toEqual({
9
+ kind: "connected",
10
+ url: "https://qp556ma755ktlag5b2xyt334ae.srv.us",
11
+ });
12
+ });
13
+ it("returns a starting event for any other non-empty line", () => {
14
+ expect(parseStdoutLine("Warning: Permanently added 'srv.us'")).toEqual({
15
+ kind: "starting",
16
+ message: "Warning: Permanently added 'srv.us'",
17
+ });
18
+ });
19
+ it("returns null for empty lines", () => {
20
+ expect(parseStdoutLine("")).toBeNull();
21
+ expect(parseStdoutLine(" ")).toBeNull();
22
+ });
23
+ });
24
+ function makeFakeChild() {
25
+ const child = new EventEmitter();
26
+ child.stdout = new Readable({ read() { } });
27
+ child.stderr = new Readable({ read() { } });
28
+ child.kill = vi.fn(() => true);
29
+ return child;
30
+ }
31
+ describe("TunnelManager", () => {
32
+ beforeEach(() => {
33
+ vi.useFakeTimers();
34
+ });
35
+ afterEach(() => {
36
+ vi.useRealTimers();
37
+ });
38
+ it("starts idle", () => {
39
+ const manager = new TunnelManager({
40
+ getPort: () => 3000,
41
+ spawn: () => makeFakeChild(),
42
+ });
43
+ expect(manager.getState()).toEqual({ status: "idle" });
44
+ });
45
+ it("transitions idle -> starting -> connected as stdout reports progress", () => {
46
+ const child = makeFakeChild();
47
+ const manager = new TunnelManager({
48
+ getPort: () => 3000,
49
+ spawn: () => child,
50
+ });
51
+ const states = [];
52
+ manager.on("state", (s) => states.push(s.status));
53
+ manager.start();
54
+ child.stdout.emit("data", Buffer.from("Opening tunnel...\n"));
55
+ child.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
56
+ expect(states).toEqual(["starting", "starting", "connected"]);
57
+ expect(manager.getState()).toEqual({
58
+ status: "connected",
59
+ url: "https://abc123.srv.us",
60
+ });
61
+ });
62
+ it("emits activity events for stdout lines after connect", () => {
63
+ const child = makeFakeChild();
64
+ const manager = new TunnelManager({
65
+ getPort: () => 3000,
66
+ spawn: () => child,
67
+ });
68
+ const activity = [];
69
+ manager.on("activity", (a) => activity.push({ text: a.text, level: a.level }));
70
+ manager.start();
71
+ child.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
72
+ child.stdout.emit("data", Buffer.from("GET /widgets/foo\n"));
73
+ child.stderr.emit("data", Buffer.from("connection wobble\n"));
74
+ expect(activity).toEqual([
75
+ { text: "GET /widgets/foo", level: "log" },
76
+ { text: "connection wobble", level: "error" },
77
+ ]);
78
+ });
79
+ it("calling start twice does not spawn twice", () => {
80
+ const spawn = vi.fn(() => makeFakeChild());
81
+ const manager = new TunnelManager({ getPort: () => 3000, spawn });
82
+ manager.start();
83
+ manager.start();
84
+ expect(spawn).toHaveBeenCalledTimes(1);
85
+ });
86
+ it("transitions to error when the connection times out", () => {
87
+ const child = makeFakeChild();
88
+ const manager = new TunnelManager({
89
+ getPort: () => 3000,
90
+ spawn: () => child,
91
+ });
92
+ manager.start();
93
+ vi.advanceTimersByTime(60_000);
94
+ expect(manager.getState().status).toBe("error");
95
+ expect(child.kill).toHaveBeenCalled();
96
+ });
97
+ it("preserves the timeout error message when the killed child later emits close", () => {
98
+ const child = makeFakeChild();
99
+ const manager = new TunnelManager({
100
+ getPort: () => 3000,
101
+ spawn: () => child,
102
+ });
103
+ manager.start();
104
+ vi.advanceTimersByTime(60_000);
105
+ // The killed child emits a non-zero close after the timeout fired.
106
+ child.emit("close", 1);
107
+ expect(manager.getState()).toEqual({
108
+ status: "error",
109
+ message: "Tunnel connection timed out after one minute",
110
+ });
111
+ });
112
+ it("stop() kills the subprocess and goes idle", () => {
113
+ const child = makeFakeChild();
114
+ const manager = new TunnelManager({
115
+ getPort: () => 3000,
116
+ spawn: () => child,
117
+ });
118
+ manager.start();
119
+ manager.stop();
120
+ expect(child.kill).toHaveBeenCalled();
121
+ expect(manager.getState()).toEqual({ status: "idle" });
122
+ });
123
+ it("subscribers get the current state on subscribe", () => {
124
+ const child = makeFakeChild();
125
+ const manager = new TunnelManager({
126
+ getPort: () => 3000,
127
+ spawn: () => child,
128
+ });
129
+ manager.start();
130
+ child.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
131
+ let received;
132
+ const unsubscribe = manager.subscribe((s) => {
133
+ received = s;
134
+ });
135
+ expect(received).toEqual({
136
+ status: "connected",
137
+ url: "https://abc123.srv.us",
138
+ });
139
+ unsubscribe();
140
+ });
141
+ it("ignores deferred close from a child that was replaced via stop()+start()", () => {
142
+ const childA = makeFakeChild();
143
+ const childB = makeFakeChild();
144
+ let spawnCount = 0;
145
+ const manager = new TunnelManager({
146
+ getPort: () => 3000,
147
+ spawn: () => (spawnCount++ === 0 ? childA : childB),
148
+ });
149
+ manager.start();
150
+ manager.stop();
151
+ manager.start();
152
+ childB.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
153
+ expect(manager.getState()).toEqual({
154
+ status: "connected",
155
+ url: "https://abc123.srv.us",
156
+ });
157
+ // Stale close from childA arrives only now — must not clobber state.
158
+ childA.emit("close", null);
159
+ expect(manager.getState()).toEqual({
160
+ status: "connected",
161
+ url: "https://abc123.srv.us",
162
+ });
163
+ });
164
+ it("ignores deferred error from a child that was replaced via stop()+start()", () => {
165
+ const childA = makeFakeChild();
166
+ const childB = makeFakeChild();
167
+ let spawnCount = 0;
168
+ const manager = new TunnelManager({
169
+ getPort: () => 3000,
170
+ spawn: () => (spawnCount++ === 0 ? childA : childB),
171
+ });
172
+ manager.start();
173
+ manager.stop();
174
+ manager.start();
175
+ childB.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
176
+ childA.emit("error", new Error("late spawn failure"));
177
+ expect(manager.getState()).toEqual({
178
+ status: "connected",
179
+ url: "https://abc123.srv.us",
180
+ });
181
+ });
182
+ it("auto-reconnects when the ssh child exits unexpectedly while up", () => {
183
+ const childA = makeFakeChild();
184
+ const childB = makeFakeChild();
185
+ let spawnCount = 0;
186
+ const manager = new TunnelManager({
187
+ getPort: () => 3000,
188
+ spawn: () => (spawnCount++ === 0 ? childA : childB),
189
+ });
190
+ const states = [];
191
+ manager.on("state", (s) => states.push(s));
192
+ manager.start();
193
+ childA.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
194
+ expect(manager.getState().status).toBe("connected");
195
+ expect(spawnCount).toBe(1);
196
+ // ssh drops unexpectedly (e.g. killed / network blip).
197
+ childA.emit("close", null);
198
+ expect(manager.getState().status).toBe("reconnecting");
199
+ // Backoff elapses → respawn.
200
+ vi.advanceTimersByTime(1_000);
201
+ expect(spawnCount).toBe(2);
202
+ // New child connects → back to connected.
203
+ childB.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
204
+ expect(manager.getState()).toEqual({
205
+ status: "connected",
206
+ url: "https://abc123.srv.us",
207
+ });
208
+ expect(states.map((s) => s.status)).toContain("reconnecting");
209
+ });
210
+ it("stop() during reconnect halts the backoff and does not respawn", () => {
211
+ const children = [makeFakeChild(), makeFakeChild()];
212
+ let spawnCount = 0;
213
+ const manager = new TunnelManager({
214
+ getPort: () => 3000,
215
+ spawn: () => children[spawnCount++] ?? makeFakeChild(),
216
+ });
217
+ const first = children[0];
218
+ manager.start();
219
+ first.stdout.emit("data", Buffer.from("https://abc123.srv.us/\n"));
220
+ first.emit("close", null);
221
+ expect(manager.getState().status).toBe("reconnecting");
222
+ manager.stop();
223
+ expect(manager.getState()).toEqual({ status: "idle" });
224
+ // Even after the backoff window, no respawn happened.
225
+ vi.advanceTimersByTime(60_000);
226
+ expect(spawnCount).toBe(1);
227
+ });
228
+ it("uses the provider's parseLine and ensure hook", async () => {
229
+ const child = makeFakeChild();
230
+ const ensure = vi.fn();
231
+ const manager = new TunnelManager({
232
+ getPort: () => 3000,
233
+ provider: {
234
+ name: "fake",
235
+ ensure,
236
+ spawn: () => child,
237
+ parseLine: (line) => {
238
+ const m = line.match(/READY (\S+)/);
239
+ return m?.[1] ? { kind: "connected", url: m[1] } : null;
240
+ },
241
+ },
242
+ });
243
+ manager.start();
244
+ // ensure() is awaited inside spawnChild; flush microtasks.
245
+ await Promise.resolve();
246
+ await Promise.resolve();
247
+ expect(ensure).toHaveBeenCalledTimes(1);
248
+ child.stdout.emit("data", Buffer.from("READY https://x.example\n"));
249
+ expect(manager.getState()).toEqual({
250
+ status: "connected",
251
+ url: "https://x.example",
252
+ });
253
+ });
254
+ });
255
+ //# sourceMappingURL=tunnel.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.test.js","sourceRoot":"","sources":["../../src/cli/tunnel.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"node:events\";\nimport { Readable } from \"node:stream\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { parseStdoutLine, TunnelManager, type TunnelState } from \"./tunnel.js\";\n\ndescribe(\"parseStdoutLine (delegates to the default srv.us provider)\", () => {\n it(\"returns a connected event when a srv.us URL is seen\", () => {\n const result = parseStdoutLine(\n \"https://qp556ma755ktlag5b2xyt334ae.srv.us/\",\n );\n expect(result).toEqual({\n kind: \"connected\",\n url: \"https://qp556ma755ktlag5b2xyt334ae.srv.us\",\n });\n });\n\n it(\"returns a starting event for any other non-empty line\", () => {\n expect(parseStdoutLine(\"Warning: Permanently added 'srv.us'\")).toEqual({\n kind: \"starting\",\n message: \"Warning: Permanently added 'srv.us'\",\n });\n });\n\n it(\"returns null for empty lines\", () => {\n expect(parseStdoutLine(\"\")).toBeNull();\n expect(parseStdoutLine(\" \")).toBeNull();\n });\n});\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\ndescribe(\"TunnelManager\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"starts idle\", () => {\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => makeFakeChild(),\n });\n expect(manager.getState()).toEqual({ status: \"idle\" });\n });\n\n it(\"transitions idle -> starting -> connected as stdout reports progress\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n const states: string[] = [];\n manager.on(\"state\", (s: TunnelState) => states.push(s.status));\n\n manager.start();\n child.stdout.emit(\"data\", Buffer.from(\"Opening tunnel...\\n\"));\n child.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n\n expect(states).toEqual([\"starting\", \"starting\", \"connected\"]);\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n });\n\n it(\"emits activity events for stdout lines after connect\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n const activity: Array<{ text: string; level: string }> = [];\n manager.on(\"activity\", (a: { text: string; level: string }) =>\n activity.push({ text: a.text, level: a.level }),\n );\n\n manager.start();\n child.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n child.stdout.emit(\"data\", Buffer.from(\"GET /widgets/foo\\n\"));\n child.stderr.emit(\"data\", Buffer.from(\"connection wobble\\n\"));\n\n expect(activity).toEqual([\n { text: \"GET /widgets/foo\", level: \"log\" },\n { text: \"connection wobble\", level: \"error\" },\n ]);\n });\n\n it(\"calling start twice does not spawn twice\", () => {\n const spawn = vi.fn(() => makeFakeChild());\n const manager = new TunnelManager({ getPort: () => 3000, spawn });\n manager.start();\n manager.start();\n expect(spawn).toHaveBeenCalledTimes(1);\n });\n\n it(\"transitions to error when the connection times out\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n\n vi.advanceTimersByTime(60_000);\n\n expect(manager.getState().status).toBe(\"error\");\n expect(child.kill).toHaveBeenCalled();\n });\n\n it(\"preserves the timeout error message when the killed child later emits close\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n\n vi.advanceTimersByTime(60_000);\n // The killed child emits a non-zero close after the timeout fired.\n child.emit(\"close\", 1);\n\n expect(manager.getState()).toEqual({\n status: \"error\",\n message: \"Tunnel connection timed out after one minute\",\n });\n });\n\n it(\"stop() kills the subprocess and goes idle\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n manager.stop();\n expect(child.kill).toHaveBeenCalled();\n expect(manager.getState()).toEqual({ status: \"idle\" });\n });\n\n it(\"subscribers get the current state on subscribe\", () => {\n const child = makeFakeChild();\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => child,\n });\n manager.start();\n child.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n\n let received: TunnelState | undefined;\n const unsubscribe = manager.subscribe((s) => {\n received = s;\n });\n expect(received).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n unsubscribe();\n });\n\n it(\"ignores deferred close from a child that was replaced via stop()+start()\", () => {\n const childA = makeFakeChild();\n const childB = makeFakeChild();\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => (spawnCount++ === 0 ? childA : childB),\n });\n\n manager.start();\n manager.stop();\n manager.start();\n childB.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n\n // Stale close from childA arrives only now — must not clobber state.\n childA.emit(\"close\", null);\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n });\n\n it(\"ignores deferred error from a child that was replaced via stop()+start()\", () => {\n const childA = makeFakeChild();\n const childB = makeFakeChild();\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => (spawnCount++ === 0 ? childA : childB),\n });\n\n manager.start();\n manager.stop();\n manager.start();\n childB.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n\n childA.emit(\"error\", new Error(\"late spawn failure\"));\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n });\n\n it(\"auto-reconnects when the ssh child exits unexpectedly while up\", () => {\n const childA = makeFakeChild();\n const childB = makeFakeChild();\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => (spawnCount++ === 0 ? childA : childB),\n });\n const states: TunnelState[] = [];\n manager.on(\"state\", (s: TunnelState) => states.push(s));\n\n manager.start();\n childA.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n expect(manager.getState().status).toBe(\"connected\");\n expect(spawnCount).toBe(1);\n\n // ssh drops unexpectedly (e.g. killed / network blip).\n childA.emit(\"close\", null);\n expect(manager.getState().status).toBe(\"reconnecting\");\n\n // Backoff elapses → respawn.\n vi.advanceTimersByTime(1_000);\n expect(spawnCount).toBe(2);\n\n // New child connects → back to connected.\n childB.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://abc123.srv.us\",\n });\n expect(states.map((s) => s.status)).toContain(\"reconnecting\");\n });\n\n it(\"stop() during reconnect halts the backoff and does not respawn\", () => {\n const children = [makeFakeChild(), makeFakeChild()];\n let spawnCount = 0;\n const manager = new TunnelManager({\n getPort: () => 3000,\n spawn: () => children[spawnCount++] ?? makeFakeChild(),\n });\n\n const first = children[0] as FakeChild;\n manager.start();\n first.stdout.emit(\"data\", Buffer.from(\"https://abc123.srv.us/\\n\"));\n first.emit(\"close\", null);\n expect(manager.getState().status).toBe(\"reconnecting\");\n\n manager.stop();\n expect(manager.getState()).toEqual({ status: \"idle\" });\n\n // Even after the backoff window, no respawn happened.\n vi.advanceTimersByTime(60_000);\n expect(spawnCount).toBe(1);\n });\n\n it(\"uses the provider's parseLine and ensure hook\", async () => {\n const child = makeFakeChild();\n const ensure = vi.fn();\n const manager = new TunnelManager({\n getPort: () => 3000,\n provider: {\n name: \"fake\",\n ensure,\n spawn: () => child,\n parseLine: (line) => {\n const m = line.match(/READY (\\S+)/);\n return m?.[1] ? { kind: \"connected\", url: m[1] } : null;\n },\n },\n });\n\n manager.start();\n // ensure() is awaited inside spawnChild; flush microtasks.\n await Promise.resolve();\n await Promise.resolve();\n expect(ensure).toHaveBeenCalledTimes(1);\n\n child.stdout.emit(\"data\", Buffer.from(\"READY https://x.example\\n\"));\n expect(manager.getState()).toEqual({\n status: \"connected\",\n url: \"https://x.example\",\n });\n });\n});\n"]}
@@ -0,0 +1,5 @@
1
+ export type Message = {
2
+ id: string;
3
+ text: string;
4
+ type: "log" | "restart" | "error";
5
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Message = {\n id: string;\n text: string;\n type: \"log\" | \"restart\" | \"error\";\n};\n"]}
@@ -0,0 +1,11 @@
1
+ export interface CommandStep {
2
+ label: string;
3
+ command?: string;
4
+ run?: () => void | Promise<void>;
5
+ }
6
+ export declare const useExecuteSteps: (steps: CommandStep[]) => {
7
+ currentStep: number;
8
+ status: "error" | "running" | "success";
9
+ error: string | null;
10
+ execute: () => Promise<void>;
11
+ };
@@ -0,0 +1,36 @@
1
+ import { useCallback, useState } from "react";
2
+ import { runCommand } from "./run-command.js";
3
+ export const useExecuteSteps = (steps) => {
4
+ const [currentStep, setCurrentStep] = useState(0);
5
+ const [status, setStatus] = useState("running");
6
+ const [error, setError] = useState(null);
7
+ const execute = useCallback(async () => {
8
+ try {
9
+ for (let i = 0; i < steps.length; i++) {
10
+ const step = steps[i];
11
+ if (step) {
12
+ setCurrentStep(i);
13
+ if (step.run) {
14
+ await step.run();
15
+ }
16
+ if (step.command) {
17
+ await runCommand(step.command);
18
+ }
19
+ }
20
+ }
21
+ setStatus("success");
22
+ setImmediate(() => {
23
+ process.exit(0);
24
+ });
25
+ }
26
+ catch (err) {
27
+ setStatus("error");
28
+ setError(err instanceof Error ? err.message : String(err));
29
+ setImmediate(() => {
30
+ process.exit(1);
31
+ });
32
+ }
33
+ }, [steps]);
34
+ return { currentStep, status, error, execute };
35
+ };
36
+ //# sourceMappingURL=use-execute-steps.js.map