skybridge 0.0.0-dev.e39da51 → 0.0.0-dev.e3c0d0b

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 (224) hide show
  1. package/README.md +157 -122
  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 +8 -13
  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 +28 -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 +13 -3
  58. package/dist/server/express.js +53 -21
  59. package/dist/server/express.js.map +1 -1
  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 +133 -69
  80. package/dist/server/server.js +329 -94
  81. package/dist/server/server.js.map +1 -1
  82. package/dist/server/templateHelper.d.ts +5 -6
  83. package/dist/server/templateHelper.js.map +1 -1
  84. package/dist/server/templates/development.hbs +2 -57
  85. package/dist/server/templates/production.hbs +1 -1
  86. package/dist/server/tunnel-proxy-router.d.ts +7 -0
  87. package/dist/server/tunnel-proxy-router.js +110 -0
  88. package/dist/server/tunnel-proxy-router.js.map +1 -0
  89. package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
  90. package/dist/server/tunnel-proxy-router.test.js +229 -0
  91. package/dist/server/tunnel-proxy-router.test.js.map +1 -0
  92. package/dist/server/viewsDevServer.d.ts +14 -0
  93. package/dist/server/viewsDevServer.js +45 -0
  94. package/dist/server/viewsDevServer.js.map +1 -0
  95. package/dist/test/utils.d.ts +16 -244
  96. package/dist/test/utils.js +42 -37
  97. package/dist/test/utils.js.map +1 -1
  98. package/dist/test/view.test.d.ts +1 -0
  99. package/dist/test/view.test.js +523 -0
  100. package/dist/test/view.test.js.map +1 -0
  101. package/dist/version.d.ts +1 -0
  102. package/dist/version.js +5 -0
  103. package/dist/version.js.map +1 -0
  104. package/dist/web/bridges/apps-sdk/adaptor.d.ts +8 -6
  105. package/dist/web/bridges/apps-sdk/adaptor.js +39 -16
  106. package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
  107. package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -5
  108. package/dist/web/bridges/apps-sdk/index.d.ts +1 -1
  109. package/dist/web/bridges/apps-sdk/index.js.map +1 -1
  110. package/dist/web/bridges/apps-sdk/types.d.ts +19 -10
  111. package/dist/web/bridges/apps-sdk/types.js.map +1 -1
  112. package/dist/web/bridges/mcp-app/adaptor.d.ts +20 -8
  113. package/dist/web/bridges/mcp-app/adaptor.js +141 -61
  114. package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
  115. package/dist/web/bridges/mcp-app/bridge.d.ts +13 -30
  116. package/dist/web/bridges/mcp-app/bridge.js +43 -196
  117. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  118. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +5 -3
  119. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +2 -2
  120. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  121. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -41
  122. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  123. package/dist/web/bridges/types.d.ts +22 -11
  124. package/dist/web/components/modal-provider.js +3 -5
  125. package/dist/web/components/modal-provider.js.map +1 -1
  126. package/dist/web/create-store.js +17 -3
  127. package/dist/web/create-store.js.map +1 -1
  128. package/dist/web/create-store.test.js +21 -18
  129. package/dist/web/create-store.test.js.map +1 -1
  130. package/dist/web/data-llm.d.ts +1 -1
  131. package/dist/web/data-llm.js +3 -3
  132. package/dist/web/data-llm.js.map +1 -1
  133. package/dist/web/data-llm.test.js +24 -23
  134. package/dist/web/data-llm.test.js.map +1 -1
  135. package/dist/web/generate-helpers.d.ts +20 -18
  136. package/dist/web/generate-helpers.js +20 -18
  137. package/dist/web/generate-helpers.js.map +1 -1
  138. package/dist/web/generate-helpers.test-d.js +26 -26
  139. package/dist/web/generate-helpers.test-d.js.map +1 -1
  140. package/dist/web/helpers/state.d.ts +2 -2
  141. package/dist/web/helpers/state.js +11 -11
  142. package/dist/web/helpers/state.js.map +1 -1
  143. package/dist/web/helpers/state.test.js +9 -9
  144. package/dist/web/helpers/state.test.js.map +1 -1
  145. package/dist/web/hooks/index.d.ts +2 -2
  146. package/dist/web/hooks/index.js +1 -1
  147. package/dist/web/hooks/index.js.map +1 -1
  148. package/dist/web/hooks/test/utils.js +4 -0
  149. package/dist/web/hooks/test/utils.js.map +1 -1
  150. package/dist/web/hooks/use-display-mode.d.ts +3 -3
  151. package/dist/web/hooks/use-display-mode.js.map +1 -1
  152. package/dist/web/hooks/use-display-mode.test-d.d.ts +1 -0
  153. package/dist/web/hooks/use-display-mode.test-d.js +8 -0
  154. package/dist/web/hooks/use-display-mode.test-d.js.map +1 -0
  155. package/dist/web/hooks/use-files.d.ts +2 -1
  156. package/dist/web/hooks/use-files.js +1 -0
  157. package/dist/web/hooks/use-files.js.map +1 -1
  158. package/dist/web/hooks/use-files.test.js +27 -3
  159. package/dist/web/hooks/use-files.test.js.map +1 -1
  160. package/dist/web/hooks/use-layout.test.js +3 -3
  161. package/dist/web/hooks/use-layout.test.js.map +1 -1
  162. package/dist/web/hooks/use-open-external.d.ts +3 -1
  163. package/dist/web/hooks/use-open-external.js +1 -1
  164. package/dist/web/hooks/use-open-external.js.map +1 -1
  165. package/dist/web/hooks/use-open-external.test.js +26 -11
  166. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  167. package/dist/web/hooks/use-request-modal.d.ts +1 -1
  168. package/dist/web/hooks/use-request-modal.js +4 -4
  169. package/dist/web/hooks/use-request-modal.js.map +1 -1
  170. package/dist/web/hooks/use-request-modal.test.js +5 -1
  171. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  172. package/dist/web/hooks/use-set-open-in-app-url.test.js +5 -11
  173. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  174. package/dist/web/hooks/use-tool-info.test.js +1 -1
  175. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  176. package/dist/web/hooks/use-user.js +18 -2
  177. package/dist/web/hooks/use-user.js.map +1 -1
  178. package/dist/web/hooks/use-user.test.js +29 -1
  179. package/dist/web/hooks/use-user.test.js.map +1 -1
  180. package/dist/web/hooks/use-view-state.d.ts +4 -0
  181. package/dist/web/hooks/use-view-state.js +32 -0
  182. package/dist/web/hooks/use-view-state.js.map +1 -0
  183. package/dist/web/hooks/use-view-state.test.d.ts +1 -0
  184. package/dist/web/hooks/use-view-state.test.js +177 -0
  185. package/dist/web/hooks/use-view-state.test.js.map +1 -0
  186. package/dist/web/index.d.ts +1 -2
  187. package/dist/web/index.js +1 -2
  188. package/dist/web/index.js.map +1 -1
  189. package/dist/web/mount-view.d.ts +1 -0
  190. package/dist/web/{mount-widget.js → mount-view.js} +2 -2
  191. package/dist/web/mount-view.js.map +1 -0
  192. package/dist/web/plugin/plugin.d.ts +4 -1
  193. package/dist/web/plugin/plugin.js +135 -18
  194. package/dist/web/plugin/plugin.js.map +1 -1
  195. package/dist/web/plugin/scan-views.d.ts +16 -0
  196. package/dist/web/plugin/scan-views.js +88 -0
  197. package/dist/web/plugin/scan-views.js.map +1 -0
  198. package/dist/web/plugin/scan-views.test.d.ts +1 -0
  199. package/dist/web/plugin/scan-views.test.js +99 -0
  200. package/dist/web/plugin/scan-views.test.js.map +1 -0
  201. package/dist/web/plugin/transform-data-llm.js +1 -1
  202. package/dist/web/plugin/transform-data-llm.js.map +1 -1
  203. package/dist/web/plugin/validate-view.d.ts +1 -0
  204. package/dist/web/plugin/validate-view.js +9 -0
  205. package/dist/web/plugin/validate-view.js.map +1 -0
  206. package/dist/web/plugin/validate-view.test.d.ts +1 -0
  207. package/dist/web/plugin/validate-view.test.js +24 -0
  208. package/dist/web/plugin/validate-view.test.js.map +1 -0
  209. package/package.json +33 -23
  210. package/tsconfig.base.json +33 -0
  211. package/dist/server/widgetsDevServer.d.ts +0 -12
  212. package/dist/server/widgetsDevServer.js +0 -57
  213. package/dist/server/widgetsDevServer.js.map +0 -1
  214. package/dist/test/widget.test.js +0 -261
  215. package/dist/test/widget.test.js.map +0 -1
  216. package/dist/web/hooks/use-widget-state.d.ts +0 -4
  217. package/dist/web/hooks/use-widget-state.js +0 -32
  218. package/dist/web/hooks/use-widget-state.js.map +0 -1
  219. package/dist/web/hooks/use-widget-state.test.js +0 -62
  220. package/dist/web/hooks/use-widget-state.test.js.map +0 -1
  221. package/dist/web/mount-widget.d.ts +0 -1
  222. package/dist/web/mount-widget.js.map +0 -1
  223. /package/dist/{test/widget.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
  224. /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
package/README.md CHANGED
@@ -1,145 +1,180 @@
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
-
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
+ <a href="https://docs.skybridge.tech/showcase">Showcase</a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <img alt="Skybridge app preview" src="docs/images/skybridge-readme-preview.png" width="520" />
20
+ </p>
21
+
22
+ <p align="center">
23
+ <a href="https://www.npmjs.com/package/skybridge">
24
+ <picture>
25
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/v/skybridge?color=77F5EE&amp;labelColor=161B22&amp;style=for-the-badge">
26
+ <img alt="npm version" src="https://img.shields.io/npm/v/skybridge?color=E3FAF7&amp;labelColor=F6F8FA&amp;style=for-the-badge">
27
+ </picture>
28
+ </a>
29
+ <a href="https://www.npmjs.com/package/skybridge">
30
+ <picture>
31
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/skybridge?color=D7FFC8&amp;labelColor=161B22&amp;style=for-the-badge">
32
+ <img alt="npm downloads" src="https://img.shields.io/npm/dm/skybridge?color=E8FBD9&amp;labelColor=F6F8FA&amp;style=for-the-badge">
33
+ </picture>
34
+ </a>
35
+ <a href="https://discord.com/invite/gNAazGueab">
36
+ <picture>
37
+ <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">
38
+ <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">
39
+ </picture>
40
+ </a>
41
+ <a href="https://github.com/alpic-ai/skybridge/blob/main/LICENSE">
42
+ <picture>
43
+ <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">
44
+ <img alt="License: MIT" src="https://img.shields.io/github/license/alpic-ai/skybridge?color=E8FBD9&amp;labelColor=F6F8FA&amp;style=for-the-badge">
45
+ </picture>
46
+ </a>
47
+ </p>
48
+
49
+ ## Used at scale in production
50
+
51
+ Skybridge powers more than 10% of apps published in the ChatGPT and Claude stores. Discover featured apps in our [showcase](https://skybridge.tech/showcase).
52
+
53
+ <p align="center">
54
+ <a href="https://www.datadoghq.com">
55
+ <picture>
56
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/datadog-dark.svg">
57
+ <img src="docs/images/user-logos/datadog-light.svg" alt="Datadog" height="24">
58
+ </picture>
59
+ </a>
60
+ &nbsp;&nbsp;
61
+ <a href="https://bitmovin.com">
62
+ <picture>
63
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/bitmovin-dark.svg">
64
+ <img src="docs/images/user-logos/bitmovin-light.svg" alt="Bitmovin" height="22">
65
+ </picture>
66
+ </a>
67
+ &nbsp;&nbsp;
68
+ <a href="https://www.evaneos.com">
69
+ <picture>
70
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/evaneos-dark.svg">
71
+ <img src="docs/images/user-logos/evaneos-light.svg" alt="Evaneos" height="18">
72
+ </picture>
73
+ </a>
74
+ &nbsp;&nbsp;
75
+ <a href="https://www.touchstream.media">
76
+ <picture>
77
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/touchstream-dark.svg">
78
+ <img src="docs/images/user-logos/touchstream-light.svg" alt="Touchstream" height="24">
79
+ </picture>
80
+ </a>
81
+ &nbsp;&nbsp;
82
+ <a href="https://www.cottages.com">
83
+ <picture>
84
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/user-logos/cottages-dark.svg">
85
+ <img src="docs/images/user-logos/cottages-light.svg" alt="Cottages.com" height="24">
86
+ </picture>
87
+ </a>
88
+ </p>
89
+
90
+ ## Why Skybridge?
91
+
92
+ 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. Building conversational Apps requires new UX design concepts, developer tooling and abstractions to build apps that make interactions between the user, the UI and the model seamless.
93
+
94
+ Skybridge is the answer to all the problems we ran into while building hundreds of production MCP Apps. Features include:
95
+
96
+ - **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.
97
+ - **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.
98
+ - **Agent-ready**: Powerful Skills, CLI, and programmatic DevTools APIs: it provides everything your coding agent needs to build MCP Apps end-to-end.
99
+ - **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.
100
+ - **React-first**: Intuitive React Query-style hooks, with advanced state management.
101
+ - **Examples library**: Get started quickly with production-ready app examples for e-commerce, travel, SaaS, and others.
102
+
103
+ ## Get started
104
+
105
+ **For agents**
106
+
107
+ Install our [Skill](https://docs.skybridge.tech/devtools/skills) for MCP Apps and ChatGPT Apps:
46
108
  ```bash
47
- npm create skybridge@latest
109
+ npx skills add alpic-ai/skybridge -s skybridge
48
110
  ```
111
+ Once installed, if you ask your agent "_what skills do you have?_", it should mention the skybridge skill. Then, you can ask it to:
49
112
 
50
- **Or add to an existing project:**
51
-
52
- ```bash
53
- npm i skybridge
54
- yarn add skybridge
55
- pnpm add skybridge
56
- bun add skybridge
57
- deno add skybridge
58
- ```
59
-
60
- <div align="center">
61
-
62
- **👉 [Read the Docs](https://docs.skybridge.tech) 👈**
63
-
64
- </div>
65
-
66
- <br />
113
+ - _Create a new MCP App_
114
+ - _Migrate my MCP Server to Skybridge framework_
115
+ - _Add a new view to my MCP App_
67
116
 
68
- ## 📦 Architecture
117
+ **For humans**
69
118
 
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
- });
119
+ Bootstrap a new project with:
120
+ ```bash
121
+ npm create skybridge my-app
87
122
  ```
123
+ For full install instructions, read the [**Quickstart section**](https://docs.skybridge.tech/quickstart/create-new-app) of our documentation.
88
124
 
89
- ### Widget
90
-
91
- ```tsx
92
- import { useToolInfo } from "skybridge/web";
125
+ ## Documentation
93
126
 
94
- function FlightsWidget() {
95
- const { output } = useToolInfo();
96
-
97
- return output.structuredContent.flights.map(flight =>
98
- <FlightCard key={flight.id} flight={flight} />
99
- );
100
- }
101
- ```
127
+ The [Skybridge documentation](https://docs.skybridge.tech) covers the full lifecycle of building MCP Apps:
102
128
 
103
- <br />
129
+ - [Fundamentals](https://docs.skybridge.tech/fundamentals): understand MCP Apps, ChatGPT Apps, and how Skybridge bridges both runtimes.
130
+ - [Core concepts](https://docs.skybridge.tech/concepts): learn data flow, LLM context sync, type safety, and fast local iteration.
131
+ - [Guides](https://docs.skybridge.tech/guides/fetching-data): build real app behavior with tools, views, state, and model communication.
132
+ - [API Reference](https://docs.skybridge.tech/api-reference): browse server APIs, React hooks, CLI commands, and runtime compatibility.
104
133
 
105
- ## 🎯 Features at a Glance
134
+ ## Deploy
106
135
 
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.
136
+ 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.
114
137
 
115
- <br />
138
+ Read the [deployment guide](https://docs.skybridge.tech/quickstart/deploy) for the full production path.
116
139
 
117
- ## 📖 Showcase
140
+ ## Example templates
118
141
 
119
- Explore production-ready examples:
142
+ Explore all our example templates in the [Examples](https://docs.skybridge.tech/examples) section of the documentation.
120
143
 
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) |
127
- | **Manifest Starter** | Starter app with Manifest UI agentic components out-of-the-box | [Try Demo](https://manifest-ui.skybridge.tech/try) | [View Code](https://github.com/alpic-ai/skybridge/tree/main/examples/manifest-ui) |
144
+ | Preview | App | Description | Demo | Code |
145
+ | --- | --- | --- | --- | --- |
146
+ | <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) |
147
+ | <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) |
148
+ | <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) |
149
+ | <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) |
150
+ | <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) |
151
+ | <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) |
152
+ | <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) |
153
+ | <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) |
154
+ | <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) |
155
+ | <img src="docs/images/showcase-magic-8-ball.png" alt="Magic 8-Ball" width="160" /> | Magic 8-Ball | Default starter app for the core Skybridge loop: input, server logic, and view rendering. | [Try Demo](https://magic-8-ball.skybridge.tech/try) | [View code](https://github.com/alpic-ai/apps-sdk-template) |
156
+ | <img src="docs/images/showcase-clerk.png" alt="Auth Clerk" width="160" /> | Auth — Clerk | Full OAuth authentication with Clerk and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-clerk) |
157
+ | <img src="docs/images/showcase-workos.png" alt="Auth WorkOS AuthKit" width="160" /> | Auth — 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) |
158
+ | <img src="docs/images/showcase-stytch.png" alt="Auth Stytch" width="160" /> | Auth — Stytch | Full OAuth authentication with Stytch and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-stytch) |
159
+ | <img src="docs/images/showcase-auth0.png" alt="Auth Auth0" width="160" /> | Auth — Auth0 | Full OAuth authentication with Auth0 and personalized coffee shop search. | — | [View code](https://github.com/alpic-ai/skybridge/tree/main/examples/auth-auth0) |
160
+ | <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) |
128
161
 
129
- See all examples in the [Showcase](https://docs.skybridge.tech/showcase) or browse the [examples/](examples/) directory.
162
+ ## Community & Contributing
130
163
 
131
- <br />
164
+ We invite you to contribute and help improve Skybridge.
132
165
 
133
- <div align="center">
166
+ Here are a few ways you can get involved:
134
167
 
135
- [![GitHub Discussions](https://img.shields.io/badge/Discussions-Ask%20Questions-blue?style=flat-square&logo=github)](https://github.com/alpic-ai/skybridge/discussions)
136
- [![GitHub Issues](https://img.shields.io/badge/Issues-Report%20Bugs-red?style=flat-square&logo=github)](https://github.com/alpic-ai/skybridge/issues)
137
- [![Discord](https://img.shields.io/badge/Discord-Chat-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.com/invite/gNAazGueab)
168
+ - **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.
169
+ - **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).
170
+ - **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.
138
171
 
139
- See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions
172
+ Skybridge is released under the [MIT License](https://github.com/alpic-ai/skybridge/blob/main/LICENSE).
140
173
 
141
- <br />
174
+ ### Contributors
142
175
 
143
- **[MIT License](LICENSE)** · Made with ❤️ by **[Alpic](https://alpic.ai)**
176
+ Built and maintained by [Harijoe](https://github.com/harijoe), [Fred Barthelet](https://github.com/fredericbarthelet), and the [Alpic](https://alpic.ai) team.
144
177
 
145
- </div>
178
+ <a href="https://github.com/alpic-ai/skybridge/graphs/contributors">
179
+ <img src="https://contrib.rocks/image?repo=alpic-ai/skybridge" alt="Skybridge contributors">
180
+ </a>
@@ -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"}