skybridge 0.0.0-dev.cde3cc2 → 0.0.0-dev.ce7f0f6

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 (228) hide show
  1. package/README.md +132 -119
  2. package/dist/cli/detect-port.d.ts +18 -0
  3. package/dist/cli/detect-port.js +61 -0
  4. package/dist/cli/detect-port.js.map +1 -0
  5. package/dist/cli/header.js +1 -1
  6. package/dist/cli/header.js.map +1 -1
  7. package/dist/cli/tunnel-control-server.d.ts +9 -0
  8. package/dist/cli/tunnel-control-server.js +31 -0
  9. package/dist/cli/tunnel-control-server.js.map +1 -0
  10. package/dist/cli/tunnel-control-server.test.js +39 -0
  11. package/dist/cli/tunnel-control-server.test.js.map +1 -0
  12. package/dist/cli/tunnel-handler.d.ts +3 -0
  13. package/dist/cli/tunnel-handler.js +48 -0
  14. package/dist/cli/tunnel-handler.js.map +1 -0
  15. package/dist/cli/tunnel-handler.test.js +105 -0
  16. package/dist/cli/tunnel-handler.test.js.map +1 -0
  17. package/dist/cli/tunnel.d.ts +57 -0
  18. package/dist/cli/tunnel.js +154 -0
  19. package/dist/cli/tunnel.js.map +1 -0
  20. package/dist/cli/tunnel.test.d.ts +1 -0
  21. package/dist/cli/tunnel.test.js +190 -0
  22. package/dist/cli/tunnel.test.js.map +1 -0
  23. package/dist/cli/types.d.ts +5 -0
  24. package/dist/cli/types.js +2 -0
  25. package/dist/cli/types.js.map +1 -0
  26. package/dist/cli/use-messages.d.ts +3 -0
  27. package/dist/cli/use-messages.js +11 -0
  28. package/dist/cli/use-messages.js.map +1 -0
  29. package/dist/cli/use-nodemon.d.ts +2 -6
  30. package/dist/cli/use-nodemon.js +20 -17
  31. package/dist/cli/use-nodemon.js.map +1 -1
  32. package/dist/cli/use-tunnel.d.ts +14 -0
  33. package/dist/cli/use-tunnel.js +131 -0
  34. package/dist/cli/use-tunnel.js.map +1 -0
  35. package/dist/cli/use-typescript-check.d.ts +1 -0
  36. package/dist/cli/use-typescript-check.js +41 -6
  37. package/dist/cli/use-typescript-check.js.map +1 -1
  38. package/dist/commands/build.js +63 -7
  39. package/dist/commands/build.js.map +1 -1
  40. package/dist/commands/dev.d.ts +3 -1
  41. package/dist/commands/dev.js +50 -8
  42. package/dist/commands/dev.js.map +1 -1
  43. package/dist/commands/start.d.ts +3 -1
  44. package/dist/commands/start.js +30 -9
  45. package/dist/commands/start.js.map +1 -1
  46. package/dist/server/asset-base-url-transform-plugin.d.ts +5 -6
  47. package/dist/server/asset-base-url-transform-plugin.js +9 -10
  48. package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
  49. package/dist/server/asset-base-url-transform-plugin.test.js +41 -13
  50. package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
  51. package/dist/server/content-helpers.d.ts +27 -0
  52. package/dist/server/content-helpers.js +46 -0
  53. package/dist/server/content-helpers.js.map +1 -0
  54. package/dist/server/content-helpers.test.d.ts +1 -0
  55. package/dist/server/content-helpers.test.js +70 -0
  56. package/dist/server/content-helpers.test.js.map +1 -0
  57. package/dist/server/express.d.ts +15 -0
  58. package/dist/server/express.js +103 -0
  59. package/dist/server/express.js.map +1 -0
  60. package/dist/server/express.test.d.ts +1 -0
  61. package/dist/server/express.test.js +348 -0
  62. package/dist/server/express.test.js.map +1 -0
  63. package/dist/server/index.d.ts +5 -3
  64. package/dist/server/index.js +3 -2
  65. package/dist/server/index.js.map +1 -1
  66. package/dist/server/inferUtilityTypes.d.ts +6 -6
  67. package/dist/server/metric.d.ts +14 -0
  68. package/dist/server/metric.js +62 -0
  69. package/dist/server/metric.js.map +1 -0
  70. package/dist/server/middleware.d.ts +124 -0
  71. package/dist/server/middleware.js +93 -0
  72. package/dist/server/middleware.js.map +1 -0
  73. package/dist/server/middleware.test-d.d.ts +1 -0
  74. package/dist/server/middleware.test-d.js +75 -0
  75. package/dist/server/middleware.test-d.js.map +1 -0
  76. package/dist/server/middleware.test.d.ts +1 -0
  77. package/dist/server/middleware.test.js +493 -0
  78. package/dist/server/middleware.test.js.map +1 -0
  79. package/dist/server/server.d.ts +149 -63
  80. package/dist/server/server.js +383 -76
  81. package/dist/server/server.js.map +1 -1
  82. package/dist/server/templateHelper.d.ts +5 -8
  83. package/dist/server/templateHelper.js +3 -22
  84. package/dist/server/templateHelper.js.map +1 -1
  85. package/dist/server/templates.generated.d.ts +4 -0
  86. package/dist/server/templates.generated.js +47 -0
  87. package/dist/server/templates.generated.js.map +1 -0
  88. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  89. package/dist/server/tunnel-proxy-router.js +110 -0
  90. package/dist/server/tunnel-proxy-router.js.map +1 -0
  91. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  92. package/dist/server/tunnel-proxy-router.test.js +229 -0
  93. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  94. package/dist/server/viewsDevServer.d.ts +14 -0
  95. package/dist/server/viewsDevServer.js +45 -0
  96. package/dist/server/viewsDevServer.js.map +1 -0
  97. package/dist/test/utils.d.ts +13 -21
  98. package/dist/test/utils.js +42 -37
  99. package/dist/test/utils.js.map +1 -1
  100. package/dist/test/view.test.d.ts +1 -0
  101. package/dist/test/view.test.js +523 -0
  102. package/dist/test/view.test.js.map +1 -0
  103. package/dist/version.d.ts +1 -0
  104. package/dist/version.js +3 -0
  105. package/dist/version.js.map +1 -0
  106. package/dist/web/bridges/apps-sdk/adaptor.d.ts +8 -6
  107. package/dist/web/bridges/apps-sdk/adaptor.js +46 -17
  108. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  109. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -1
  110. package/dist/web/bridges/apps-sdk/index.d.ts +1 -1
  111. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  112. package/dist/web/bridges/apps-sdk/types.d.ts +24 -13
  113. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  114. package/dist/web/bridges/mcp-app/adaptor.d.ts +20 -8
  115. package/dist/web/bridges/mcp-app/adaptor.js +141 -61
  116. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  117. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -30
  118. package/dist/web/bridges/mcp-app/bridge.js +43 -196
  119. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  120. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +5 -3
  121. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +2 -2
  122. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  123. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -41
  124. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  125. package/dist/web/bridges/types.d.ts +22 -11
  126. package/dist/web/components/modal-provider.js +3 -5
  127. package/dist/web/components/modal-provider.js.map +1 -1
  128. package/dist/web/create-store.js +17 -3
  129. package/dist/web/create-store.js.map +1 -1
  130. package/dist/web/create-store.test.js +23 -20
  131. package/dist/web/create-store.test.js.map +1 -1
  132. package/dist/web/data-llm.d.ts +1 -1
  133. package/dist/web/data-llm.js +3 -3
  134. package/dist/web/data-llm.js.map +1 -1
  135. package/dist/web/data-llm.test.js +33 -30
  136. package/dist/web/data-llm.test.js.map +1 -1
  137. package/dist/web/generate-helpers.d.ts +20 -18
  138. package/dist/web/generate-helpers.js +20 -18
  139. package/dist/web/generate-helpers.js.map +1 -1
  140. package/dist/web/generate-helpers.test-d.js +26 -26
  141. package/dist/web/generate-helpers.test-d.js.map +1 -1
  142. package/dist/web/helpers/state.d.ts +2 -2
  143. package/dist/web/helpers/state.js +11 -11
  144. package/dist/web/helpers/state.js.map +1 -1
  145. package/dist/web/helpers/state.test.js +9 -9
  146. package/dist/web/helpers/state.test.js.map +1 -1
  147. package/dist/web/hooks/index.d.ts +2 -2
  148. package/dist/web/hooks/index.js +1 -1
  149. package/dist/web/hooks/index.js.map +1 -1
  150. package/dist/web/hooks/test/utils.js +4 -0
  151. package/dist/web/hooks/test/utils.js.map +1 -1
  152. package/dist/web/hooks/use-display-mode.d.ts +3 -3
  153. package/dist/web/hooks/use-display-mode.js.map +1 -1
  154. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  155. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  156. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  157. package/dist/web/hooks/use-files.d.ts +2 -1
  158. package/dist/web/hooks/use-files.js +1 -0
  159. package/dist/web/hooks/use-files.js.map +1 -1
  160. package/dist/web/hooks/use-files.test.js +27 -3
  161. package/dist/web/hooks/use-files.test.js.map +1 -1
  162. package/dist/web/hooks/use-layout.test.js +3 -3
  163. package/dist/web/hooks/use-layout.test.js.map +1 -1
  164. package/dist/web/hooks/use-open-external.d.ts +3 -1
  165. package/dist/web/hooks/use-open-external.js +1 -1
  166. package/dist/web/hooks/use-open-external.js.map +1 -1
  167. package/dist/web/hooks/use-open-external.test.js +26 -11
  168. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  169. package/dist/web/hooks/use-request-modal.d.ts +1 -1
  170. package/dist/web/hooks/use-request-modal.js +4 -4
  171. package/dist/web/hooks/use-request-modal.js.map +1 -1
  172. package/dist/web/hooks/use-request-modal.test.js +5 -1
  173. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  174. package/dist/web/hooks/use-set-open-in-app-url.test.js +5 -11
  175. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  176. package/dist/web/hooks/use-tool-info.test.js +1 -1
  177. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  178. package/dist/web/hooks/use-user.js +18 -2
  179. package/dist/web/hooks/use-user.js.map +1 -1
  180. package/dist/web/hooks/use-user.test.js +29 -1
  181. package/dist/web/hooks/use-user.test.js.map +1 -1
  182. package/dist/web/hooks/use-view-state.d.ts +4 -0
  183. package/dist/web/hooks/use-view-state.js +32 -0
  184. package/dist/web/hooks/use-view-state.js.map +1 -0
  185. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  186. package/dist/web/hooks/use-view-state.test.js +177 -0
  187. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  188. package/dist/web/index.d.ts +1 -2
  189. package/dist/web/index.js +1 -2
  190. package/dist/web/index.js.map +1 -1
  191. package/dist/web/mount-view.d.ts +1 -0
  192. package/dist/web/{mount-widget.js → mount-view.js} +2 -2
  193. package/dist/web/mount-view.js.map +1 -0
  194. package/dist/web/plugin/plugin.d.ts +4 -1
  195. package/dist/web/plugin/plugin.js +135 -18
  196. package/dist/web/plugin/plugin.js.map +1 -1
  197. package/dist/web/plugin/scan-views.d.ts +16 -0
  198. package/dist/web/plugin/scan-views.js +88 -0
  199. package/dist/web/plugin/scan-views.js.map +1 -0
  200. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  201. package/dist/web/plugin/scan-views.test.js +99 -0
  202. package/dist/web/plugin/scan-views.test.js.map +1 -0
  203. package/dist/web/plugin/transform-data-llm.js +1 -1
  204. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  205. package/dist/web/plugin/validate-view.d.ts +1 -0
  206. package/dist/web/plugin/validate-view.js +9 -0
  207. package/dist/web/plugin/validate-view.js.map +1 -0
  208. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  209. package/dist/web/plugin/validate-view.test.js +24 -0
  210. package/dist/web/plugin/validate-view.test.js.map +1 -0
  211. package/package.json +41 -30
  212. package/tsconfig.base.json +33 -0
  213. package/dist/server/templates/development.hbs +0 -67
  214. package/dist/server/templates/production.hbs +0 -6
  215. package/dist/server/widgetsDevServer.d.ts +0 -12
  216. package/dist/server/widgetsDevServer.js +0 -57
  217. package/dist/server/widgetsDevServer.js.map +0 -1
  218. package/dist/test/widget.test.js +0 -255
  219. package/dist/test/widget.test.js.map +0 -1
  220. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  221. package/dist/web/hooks/use-widget-state.js +0 -32
  222. package/dist/web/hooks/use-widget-state.js.map +0 -1
  223. package/dist/web/hooks/use-widget-state.test.js +0 -61
  224. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  225. package/dist/web/mount-widget.d.ts +0 -1
  226. package/dist/web/mount-widget.js.map +0 -1
  227. /package/dist/{test/widget.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  228. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
package/README.md CHANGED
@@ -1,144 +1,157 @@
1
- <div align="center">
2
-
3
- <img alt="Skybridge" src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/github-banner.png" width="100%">
4
-
5
- <br />
6
-
7
- # Skybridge
8
-
9
- **Build ChatGPT & MCP Apps. The Modern TypeScript Way.**
10
-
11
- The fullstack TypeScript framework for AI-embedded widgets.<br />
12
- **Type-safe. React-powered. Platform-agnostic.**
13
-
14
- <br />
15
-
16
- [![NPM Version](https://img.shields.io/npm/v/skybridge?color=e90060&style=for-the-badge)](https://www.npmjs.com/package/skybridge)
17
- [![NPM Downloads](https://img.shields.io/npm/dm/skybridge?color=e90060&style=for-the-badge)](https://www.npmjs.com/package/skybridge)
18
- [![GitHub License](https://img.shields.io/github/license/alpic-ai/skybridge?color=e90060&style=for-the-badge)](https://github.com/alpic-ai/skybridge/blob/main/LICENSE)
19
-
20
- <br />
21
-
22
- [Documentation](https://docs.skybridge.tech) · [Quick Start](https://docs.skybridge.tech/quickstart/create-new-app) · [Showcase](https://docs.skybridge.tech/showcase)
23
-
24
- </div>
25
-
26
- <br />
27
-
28
- ## ✨ Why Skybridge?
29
-
30
- ChatGPT Apps and MCP Apps let you embed **rich, interactive UIs** directly in AI conversations. But the raw SDKs are low-level—no hooks, no type safety, no dev tools, and no HMR.
31
-
32
- **Skybridge fixes that.**
33
-
34
- | | |
35
- |:--|:--|
36
- | 👨‍💻 **Full Dev Environment** — HMR, debug traces, and local devtools. No more refresh loops. | ✅ **End-to-End Type Safety** — tRPC-style inference from server to widget. Autocomplete everywhere. |
37
- | 🔄 **Widget-to-Model Sync** — Keep the model aware of UI state with `data-llm`. Dual surfaces, one source of truth. | ⚒️ **React Query-style Hooks** — `isPending`, `isError`, callbacks. State management you already know. |
38
- | 🌐 **Platform Agnostic** — Write once, run anywhere. Works with ChatGPT (Apps SDK) and MCP-compatible clients. | 📦 **Showcase Examples** — Production-ready examples to learn from and build upon. |
39
-
40
- <br />
41
-
42
- ## 🚀 Get Started
43
-
44
- **Create a new app:**
45
-
46
- ```bash
47
- npm create skybridge@latest
48
- ```
49
-
50
- **Or add to an existing project:**
51
-
1
+ <p align="center">
2
+ <a href="https://skybridge.tech">
3
+ <img alt="Skybridge" src="docs/images/skybridge-readme-banner-new.png" width="100%" />
4
+ </a>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>The full-stack React framework for MCP Apps and MCP Servers.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://docs.skybridge.tech">Documentation</a> ·
13
+ <a href="https://docs.skybridge.tech/quickstart/create-new-app">Quickstart</a> ·
14
+ <a href="https://github.com/alpic-ai/skybridge/tree/main/examples">Examples</a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://www.npmjs.com/package/skybridge"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/v/skybridge?color=77F5EE&amp;labelColor=161B22&amp;style=for-the-badge"><img alt="npm version" src="https://img.shields.io/npm/v/skybridge?color=E3FAF7&amp;labelColor=F6F8FA&amp;style=for-the-badge"></picture></a>
19
+ <a href="https://www.npmjs.com/package/skybridge"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/skybridge?color=D7FFC8&amp;labelColor=161B22&amp;style=for-the-badge"><img alt="npm downloads" src="https://img.shields.io/npm/dm/skybridge?color=E8FBD9&amp;labelColor=F6F8FA&amp;style=for-the-badge"></picture></a>
20
+ <a href="https://discord.com/invite/gNAazGueab"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/Discord-community-77F5EE?style=for-the-badge&amp;logo=discord&amp;logoColor=77F5EE&amp;labelColor=161B22"><img alt="Discord community" src="https://img.shields.io/badge/Discord-community-E3FAF7?style=for-the-badge&amp;logo=discord&amp;logoColor=5865F2&amp;labelColor=F6F8FA"></picture></a>
21
+ <a href="https://github.com/alpic-ai/skybridge/blob/main/LICENSE"><picture><source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/license/alpic-ai/skybridge?color=D7FFC8&amp;labelColor=161B22&amp;style=for-the-badge"><img alt="License: MIT" src="https://img.shields.io/github/license/alpic-ai/skybridge?color=E8FBD9&amp;labelColor=F6F8FA&amp;style=for-the-badge"></picture></a>
22
+ </p>
23
+
24
+ ## About Skybridge
25
+
26
+ MCP Apps is an extension of the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) adding **rich, interactive UI views** to MCP servers. Conversational Apps need seamless interaction between the user, the UI, and the model. This means new UX patterns, developer tooling, and abstractions. This is why we built Skybridge.
27
+
28
+ Features include:
29
+
30
+ - **Delightful dev environment**: It provides a dev server with a local emulator, Hot Module Reload, and a permanent tunnel to connect your local app to Claude and ChatGPT.
31
+ - **Write once, run everywhere**: Skybridge abstracts implementation differences between MCP Clients, so your app runs seamlessly in Claude, ChatGPT, VSCode, and any other compatible MCP Apps client.
32
+ - **Agent-ready**: Powerful Skills, CLI, and programmatic DevTools APIs: it provides everything your coding agent needs to build MCP Apps end-to-end.
33
+ - **Type-safe end-to-end**: tRPC-style inference from MCP server tool definition to React view for type-safety end-to-end from server to frontend.
34
+ - **React-first**: Intuitive React Query-style hooks, with advanced state management.
35
+ - **Examples library**: Get started quickly with production-ready app examples for e-commerce, travel, SaaS, and others.
36
+
37
+ These companies chose Skybridge to deploy their apps on ChatGPT and Claude stores:
38
+
39
+ <p align="center">
40
+ <a href="https://www.datadoghq.com">
41
+ <picture>
42
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/datadog-dark.svg">
43
+ <img src="docs/images/user-logos/datadog-light.svg" alt="Datadog" height="24">
44
+ </picture>
45
+ </a>
46
+ &nbsp;&nbsp;
47
+ <a href="https://bitmovin.com">
48
+ <picture>
49
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/bitmovin-dark.svg">
50
+ <img src="docs/images/user-logos/bitmovin-light.svg" alt="Bitmovin" height="22">
51
+ </picture>
52
+ </a>
53
+ &nbsp;&nbsp;
54
+ <a href="https://www.evaneos.com">
55
+ <picture>
56
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/evaneos-dark.svg">
57
+ <img src="docs/images/user-logos/evaneos-light.svg" alt="Evaneos" height="18">
58
+ </picture>
59
+ </a>
60
+ &nbsp;&nbsp;
61
+ <a href="https://www.touchstream.media">
62
+ <picture>
63
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/touchstream-dark.svg">
64
+ <img src="docs/images/user-logos/touchstream-light.svg" alt="Touchstream" height="24">
65
+ </picture>
66
+ </a>
67
+ &nbsp;&nbsp;
68
+ <a href="https://www.cottages.com">
69
+ <picture>
70
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/cottages-dark.svg">
71
+ <img src="docs/images/user-logos/cottages-light.svg" alt="Cottages.com" height="24">
72
+ </picture>
73
+ </a>
74
+ </p>
75
+
76
+ ## Get started
77
+
78
+ **For agents**
79
+
80
+ Install our [Skill](https://docs.skybridge.tech/devtools/skills) for MCP Apps and ChatGPT Apps:
52
81
  ```bash
53
- npm i skybridge
54
- yarn add skybridge
55
- pnpm add skybridge
56
- bun add skybridge
57
- deno add skybridge
82
+ npx skills add alpic-ai/skybridge -s skybridge
58
83
  ```
84
+ Once installed, if you ask your agent "_what skills do you have?_", it should mention the skybridge skill. Then, you can ask it to:
59
85
 
60
- <div align="center">
61
-
62
- **👉 [Read the Docs](https://docs.skybridge.tech) 👈**
86
+ - _Create a new MCP App_
87
+ - _Migrate my MCP Server to Skybridge framework_
88
+ - _Add a new view to my MCP App_
63
89
 
64
- </div>
90
+ **For humans**
65
91
 
66
- <br />
67
-
68
- ## 📦 Architecture
69
-
70
- Skybridge is a fullstack framework with unified server and client modules:
71
-
72
- - **`skybridge/server`** — Define tools and widgets with full type inference. Extends the MCP SDK.
73
- - **`skybridge/web`** — React hooks that consume your server types. Works with Apps SDK (ChatGPT) and MCP Apps.
74
- - **Dev Environment** — Vite plugin with HMR, DevTools emulator, and optimized builds.
75
-
76
- ### Server
77
-
78
- ```ts
79
- import { McpServer } from "skybridge/server";
80
-
81
- server.registerWidget("flights", {}, {
82
- inputSchema: { destination: z.string() },
83
- }, async ({ destination }) => {
84
- const flights = await searchFlights(destination);
85
- return { structuredContent: { flights } };
86
- });
92
+ Bootstrap a new project with:
93
+ ```bash
94
+ npm create skybridge my-app
87
95
  ```
96
+ For full install instructions, read the [**Quickstart section**](https://docs.skybridge.tech/quickstart/create-new-app) of our documentation.
88
97
 
89
- ### Widget
98
+ ## Documentation
90
99
 
91
- ```tsx
92
- import { useToolInfo } from "skybridge/web";
100
+ The [Skybridge documentation](https://docs.skybridge.tech) covers the full lifecycle of building MCP Apps:
93
101
 
94
- function FlightsWidget() {
95
- const { output } = useToolInfo();
102
+ - [Fundamentals](https://docs.skybridge.tech/fundamentals): understand MCP Apps, ChatGPT Apps, and how Skybridge bridges both runtimes.
103
+ - [Core concepts](https://docs.skybridge.tech/concepts): learn data flow, LLM context sync, type safety, and fast local iteration.
104
+ - [Guides](https://docs.skybridge.tech/guides/fetching-data): build real app behavior with tools, views, state, and model communication.
105
+ - [API Reference](https://docs.skybridge.tech/api-reference): browse server APIs, React hooks, CLI commands, and runtime compatibility.
96
106
 
97
- return output.structuredContent.flights.map(flight =>
98
- <FlightCard key={flight.id} flight={flight} />
99
- );
100
- }
101
- ```
107
+ ## Deploy
102
108
 
103
- <br />
109
+ Deploy Skybridge apps instantly on [Alpic](https://alpic.ai) to get scalable hosting, MCP Analytics, permanent tunnel, MCP auditing and app stores submission help, or self-host on any Node.js-compatible platform.
104
110
 
105
- ## 🎯 Features at a Glance
111
+ Read the [deployment guide](https://docs.skybridge.tech/quickstart/deploy) for the full production path.
106
112
 
107
- - **Live Reload** — Vite HMR. See changes instantly without reinstalling.
108
- - **Typed Hooks** — Full autocomplete for tools, inputs, outputs.
109
- - **Widget → Tool Calls** — Trigger server actions from UI.
110
- - **Dual Surface Sync** — Keep model aware of what users see with `data-llm`.
111
- - **React Query-style API** — `isPending`, `isError`, callbacks.
112
- - **Platform Agnostic** — Works with ChatGPT (Apps SDK) and MCP Apps clients (Goose, VSCode, etc.).
113
- - **MCP Compatible** — Extends the official SDK. Works with any MCP client.
113
+ ## Community & Contributing
114
114
 
115
- <br />
115
+ We invite you to contribute and help improve Skybridge.
116
116
 
117
- ## 📖 Showcase
117
+ Here are a few ways you can get involved:
118
118
 
119
- Explore production-ready examples:
119
+ - **Reporting bugs**: If you run into a bug or unexpected behavior, please open an issue in [GitHub Issues](https://github.com/alpic-ai/skybridge/issues) with a clear reproduction.
120
+ - **Questions and Suggestions**: Need help building with Skybridge or have ideas to improve the framework, docs, examples, or developer experience? [Open an issue](https://github.com/alpic-ai/skybridge/issues) or share them on our [Discord](https://discord.com/invite/gNAazGueab).
121
+ - **Pull requests**: For code or documentation changes, please read the [Contributing Guide](https://github.com/alpic-ai/skybridge/blob/main/CONTRIBUTING.md) before opening a PR.
120
122
 
121
- | Example | Description | Demo | Code |
122
- |------------------------|----------------------------------------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------------------------------|
123
- | **Capitals Explorer** | Interactive world map with geolocation and Wikipedia integration | [Try Demo](https://capitals.skybridge.tech/try) | [View Code](https://github.com/alpic-ai/skybridge/tree/main/examples/capitals) |
124
- | **Ecommerce Carousel** | Product carousel with cart, localization, and modals | [Try Demo](https://ecommerce.skybridge.tech/try) | [View Code](https://github.com/alpic-ai/skybridge/tree/main/examples/ecom-carousel) |
125
- | **Everything** | Comprehensive playground showcasing all hooks and features | [Try Demo](https://everything.skybridge.tech/try) | [View Code](https://github.com/alpic-ai/skybridge/tree/main/examples/everything) |
126
- | **Productivity** | Data visualization dashboard demonstrating Skybridge capabilities for MCP Apps | [Try Demo](https://productivity.skybridge.tech/try) | [View Code](https://github.com/alpic-ai/skybridge/tree/main/examples/productivity) |
123
+ Skybridge is released under the [MIT License](https://github.com/alpic-ai/skybridge/blob/main/LICENSE).
127
124
 
128
- See all examples in the [Showcase](https://docs.skybridge.tech/showcase) or browse the [examples/](examples/) directory.
125
+ ### Contributors
129
126
 
130
- <br />
127
+ Built and maintained by [Harijoe](https://github.com/harijoe), [Fred Barthelet](https://github.com/fredericbarthelet), and the [Alpic](https://alpic.ai) team.
131
128
 
132
- <div align="center">
129
+ <a href="https://github.com/alpic-ai/skybridge/graphs/contributors">
130
+ <img src="https://contrib.rocks/image?repo=alpic-ai/skybridge" alt="Skybridge contributors">
131
+ </a>
133
132
 
134
- [![GitHub Discussions](https://img.shields.io/badge/Discussions-Ask%20Questions-blue?style=flat-square&logo=github)](https://github.com/alpic-ai/skybridge/discussions)
135
- [![GitHub Issues](https://img.shields.io/badge/Issues-Report%20Bugs-red?style=flat-square&logo=github)](https://github.com/alpic-ai/skybridge/issues)
136
- [![Discord](https://img.shields.io/badge/Discord-Chat-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.com/invite/gNAazGueab)
133
+ ## Example templates
137
134
 
138
- See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions
135
+ Explore all our example templates in the [Examples](https://docs.skybridge.tech/examples) section of the documentation.
139
136
 
140
- <br />
137
+ | Preview | App | Description | Demo | Code |
138
+ | --- | --- | --- | --- | --- |
139
+ | <img src="docs/images/showcase-capitals.png" alt="Capitals Explorer" width="160" /> | Capitals Explorer | Interactive world map with geolocation, country information, and dynamic capital exploration. | [Try Demo](https://capitals.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/capitals) |
140
+ | <img src="docs/images/showcase-flight-booking.png" alt="Flight Booking" width="160" /> | Flight Booking | Flight search carousel with route details, pricing comparison, and external booking. | [Try Demo](https://flight-booking.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/flight-booking) |
141
+ | <img src="docs/images/showcase-ecommerce.png" alt="Ecommerce Carousel" width="160" /> | Ecommerce Carousel | Product carousel with persistent cart, localization, theme switching, and modal dialogs. | [Try Demo](https://ecommerce.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/ecom-carousel) |
142
+ | <img src="docs/images/showcase-example.png" alt="Everything" width="160" /> | Everything | Comprehensive playground showcasing all Skybridge hooks and features. | [Try Demo](https://everything.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/everything) |
143
+ | <img src="docs/images/showcase-investigation-game.png" alt="Investigation Game" width="160" /> | Investigation Game | Multi-screen mystery game with fullscreen mode and dynamic story progression. | [Try Demo](https://investigation-game.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/investigation-game) |
144
+ | <img src="docs/images/showcase-productivity.png" alt="Productivity" width="160" /> | Productivity | Data visualization dashboard demonstrating Skybridge capabilities for MCP Apps. | [Try Demo](https://productivity.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/productivity) |
145
+ | <img src="docs/images/showcase-times-up.png" alt="Time's Up" width="160" /> | Time's Up | Word-guessing party game where the user gives hints and the AI tries to guess. | [Try Demo](https://times-up.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/times-up) |
146
+ | <img src="docs/images/showcase-manifest-ui.png" alt="Manifest UI" width="160" /> | Manifest UI | Agentic component library example for rich AI-powered experiences. | [Try Demo](https://manifest-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/manifest-ui) |
147
+ | <img src="docs/images/showcase-generative-ui.png" alt="Generative UI" width="160" /> | Generative UI | LLM-generated dynamic UIs with json-render and 36 pre-built shadcn/ui components. | [Try Demo](https://generative-ui.skybridge.tech/try) | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/generative-ui) |
148
+ | <img src="docs/images/showcase-lumo.png" alt="Lumo Interactive AI Tutor" width="160" /> | Lumo — Interactive AI Tutor | Adaptive tutor with Mermaid diagrams, mind maps, quizzes, and fill-in-the-blank exercises. | [Try Demo](https://lumo-mcp-app-39519fdd.alpic.live/try) | [View code](https://github.com/connorads/lumo-mcp-app) |
141
149
 
142
- **[MIT License](LICENSE)** · Made with ❤️ by **[Alpic](https://alpic.ai)**
150
+ ### OAuth providers
143
151
 
144
- </div>
152
+ | Preview | Provider | Description | Code |
153
+ | --- | --- | --- | --- |
154
+ | <img src="docs/images/showcase-clerk.png" alt="Auth Clerk" width="160" /> | Clerk | Full OAuth authentication with Clerk and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-clerk) |
155
+ | <img src="docs/images/showcase-workos.png" alt="Auth WorkOS AuthKit" width="160" /> | WorkOS AuthKit | Full OAuth authentication with WorkOS AuthKit and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-workos) |
156
+ | <img src="docs/images/showcase-stytch.png" alt="Auth Stytch" width="160" /> | Stytch | Full OAuth authentication with Stytch and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-stytch) |
157
+ | <img src="docs/images/showcase-auth0.png" alt="Auth Auth0" width="160" /> | Auth0 | Full OAuth authentication with Auth0 and personalized coffee shop search. | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-auth0) |
@@ -0,0 +1,18 @@
1
+ export declare function resolvePort(flagPort?: number): Promise<{
2
+ port: number;
3
+ fallback: boolean;
4
+ envWarning?: undefined;
5
+ } | {
6
+ port: number;
7
+ fallback: boolean;
8
+ envWarning: string;
9
+ }>;
10
+ /**
11
+ * Returns the given port if available, otherwise lets the OS
12
+ * pick a free port via `listen(0)`.
13
+ *
14
+ * @param host - Bind address for the check. Pass `"localhost"` for
15
+ * services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for
16
+ * services that bind to all interfaces (e.g. the HTTP server).
17
+ */
18
+ export declare function detectAvailablePort(startPort: number, host?: string): Promise<number>;
@@ -0,0 +1,61 @@
1
+ import net from "node:net";
2
+ const DEFAULT_PORT = 3000;
3
+ export async function resolvePort(flagPort) {
4
+ if (flagPort && flagPort > 1) {
5
+ return { port: flagPort, fallback: false };
6
+ }
7
+ const rawEnv = process.env.PORT;
8
+ if (rawEnv) {
9
+ const parsed = Number(rawEnv);
10
+ if (Number.isInteger(parsed) && parsed > 0) {
11
+ return { port: parsed, fallback: false };
12
+ }
13
+ return {
14
+ port: await detectAvailablePort(DEFAULT_PORT),
15
+ fallback: false,
16
+ envWarning: `Invalid PORT="${rawEnv}", ignoring and using default`,
17
+ };
18
+ }
19
+ const port = await detectAvailablePort(DEFAULT_PORT);
20
+ return { port, fallback: port !== DEFAULT_PORT };
21
+ }
22
+ /**
23
+ * Returns the given port if available, otherwise lets the OS
24
+ * pick a free port via `listen(0)`.
25
+ *
26
+ * @param host - Bind address for the check. Pass `"localhost"` for
27
+ * services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for
28
+ * services that bind to all interfaces (e.g. the HTTP server).
29
+ */
30
+ export async function detectAvailablePort(startPort, host) {
31
+ const available = await isPortAvailable(startPort, host);
32
+ if (available) {
33
+ return startPort;
34
+ }
35
+ return new Promise((resolve, reject) => {
36
+ const server = net.createServer();
37
+ server.once("error", reject);
38
+ server.once("listening", () => {
39
+ const addr = server.address();
40
+ if (addr && typeof addr === "object") {
41
+ const { port } = addr;
42
+ server.close(() => resolve(port));
43
+ }
44
+ else {
45
+ server.close(() => reject(new Error("Failed to detect available port")));
46
+ }
47
+ });
48
+ server.listen(0, host);
49
+ });
50
+ }
51
+ function isPortAvailable(port, host) {
52
+ return new Promise((resolve) => {
53
+ const server = net.createServer();
54
+ server.once("error", () => resolve(false));
55
+ server.once("listening", () => {
56
+ server.close(() => resolve(true));
57
+ });
58
+ server.listen(port, host);
59
+ });
60
+ }
61
+ //# sourceMappingURL=detect-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-port.js","sourceRoot":"","sources":["../../src/cli/detect-port.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAiB;IACjD,IAAI,QAAQ,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,mBAAmB,CAAC,YAAY,CAAC;YAC7C,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,iBAAiB,MAAM,+BAA+B;SACnE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,IAAa;IAEb,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;gBACtB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAChB,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CACrD,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,IAAa;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  export const Header = ({ version, children, }) => {
4
- return (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u26F0", " ", "Welcome to Skybridge"] }), _jsxs(Text, { color: "cyan", children: [" v", version] }), children] }));
4
+ return (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u26F0", " ", "Skybridge"] }), _jsxs(Text, { color: "cyan", children: [" v", version] }), children] }));
5
5
  };
6
6
  //# sourceMappingURL=header.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/cli/header.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,OAAO,EACP,QAAQ,GAIT,EAAE,EAAE;IACH,OAAO,CACL,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,6BACnB,IAAI,4BACD,EACP,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,mBAAI,OAAO,IAAQ,EACpC,QAAQ,IACL,CACP,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/cli/header.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,OAAO,EACP,QAAQ,GAIT,EAAE,EAAE;IACH,OAAO,CACL,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,6BACnB,IAAI,iBACD,EACP,MAAC,IAAI,IAAC,KAAK,EAAC,MAAM,mBAAI,OAAO,IAAQ,EACpC,QAAQ,IACL,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type SpawnFn, TunnelManager } from "./tunnel.js";
2
+ export type TunnelControlServer = {
3
+ port: number;
4
+ manager: TunnelManager;
5
+ close: () => Promise<void>;
6
+ };
7
+ export declare function startTunnelControlServer(getPort: () => number, options?: {
8
+ spawn?: SpawnFn;
9
+ }): Promise<TunnelControlServer>;
@@ -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"}
@@ -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"}
@@ -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"}