sunpeak 0.14.1 → 0.15.1

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 (130) hide show
  1. package/README.md +48 -36
  2. package/bin/commands/build.mjs +2 -1
  3. package/bin/commands/dev.mjs +72 -2
  4. package/dist/chatgpt/{conversation.d.ts → chatgpt-conversation.d.ts} +1 -1
  5. package/dist/chatgpt/chatgpt-host.d.ts +1 -0
  6. package/dist/chatgpt/globals.css +618 -6156
  7. package/dist/chatgpt/index.cjs +11 -8
  8. package/dist/chatgpt/index.cjs.map +1 -1
  9. package/dist/chatgpt/index.d.ts +10 -32
  10. package/dist/chatgpt/index.js +15 -12
  11. package/dist/chatgpt/index.js.map +1 -1
  12. package/dist/claude/claude-conversation.d.ts +23 -0
  13. package/dist/claude/claude-host.d.ts +1 -0
  14. package/dist/claude/index.cjs +6 -0
  15. package/dist/claude/index.cjs.map +1 -0
  16. package/dist/claude/index.d.ts +1 -0
  17. package/dist/claude/index.js +6 -0
  18. package/dist/claude/index.js.map +1 -0
  19. package/dist/claude-host-C7KPfOM8.cjs +284 -0
  20. package/dist/claude-host-C7KPfOM8.cjs.map +1 -0
  21. package/dist/claude-host-CaD7ptbt.js +283 -0
  22. package/dist/claude-host-CaD7ptbt.js.map +1 -0
  23. package/dist/{discovery-COZUnY6a.js → discovery-DzV3HLXs.js} +5 -5
  24. package/dist/{discovery-COZUnY6a.js.map → discovery-DzV3HLXs.js.map} +1 -1
  25. package/dist/hooks/index.d.ts +4 -0
  26. package/dist/hooks/use-app-tools.d.ts +44 -0
  27. package/dist/hooks/use-update-model-context.d.ts +29 -0
  28. package/dist/index-BKrboRah.js +44 -0
  29. package/dist/index-BKrboRah.js.map +1 -0
  30. package/dist/index-BSKuY-oH.cjs +527 -0
  31. package/dist/index-BSKuY-oH.cjs.map +1 -0
  32. package/dist/index-CiqvXo8n.js +512 -0
  33. package/dist/index-CiqvXo8n.js.map +1 -0
  34. package/dist/index-Dr-L0Nb3.cjs +43 -0
  35. package/dist/index-Dr-L0Nb3.cjs.map +1 -0
  36. package/dist/index.cjs +1705 -1647
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.js +2732 -2675
  40. package/dist/index.js.map +1 -1
  41. package/dist/lib/default-style-variables.d.ts +2 -0
  42. package/dist/lib/discovery-cli.js +1 -1
  43. package/dist/mcp/favicon.d.ts +1 -1
  44. package/dist/mcp/index.cjs +87 -28
  45. package/dist/mcp/index.cjs.map +1 -1
  46. package/dist/mcp/index.d.ts +3 -1
  47. package/dist/mcp/index.js +86 -27
  48. package/dist/mcp/index.js.map +1 -1
  49. package/dist/mcp/server.d.ts +4 -3
  50. package/dist/mcp/types.d.ts +11 -0
  51. package/dist/platform/chatgpt/index.cjs +1 -1
  52. package/dist/platform/chatgpt/index.js +1 -1
  53. package/dist/platform/index.cjs +3 -0
  54. package/dist/platform/index.cjs.map +1 -1
  55. package/dist/platform/index.d.ts +3 -1
  56. package/dist/platform/index.js +3 -0
  57. package/dist/platform/index.js.map +1 -1
  58. package/dist/{protocol-BQCnIrc9.js → protocol-DFbsCx7E.js} +29 -29
  59. package/dist/{protocol-BQCnIrc9.js.map → protocol-DFbsCx7E.js.map} +1 -1
  60. package/dist/simulator/host-styles.d.ts +5 -0
  61. package/dist/simulator/hosts.d.ts +73 -0
  62. package/dist/{chatgpt → simulator}/iframe-resource.d.ts +27 -3
  63. package/dist/simulator/index.cjs +40 -0
  64. package/dist/simulator/index.cjs.map +1 -0
  65. package/dist/simulator/index.d.ts +18 -0
  66. package/dist/simulator/index.js +40 -0
  67. package/dist/simulator/index.js.map +1 -0
  68. package/dist/{chatgpt → simulator}/mcp-app-host.d.ts +8 -1
  69. package/dist/simulator/mock-openai-runtime.d.ts +20 -0
  70. package/dist/{chatgpt → simulator}/simulator-url.d.ts +8 -1
  71. package/dist/simulator/simulator.d.ts +12 -0
  72. package/dist/{chatgpt → simulator}/theme-provider.d.ts +3 -1
  73. package/dist/simulator/use-simulator-state.d.ts +91 -0
  74. package/dist/simulator-BqZmzFVR.cjs +8214 -0
  75. package/dist/simulator-BqZmzFVR.cjs.map +1 -0
  76. package/dist/simulator-CMgCGNuD.js +8199 -0
  77. package/dist/simulator-CMgCGNuD.js.map +1 -0
  78. package/dist/simulator-url-BQ-7SMht.js +335 -0
  79. package/dist/simulator-url-BQ-7SMht.js.map +1 -0
  80. package/dist/simulator-url-uNqOCaPJ.cjs +334 -0
  81. package/dist/simulator-url-uNqOCaPJ.cjs.map +1 -0
  82. package/dist/style.css +558 -6143
  83. package/dist/types/runtime.d.ts +1 -1
  84. package/dist/{use-app-D7kRAPSG.cjs → use-app-BnoSPiUT.cjs} +2 -1
  85. package/dist/{use-app-D7kRAPSG.cjs.map → use-app-BnoSPiUT.cjs.map} +1 -1
  86. package/dist/{use-app-Dvr4LKs2.js → use-app-D_TeaMFG.js} +4 -3
  87. package/dist/{use-app-Dvr4LKs2.js.map → use-app-D_TeaMFG.js.map} +1 -1
  88. package/package.json +18 -3
  89. package/template/.sunpeak/dev.tsx +4 -4
  90. package/template/.sunpeak/resource-loader.html +1 -1
  91. package/template/README.md +8 -0
  92. package/template/node_modules/.bin/nodemon +2 -2
  93. package/template/node_modules/.bin/playwright +2 -2
  94. package/template/node_modules/.bin/sunpeak +2 -2
  95. package/template/node_modules/.bin/tsc +2 -2
  96. package/template/node_modules/.bin/tsserver +2 -2
  97. package/template/node_modules/.bin/tsx +2 -2
  98. package/template/node_modules/.bin/vite +2 -2
  99. package/template/src/components/avatar.tsx +4 -1
  100. package/template/src/components/button.tsx +17 -20
  101. package/template/src/resources/albums/albums-resource.tsx +2 -3
  102. package/template/src/resources/albums/components/album-card.tsx +4 -2
  103. package/template/src/resources/albums/components/film-strip.test.tsx +2 -2
  104. package/template/src/resources/albums/components/film-strip.tsx +2 -2
  105. package/template/src/resources/albums/components/fullscreen-viewer.tsx +7 -5
  106. package/template/src/resources/carousel/carousel-resource.tsx +2 -3
  107. package/template/src/resources/carousel/components/card.test.tsx +3 -2
  108. package/template/src/resources/carousel/components/card.tsx +8 -4
  109. package/template/src/resources/map/components/map-view.tsx +1 -1
  110. package/template/src/resources/map/components/map.tsx +1 -1
  111. package/template/src/resources/map/components/place-card.tsx +8 -4
  112. package/template/src/resources/map/components/place-carousel.tsx +1 -1
  113. package/template/src/resources/map/components/place-inspector.tsx +15 -7
  114. package/template/src/resources/map/components/place-list.tsx +7 -4
  115. package/template/src/resources/map/map-resource.tsx +0 -2
  116. package/template/src/resources/review/review-resource.tsx +61 -27
  117. package/template/tests/e2e/albums.spec.ts +118 -102
  118. package/template/tests/e2e/carousel.spec.ts +103 -100
  119. package/template/tests/e2e/map.spec.ts +220 -181
  120. package/template/tests/e2e/review.spec.ts +224 -198
  121. package/dist/_commonjsHelpers-Bc2YnDe1.cjs +0 -8
  122. package/dist/_commonjsHelpers-Bc2YnDe1.cjs.map +0 -1
  123. package/dist/_commonjsHelpers-DWwsNxpa.js +0 -9
  124. package/dist/_commonjsHelpers-DWwsNxpa.js.map +0 -1
  125. package/dist/index-CJ3jfcjj.js +0 -15131
  126. package/dist/index-CJ3jfcjj.js.map +0 -1
  127. package/dist/index-Cdeg96So.cjs +0 -15147
  128. package/dist/index-Cdeg96So.cjs.map +0 -1
  129. /package/dist/{chatgpt → simulator}/simple-sidebar.d.ts +0 -0
  130. /package/dist/{chatgpt/chatgpt-simulator-types.d.ts → simulator/simulator-types.d.ts} +0 -0
package/README.md CHANGED
@@ -1,20 +1,20 @@
1
1
  <div align="center">
2
2
  <a href="https://sunpeak.ai">
3
3
  <picture>
4
- <img alt="Sunpeak logo" src="https://cdn.sunpeak.ai/sunpeak-github.svg">
4
+ <img alt="Sunpeak logo" src="https://cdn.sunpeak.ai/sunpeak-github.png">
5
5
  </picture>
6
6
  </a>
7
7
  </div>
8
8
 
9
9
  # sunpeak
10
10
 
11
- [![npm version](https://img.shields.io/npm/v/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://www.npmjs.com/package/sunpeak)
12
- [![npm downloads](https://img.shields.io/npm/dm/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://www.npmjs.com/package/sunpeak)
13
- [![stars](https://img.shields.io/github/stars/Sunpeak-AI/sunpeak?style=flat&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak)
14
- [![CI](https://img.shields.io/github/actions/workflow/status/Sunpeak-AI/sunpeak/ci.yml?branch=main&style=flat&label=ci&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak/actions)
15
- [![License](https://img.shields.io/npm/l/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak/blob/main/LICENSE)
16
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat&logo=typescript&label=ts&color=FFB800&logoColor=white&labelColor=1A1F36)](https://www.typescriptlang.org/)
17
- [![React](https://img.shields.io/badge/React-19-blue?style=flat&logo=react&label=react&color=FFB800&logoColor=white&labelColor=1A1F36)](https://reactjs.org/)
11
+ [![npm version](https://img.shields.io/npm/v/sunpeak.svg?style=flat&color=FFB800&labelColor=000035)](https://www.npmjs.com/package/sunpeak)
12
+ [![npm downloads](https://img.shields.io/npm/dm/sunpeak.svg?style=flat&color=FFB800&labelColor=000035)](https://www.npmjs.com/package/sunpeak)
13
+ [![stars](https://img.shields.io/github/stars/Sunpeak-AI/sunpeak?style=flat&color=FFB800&labelColor=000035)](https://github.com/Sunpeak-AI/sunpeak)
14
+ [![CI](https://img.shields.io/github/actions/workflow/status/Sunpeak-AI/sunpeak/ci.yml?branch=main&style=flat&label=ci&color=FFB800&labelColor=000035)](https://github.com/Sunpeak-AI/sunpeak/actions)
15
+ [![License](https://img.shields.io/npm/l/sunpeak.svg?style=flat&color=FFB800&labelColor=000035)](https://github.com/Sunpeak-AI/sunpeak/blob/main/LICENSE)
16
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat&logo=typescript&label=ts&color=FFB800&logoColor=white&labelColor=000035)](https://www.typescriptlang.org/)
17
+ [![React](https://img.shields.io/badge/React-19-blue?style=flat&logo=react&label=react&color=FFB800&logoColor=white&labelColor=000035)](https://reactjs.org/)
18
18
 
19
19
  Local-first MCP Apps framework.
20
20
 
@@ -27,9 +27,9 @@ Quickstart, build, test, and ship your Claude or ChatGPT App!
27
27
  [GitHub](https://github.com/Sunpeak-AI/sunpeak)
28
28
 
29
29
  <div align="center">
30
- <a href="https://docs.sunpeak.ai/library/chatgpt-simulator">
30
+ <a href="https://docs.sunpeak.ai/library/simulator">
31
31
  <picture>
32
- <img alt="ChatGPT Simulator" src="https://cdn.sunpeak.ai/chatgpt-simulator.png">
32
+ <img alt="Simulator" src="https://cdn.sunpeak.ai/chatgpt-simulator.png">
33
33
  </picture>
34
34
  </a>
35
35
  </div>
@@ -51,9 +51,9 @@ To add `sunpeak` to an existing project, refer to the [documentation](https://do
51
51
 
52
52
  ### The `sunpeak` library
53
53
 
54
- 1. Runtime APIs: Strongly typed React hooks for interacting with the host runtime (`useApp`, `useToolData`, `useAppState`, `useHostContext`), architected to **support generic and platforms-specific features** (ChatGPT, Claude, etc.).
55
- 2. ChatGPT simulator: React component replicating ChatGPT's runtime to **test Apps locally and automatically** via UI, props, or URL parameters.
56
- 3. MCP server: Serve Resources with mock data to the real ChatGPT with HMR (**no more cache issues or 5-click manual refreshes**).
54
+ 1. Runtime APIs: Strongly typed React hooks for interacting with the host runtime (`useApp`, `useToolData`, `useAppState`, `useHostContext`, `useUpdateModelContext`, `useAppTools`), architected to **support generic and platform-specific features** (ChatGPT, Claude, etc.). Platform-specific hooks like `useUploadFile`, `useRequestModal`, and `useRequestCheckout` are available via `sunpeak/platform/chatgpt`, with `isChatGPT()` / `isClaude()` platform detection via `sunpeak/platform`.
55
+ 2. Multi-host simulator: React component replicating host runtimes (ChatGPT, Claude) to **test Apps locally and automatically** via UI, props, or URL parameters.
56
+ 3. MCP server: Serve Resources with mock data to hosts like ChatGPT and Claude with HMR (**no more cache issues or 5-click manual refreshes**).
57
57
 
58
58
  ### The `sunpeak` framework
59
59
 
@@ -72,10 +72,10 @@ my-app/
72
72
  ```
73
73
 
74
74
  1. Project scaffold: Complete development setup with the `sunpeak` library.
75
- 2. UI components: Production-ready components following ChatGPT design guidelines.
75
+ 2. UI components: Production-ready components following MCP App design guidelines.
76
76
  3. Convention over configuration:
77
77
  1. Create a UI by creating a `-resource.tsx` file that exports a `ResourceConfig` and a React component ([example below](#resource-component)).
78
- 2. Create test state (`Simulation`s) for local dev, ChatGPT dev, automated testing, and demos by creating a `-simulation.json` file. ([example below](#simulation))
78
+ 2. Create test state (`Simulation`s) for local dev, host dev, automated testing, and demos by creating a `-simulation.json` file. ([example below](#simulation))
79
79
 
80
80
  ### The `sunpeak` CLI
81
81
 
@@ -88,7 +88,7 @@ Commands for managing MCP Apps:
88
88
 
89
89
  ## Example App
90
90
 
91
- Example `Resource`, `Simulation`, and testing file (using `ChatGPTSimulator`) for an MCP resource called "Review".
91
+ Example `Resource`, `Simulation`, and testing file (using the `Simulator`) for an MCP resource called "Review".
92
92
 
93
93
  ### `Resource` Component
94
94
 
@@ -110,7 +110,7 @@ import type { ResourceConfig } from 'sunpeak';
110
110
  export const resource: ResourceConfig = {
111
111
  name: 'review',
112
112
  description: 'Visualize and review a code change',
113
- _meta: { ui: { csp: { resourceDomains: ['https://cdn.openai.com'] } } },
113
+ _meta: { ui: { csp: { resourceDomains: ['https://cdn.example.com'] } } },
114
114
  };
115
115
 
116
116
  export function ReviewResource() {
@@ -165,7 +165,7 @@ Testing an MCP App requires setting a lot of state: state in your **backend**, *
165
165
  }
166
166
  ```
167
167
 
168
- ### `ChatGPTSimulator`
168
+ ### `Simulator`
169
169
 
170
170
  ```bash
171
171
  ├── tests/e2e/
@@ -173,38 +173,50 @@ Testing an MCP App requires setting a lot of state: state in your **backend**, *
173
173
  └── package.json
174
174
  ```
175
175
 
176
- The `ChatGPTSimulator` allows you to set **host state** (like light/dark mode) via URL params, which can be rendered alongside your `Simulation`s and tested via pre-configured Playwright end-to-end tests (`.spec.ts`).
176
+ The `Simulator` allows you to set **host state** (like host platform, light/dark mode) via URL params, which can be rendered alongside your `Simulation`s and tested via pre-configured Playwright end-to-end tests (`.spec.ts`).
177
177
 
178
- Using the `ChatGPTSimulator` and `Simulation`s, you can test all possible App states locally and automatically!
178
+ Using the `Simulator` and `Simulation`s, you can test all possible App states locally and automatically across hosts (ChatGPT, Claude)!
179
179
 
180
180
  ```ts
181
181
  // tests/e2e/review.spec.ts
182
182
 
183
183
  import { test, expect } from '@playwright/test';
184
- import { createSimulatorUrl } from 'sunpeak/chatgpt';
184
+ import { createSimulatorUrl } from 'sunpeak/simulator';
185
185
 
186
- test.describe('Review Resource', () => {
187
- test.describe('Light Mode', () => {
188
- test('should render review title with correct styles', async ({ page }) => {
189
- const params = { simulation: 'review-diff', theme: 'light' }; // Set sim & host state.
190
- await page.goto(createSimulatorUrl(params));
186
+ const hosts = ['chatgpt', 'claude'] as const;
191
187
 
192
- // Resource content renders inside an iframe
193
- const iframe = page.frameLocator('iframe');
194
- const title = iframe.locator('h1:has-text("Refactor Authentication Module")');
195
- await expect(title).toBeVisible();
188
+ for (const host of hosts) {
189
+ test.describe(`Review Resource [${host}]`, () => {
190
+ test.describe('Light Mode', () => {
191
+ test('should render review title with correct styles', async ({ page }) => {
192
+ const params = { simulation: 'review-diff', theme: 'light', host }; // Set sim & host state.
193
+ await page.goto(createSimulatorUrl(params));
196
194
 
197
- const color = await title.evaluate((el) => window.getComputedStyle(el).color);
195
+ // Resource content renders inside an iframe
196
+ const iframe = page.frameLocator('iframe');
197
+ const title = iframe.locator('h1:has-text("Refactor Authentication Module")');
198
+ await expect(title).toBeVisible();
198
199
 
199
- // Light mode should render dark text.
200
- expect(color).toBe('rgb(13, 13, 13)');
200
+ const color = await title.evaluate((el) => window.getComputedStyle(el).color);
201
+
202
+ // Light mode should render dark text.
203
+ expect(color).toBe('rgb(13, 13, 13)');
204
+ });
201
205
  });
202
206
  });
203
- });
207
+ }
208
+ ```
209
+
210
+ ## Coding Agent Skill
211
+
212
+ Install the `create-sunpeak-app` skill to give your coding agent (Claude Code, Cursor, etc.) built-in knowledge of sunpeak patterns, hooks, simulation files, and testing conventions:
213
+
214
+ ```bash
215
+ npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app
204
216
  ```
205
217
 
206
218
  ## Resources
207
219
 
208
220
  - [MCP Apps](https://github.com/modelcontextprotocol/ext-apps)
209
- - [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
210
- - [ChatGPT Apps SDK Documentation - UI](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
221
+ - [MCP Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
222
+ - [MCP Apps SDK Documentation - UI](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
@@ -225,6 +225,7 @@ export async function build(projectRoot = process.cwd()) {
225
225
 
226
226
  // Build with vite programmatically
227
227
  await viteBuild({
228
+ mode: 'production',
228
229
  root: projectRoot,
229
230
  plugins: [react(), tailwindcss(), inlineCssPlugin(buildOutDir)],
230
231
  define: {
@@ -261,7 +262,7 @@ export async function build(projectRoot = process.cwd()) {
261
262
  },
262
263
  });
263
264
  } catch (error) {
264
- console.error(`Failed to build ${output}`);
265
+ console.error(`Failed to build ${kebabName}`);
265
266
  console.error(error);
266
267
  process.exit(1);
267
268
  }
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- const { existsSync, readFileSync } = fs;
4
+ const { existsSync, readFileSync, watch: fsWatch } = fs;
5
5
  const { join, resolve, basename, dirname } = path;
6
6
  import { createRequire } from 'module';
7
7
  import { pathToFileURL } from 'url';
8
+ import { spawn } from 'child_process';
8
9
 
9
10
  /**
10
11
  * Import a module from the project's node_modules using ESM resolution
@@ -54,6 +55,68 @@ async function importFromProject(require, moduleName) {
54
55
  return import(pathToFileURL(entryPath).href);
55
56
  }
56
57
 
58
+ /**
59
+ * Run an initial production build and watch source files for changes.
60
+ * Tunnel clients (e.g. Claude via ngrok) are served the pre-built HTML since they
61
+ * can't reach the local Vite dev server. This keeps the prod output up to date.
62
+ *
63
+ * When a file changes during a build, the current build is killed and restarted.
64
+ */
65
+ function startBuildWatcher(projectRoot, resourcesDir, mcpHandle) {
66
+ let activeChild = null;
67
+ const sunpeakBin = join(dirname(new URL(import.meta.url).pathname), '..', 'sunpeak.js');
68
+
69
+ const runBuild = (reason) => {
70
+ // Kill any in-progress build and start fresh
71
+ if (activeChild) {
72
+ activeChild.kill('SIGTERM');
73
+ activeChild = null;
74
+ }
75
+
76
+ console.log(`[build] ${reason}`);
77
+ const child = spawn(process.execPath, [sunpeakBin, 'build'], {
78
+ cwd: projectRoot,
79
+ stdio: ['ignore', 'inherit', 'inherit'],
80
+ env: { ...process.env, NODE_ENV: 'production' },
81
+ });
82
+ activeChild = child;
83
+
84
+ child.on('exit', (code) => {
85
+ if (child !== activeChild) return; // Superseded by a newer build
86
+ activeChild = null;
87
+ if (code === 0) {
88
+ // Notify non-local sessions (Claude, etc.) that resources changed
89
+ mcpHandle?.invalidateResources();
90
+ } else if (code !== null) {
91
+ console.error(`[build] Failed (exit ${code})`);
92
+ }
93
+ });
94
+ };
95
+
96
+ // Initial build
97
+ runBuild('Initial production build for tunnel clients...');
98
+
99
+ // Watch src/resources/ for changes using fs.watch (recursive supported on macOS/Windows)
100
+ let debounceTimer = null;
101
+ try {
102
+ fsWatch(resourcesDir, { recursive: true }, (_event, filename) => {
103
+ if (!filename) return;
104
+ // Only rebuild on source file changes
105
+ if (!/\.(tsx?|css)$/.test(filename)) return;
106
+ // Skip test files
107
+ if (/\.(test|spec)\.tsx?$/.test(filename)) return;
108
+
109
+ clearTimeout(debounceTimer);
110
+ debounceTimer = setTimeout(() => {
111
+ runBuild(`Rebuilding (${filename} changed)...`);
112
+ }, 500);
113
+ });
114
+ console.log('[build] Watching src/resources/ for changes...');
115
+ } catch {
116
+ console.warn('[build] Could not start file watcher — run "sunpeak build" manually after changes');
117
+ }
118
+ }
119
+
57
120
  /**
58
121
  * Start the Vite development server
59
122
  * Runs in the context of a user's project directory
@@ -281,7 +344,7 @@ if (import.meta.hot) {
281
344
  }
282
345
 
283
346
  const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
284
- runMCPServer({
347
+ const mcpHandle = runMCPServer({
285
348
  name: pkg.name || 'Sunpeak',
286
349
  version: pkg.version || '0.1.0',
287
350
  simulations,
@@ -289,6 +352,13 @@ if (import.meta.hot) {
289
352
  ...(mcpViteServer && { viteServer: mcpViteServer }),
290
353
  });
291
354
 
355
+ // Build production bundles and watch for changes.
356
+ // Tunnel clients (e.g. Claude via ngrok) get the pre-built HTML since they can't
357
+ // reach the local Vite dev server. The watcher rebuilds on source file changes
358
+ // so the prod output stays fresh without manual `sunpeak build`.
359
+ // On successful builds, mcpHandle.invalidateResources() notifies tunnel sessions.
360
+ startBuildWatcher(projectRoot, resourcesDir, mcpHandle);
361
+
292
362
  // Handle signals - close both servers
293
363
  process.on('SIGINT', async () => {
294
364
  if (mcpViteServer) await mcpViteServer.close();
@@ -1,4 +1,4 @@
1
- import { ScreenWidth } from './chatgpt-simulator-types';
1
+ import { ScreenWidth } from '../simulator/simulator-types';
2
2
  import { McpUiDisplayMode, McpUiHostContext } from '@modelcontextprotocol/ext-apps';
3
3
  import * as React from 'react';
4
4
  type Platform = NonNullable<McpUiHostContext['platform']>;
@@ -0,0 +1 @@
1
+ export {};