sunpeak 0.16.24 → 0.16.28

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 (116) hide show
  1. package/bin/commands/build.mjs +0 -1
  2. package/bin/commands/dev.mjs +98 -5
  3. package/bin/commands/start.mjs +3 -2
  4. package/bin/lib/live/browser-auth.mjs +53 -17
  5. package/bin/lib/live/global-setup.mjs +107 -99
  6. package/bin/lib/live/host-page.mjs +63 -11
  7. package/bin/lib/live/live-config.mjs +1 -1
  8. package/bin/lib/sandbox-server.mjs +304 -0
  9. package/dist/chatgpt/chatgpt-conversation.d.ts +3 -7
  10. package/dist/chatgpt/globals.css +28 -9
  11. package/dist/chatgpt/index.cjs +55 -24
  12. package/dist/chatgpt/index.cjs.map +1 -1
  13. package/dist/chatgpt/index.js +31 -25
  14. package/dist/chatgpt/index.js.map +1 -1
  15. package/dist/chunk-9hOWP6kD.cjs +64 -0
  16. package/dist/chunk-D6g4UhsZ.js +35 -0
  17. package/dist/claude/claude-conversation.d.ts +3 -2
  18. package/dist/claude/index.cjs +4 -4
  19. package/dist/claude/index.js +3 -5
  20. package/dist/discovery-BxKCIgG5.cjs +332 -0
  21. package/dist/discovery-BxKCIgG5.cjs.map +1 -0
  22. package/dist/discovery-Du4LHrih.js +261 -0
  23. package/dist/discovery-Du4LHrih.js.map +1 -0
  24. package/dist/host/chatgpt/index.cjs +171 -65
  25. package/dist/host/chatgpt/index.cjs.map +1 -1
  26. package/dist/host/chatgpt/index.js +170 -70
  27. package/dist/host/chatgpt/index.js.map +1 -1
  28. package/dist/host/index.cjs +47 -19
  29. package/dist/host/index.cjs.map +1 -1
  30. package/dist/host/index.js +47 -24
  31. package/dist/host/index.js.map +1 -1
  32. package/dist/index.cjs +3103 -3725
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js +3026 -3746
  35. package/dist/index.js.map +1 -1
  36. package/dist/lib/discovery-cli.cjs +117 -131
  37. package/dist/lib/discovery-cli.cjs.map +1 -1
  38. package/dist/lib/discovery-cli.js +107 -111
  39. package/dist/lib/discovery-cli.js.map +1 -1
  40. package/dist/mcp/favicon.d.ts +3 -1
  41. package/dist/mcp/index.cjs +9821 -10270
  42. package/dist/mcp/index.cjs.map +1 -1
  43. package/dist/mcp/index.d.ts +2 -2
  44. package/dist/mcp/index.js +9801 -10268
  45. package/dist/mcp/index.js.map +1 -1
  46. package/dist/mcp/production-server.d.ts +7 -1
  47. package/dist/mcp/types.d.ts +30 -1
  48. package/dist/protocol-DJmRaBzO.js +11080 -0
  49. package/dist/{protocol-DkDHRwOW.cjs.map → protocol-DJmRaBzO.js.map} +1 -1
  50. package/dist/protocol-jbxhzcnS.cjs +11493 -0
  51. package/dist/protocol-jbxhzcnS.cjs.map +1 -0
  52. package/dist/simulator/hosts.d.ts +11 -2
  53. package/dist/simulator/iframe-resource.d.ts +8 -1
  54. package/dist/simulator/index.cjs +79 -36
  55. package/dist/simulator/index.cjs.map +1 -1
  56. package/dist/simulator/index.js +43 -37
  57. package/dist/simulator/index.js.map +1 -1
  58. package/dist/simulator/mcp-app-host.d.ts +17 -0
  59. package/dist/simulator/sandbox-proxy.d.ts +38 -0
  60. package/dist/simulator/simulator.d.ts +7 -1
  61. package/dist/simulator/use-simulator-state.d.ts +2 -4
  62. package/dist/simulator-BYIH-xqQ.cjs +3701 -0
  63. package/dist/simulator-BYIH-xqQ.cjs.map +1 -0
  64. package/dist/simulator-CmgNnWBO.js +3575 -0
  65. package/dist/simulator-CmgNnWBO.js.map +1 -0
  66. package/dist/simulator-url-BDGD4vZD.cjs +69 -0
  67. package/dist/simulator-url-BDGD4vZD.cjs.map +1 -0
  68. package/dist/simulator-url-Bkxj43yT.js +64 -0
  69. package/dist/simulator-url-Bkxj43yT.js.map +1 -0
  70. package/dist/style.css +28 -9
  71. package/dist/use-app-D2h-aiyr.cjs +940 -0
  72. package/dist/use-app-D2h-aiyr.cjs.map +1 -0
  73. package/dist/use-app-X7JbGskk.js +598 -0
  74. package/dist/use-app-X7JbGskk.js.map +1 -0
  75. package/package.json +8 -8
  76. package/template/.sunpeak/dev.tsx +9 -3
  77. package/template/node_modules/.bin/vite +2 -2
  78. package/template/node_modules/.bin/vitest +2 -2
  79. package/template/package.json +5 -5
  80. package/template/playwright.config.ts +10 -5
  81. package/template/src/server.ts +16 -2
  82. package/template/src/tools/show-albums.ts +17 -0
  83. package/template/tests/e2e/albums.spec.ts +37 -5
  84. package/template/tests/e2e/carousel.spec.ts +6 -6
  85. package/template/tests/e2e/global-setup.ts +6 -21
  86. package/template/tests/e2e/map.spec.ts +11 -11
  87. package/template/tests/e2e/review.spec.ts +24 -24
  88. package/dist/claude/index.cjs.map +0 -1
  89. package/dist/claude/index.js.map +0 -1
  90. package/dist/discovery-BVqD-JsT.js +0 -224
  91. package/dist/discovery-BVqD-JsT.js.map +0 -1
  92. package/dist/discovery-D1gpaVz4.cjs +0 -223
  93. package/dist/discovery-D1gpaVz4.cjs.map +0 -1
  94. package/dist/index-B7Qw3Vhh.js +0 -29
  95. package/dist/index-B7Qw3Vhh.js.map +0 -1
  96. package/dist/index-BEHP_bM8.js +0 -41
  97. package/dist/index-BEHP_bM8.js.map +0 -1
  98. package/dist/index-SfudQ9Y_.cjs +0 -28
  99. package/dist/index-SfudQ9Y_.cjs.map +0 -1
  100. package/dist/index-XKHXfBiD.cjs +0 -40
  101. package/dist/index-XKHXfBiD.cjs.map +0 -1
  102. package/dist/protocol-DkDHRwOW.cjs +0 -12221
  103. package/dist/protocol-uge7qFev.js +0 -12223
  104. package/dist/protocol-uge7qFev.js.map +0 -1
  105. package/dist/simulator-BCq2iOT-.js +0 -3262
  106. package/dist/simulator-BCq2iOT-.js.map +0 -1
  107. package/dist/simulator-DRUsm6IZ.cjs +0 -3277
  108. package/dist/simulator-DRUsm6IZ.cjs.map +0 -1
  109. package/dist/simulator-url-DcSYRl-P.cjs +0 -53
  110. package/dist/simulator-url-DcSYRl-P.cjs.map +0 -1
  111. package/dist/simulator-url-j_XV3EoP.js +0 -54
  112. package/dist/simulator-url-j_XV3EoP.js.map +0 -1
  113. package/dist/use-app-C9gpzIQO.js +0 -349
  114. package/dist/use-app-C9gpzIQO.js.map +0 -1
  115. package/dist/use-app-D09O2swh.cjs +0 -348
  116. package/dist/use-app-D09O2swh.cjs.map +0 -1
@@ -87,9 +87,38 @@ export class HostPage {
87
87
  return warnings.length === 0;
88
88
  }
89
89
 
90
+ /**
91
+ * Check if truly logged in: profile button visible AND no "Log in" buttons.
92
+ * The logged-out page can show UI elements that look like a logged-in state
93
+ * (e.g., sidebar with profile-like elements), so checking just the profile
94
+ * button isn't enough.
95
+ */
96
+ async _isFullyLoggedIn() {
97
+ const hasProfile = await this.page
98
+ .locator(this.selectors.loggedInIndicator)
99
+ .first()
100
+ .isVisible()
101
+ .catch(() => false);
102
+
103
+ if (!hasProfile) return false;
104
+
105
+ const hasLoginButton = await this.page
106
+ .locator(this.selectors.loginPage)
107
+ .first()
108
+ .isVisible()
109
+ .catch(() => false);
110
+
111
+ return !hasLoginButton;
112
+ }
113
+
90
114
  /**
91
115
  * Verify the user is logged into the host.
92
116
  * Navigates to the host if not already there.
117
+ *
118
+ * If not logged in, waits up to 3 minutes for the user to complete login
119
+ * in the open browser window, polling every 5 seconds. This handles the
120
+ * case where storageState doesn't capture Cloudflare's HttpOnly cookies
121
+ * and the browser needs a fresh login.
93
122
  */
94
123
  async verifyLoggedIn() {
95
124
  const url = this.page.url();
@@ -97,20 +126,43 @@ export class HostPage {
97
126
  await this.page.goto(this.urls.base, { waitUntil: 'domcontentloaded' });
98
127
  }
99
128
 
100
- const loggedIn = this.page.locator(this.selectors.loggedInIndicator).first();
101
- const loginPage = this.page.locator(this.selectors.loginPage);
129
+ // Wait for the page to settle (Cloudflare challenge or UI loading)
130
+ await this.page.waitForTimeout(5_000);
102
131
 
103
- const result = await Promise.race([
104
- loggedIn.waitFor({ timeout: 15_000 }).then(() => 'logged-in'),
105
- loginPage.waitFor({ timeout: 15_000 }).then(() => 'login-page'),
106
- ]).catch(() => 'timeout');
132
+ // Quick check: truly logged in? (profile button AND no "Log in" buttons)
133
+ if (await this._isFullyLoggedIn()) return;
107
134
 
108
- if (result !== 'logged-in') {
109
- throw new Error(
110
- `Not logged into ${this.hostName}. Run \`pnpm test:live\` to open a browser and log in.\n` +
111
- 'Your session is saved for 24 hours after the first login.'
112
- );
135
+ // Not logged in. Wait for the user to authenticate in this browser window.
136
+ console.log(
137
+ `\n` +
138
+ `╔══════════════════════════════════════════════════════════════╗\n` +
139
+ `║ Not logged into ${this.hostName.padEnd(42)}║\n` +
140
+ `║ ║\n` +
141
+ `║ Please log in at: ${this.urls.base.padEnd(39)}║\n` +
142
+ `║ in the browser window that just opened. ║\n` +
143
+ `║ ║\n` +
144
+ `║ Waiting up to 3 minutes... ║\n` +
145
+ `╚══════════════════════════════════════════════════════════════╝\n`
146
+ );
147
+
148
+ // Poll for login — the user may need to pass Cloudflare + enter credentials
149
+ const maxWait = 180_000; // 3 minutes
150
+ const pollInterval = 5_000;
151
+ const start = Date.now();
152
+
153
+ while (Date.now() - start < maxWait) {
154
+ if (await this._isFullyLoggedIn()) {
155
+ console.log(`Logged into ${this.hostName}!\n`);
156
+ return;
157
+ }
158
+ await this.page.waitForTimeout(pollInterval);
113
159
  }
160
+
161
+ throw new Error(
162
+ `Login to ${this.hostName} timed out after 3 minutes.\n` +
163
+ `Please log in at ${this.urls.base} in the browser window and try again.\n` +
164
+ 'If the session expired, delete the .auth/ directory and try again.'
165
+ );
114
166
  }
115
167
 
116
168
  /**
@@ -89,7 +89,7 @@ export function createLiveConfig(hostOptions, options = {}) {
89
89
  },
90
90
  ],
91
91
  webServer: {
92
- command: `SUNPEAK_LIVE_TEST=1 pnpm dev -- --prod-resources --port ${vitePort}`,
92
+ command: `SUNPEAK_LIVE_TEST=1 SUNPEAK_SANDBOX_PORT=${getPortSync(24680)} pnpm dev -- --prod-resources --port ${vitePort}`,
93
93
  url: `http://localhost:${vitePort}/health`,
94
94
  reuseExistingServer: !process.env.CI,
95
95
  timeout: 60_000,
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Separate-origin sandbox server for the simulator's double-iframe architecture.
3
+ *
4
+ * Real hosts (ChatGPT, Claude) run the sandbox proxy iframe on a separate origin
5
+ * (e.g., web-sandbox.oaiusercontent.com). This server replicates that by serving
6
+ * the proxy HTML on a different localhost port, giving real origin isolation.
7
+ *
8
+ * This means:
9
+ * - window.top.location access from inside the iframe is blocked (cross-origin)
10
+ * - document.referrer is empty (cross-origin navigation)
11
+ * - The iframe sandbox attribute behaves identically to production
12
+ *
13
+ * The server is started by `sunpeak dev` and its URL is injected into the Simulator
14
+ * via `__SUNPEAK_SANDBOX_URL__`.
15
+ *
16
+ * NOTE: The proxy HTML and mock openai script are duplicated from sandbox-proxy.ts
17
+ * and mock-openai-runtime.ts because this file runs in Node.js at dev time and
18
+ * cannot import TypeScript modules. Keep them in sync when making changes.
19
+ */
20
+ import { createServer } from 'http';
21
+ import { getPort } from './get-port.mjs';
22
+
23
+ /**
24
+ * Start the sandbox proxy server on a separate port.
25
+ *
26
+ * @param {Object} options
27
+ * @param {number} [options.preferredPort=24680] - Port to try first
28
+ * @returns {Promise<{ url: string, port: number, close: () => Promise<void> }>}
29
+ */
30
+ export async function startSandboxServer({ preferredPort = 24680 } = {}) {
31
+ const port = await getPort(preferredPort);
32
+
33
+ const server = createServer((req, res) => {
34
+ const url = new URL(req.url, `http://localhost:${port}`);
35
+
36
+ if (url.pathname === '/proxy') {
37
+ const theme = url.searchParams.get('theme') || 'dark';
38
+ const platform = url.searchParams.get('platform') || '';
39
+ const html = generateProxyHtml(theme, platform);
40
+
41
+ res.writeHead(200, {
42
+ 'Content-Type': 'text/html; charset=utf-8',
43
+ 'Cache-Control': 'no-cache',
44
+ // No CORS headers needed — iframe src loads don't check CORS.
45
+ // PostMessage works cross-origin by design.
46
+ });
47
+ res.end(html);
48
+ return;
49
+ }
50
+
51
+ // Health check
52
+ if (url.pathname === '/health') {
53
+ res.writeHead(200, { 'Content-Type': 'application/json' });
54
+ res.end('{"status":"ok"}');
55
+ return;
56
+ }
57
+
58
+ res.writeHead(404);
59
+ res.end('Not found');
60
+ });
61
+
62
+ await new Promise((resolve, reject) => {
63
+ server.listen(port, () => resolve());
64
+ server.on('error', reject);
65
+ });
66
+
67
+ const sandboxUrl = `http://localhost:${port}`;
68
+
69
+ return {
70
+ url: sandboxUrl,
71
+ port,
72
+ close: () => new Promise((resolve) => server.close(resolve)),
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Generate the sandbox proxy HTML.
78
+ *
79
+ * This is the same proxy logic as sandbox-proxy.ts but as a plain string
80
+ * (no TypeScript imports needed at dev server runtime). The proxy:
81
+ * 1. Signals readiness via `ui/notifications/sandbox-proxy-ready`
82
+ * 2. Listens for resource content or URL to load into the inner iframe
83
+ * 3. Relays all PostMessage between parent and inner iframe
84
+ * 4. Optionally injects platform runtime scripts (e.g., mock window.openai)
85
+ */
86
+ function generateProxyHtml(theme, platform) {
87
+ const colorScheme = theme === 'light' ? 'light' : 'dark';
88
+
89
+ // Platform-specific runtime script (injected into the inner iframe)
90
+ let platformScript = 'null';
91
+ if (platform === 'chatgpt') {
92
+ platformScript = JSON.stringify(MOCK_OPENAI_SCRIPT);
93
+ }
94
+
95
+ return `<!DOCTYPE html>
96
+ <html style="color-scheme:${colorScheme}">
97
+ <head>
98
+ <meta name="color-scheme" content="${colorScheme}" />
99
+ <style>
100
+ html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
101
+ iframe { border: none; width: 100%; height: 100%; display: block; }
102
+ </style>
103
+ </head>
104
+ <body>
105
+ <script>
106
+ (function() {
107
+ var innerFrame = null;
108
+ var innerWindow = null;
109
+ var platformScript = ${platformScript};
110
+
111
+ // Relay messages between parent (host) and inner iframe (app)
112
+ window.addEventListener('message', function(event) {
113
+ var data = event.data;
114
+ if (!data || typeof data !== 'object') return;
115
+
116
+ if (event.source === window.parent) {
117
+ // sandbox-resource-ready: load HTML into inner iframe (scriptSrc/prod mode)
118
+ if (data.method === 'ui/notifications/sandbox-resource-ready' && data.params) {
119
+ createInnerFrame(data.params);
120
+ return;
121
+ }
122
+
123
+ // sunpeak/sandbox-load-src: load URL into inner iframe (src/dev mode)
124
+ if (data.method === 'sunpeak/sandbox-load-src' && data.params) {
125
+ createInnerFrameWithSrc(data.params);
126
+ return;
127
+ }
128
+
129
+ // Handle paint fence. Forward to the inner iframe and wait for its ack.
130
+ // The inner iframe has a fence responder (injected by the Vite dev page
131
+ // or embedded in the production HTML). If the ack arrives, relay it to
132
+ // the host. If not (cross-origin injection failed), fall back to a
133
+ // timeout-based ack after 150ms.
134
+ if (data.method === 'sunpeak/fence' && data.params) {
135
+ var fenceId = data.params.fenceId;
136
+ var acked = false;
137
+
138
+ // Listen for the inner iframe's fence-ack
139
+ var onAck = function(e) {
140
+ if (e.source !== innerWindow) return;
141
+ if (e.data && e.data.method === 'sunpeak/fence-ack' &&
142
+ e.data.params && e.data.params.fenceId === fenceId) {
143
+ acked = true;
144
+ window.removeEventListener('message', onAck);
145
+ window.parent.postMessage(e.data, '*');
146
+ }
147
+ };
148
+ window.addEventListener('message', onAck);
149
+
150
+ // Forward fence to inner iframe
151
+ if (innerWindow) {
152
+ try { innerWindow.postMessage(data, '*'); } catch(e) {}
153
+ }
154
+
155
+ // Fallback: if no ack within 150ms (fence responder not available),
156
+ // ack from the proxy after allowing time for the inner iframe to
157
+ // process the preceding hostContext change.
158
+ setTimeout(function() {
159
+ if (!acked) {
160
+ window.removeEventListener('message', onAck);
161
+ window.parent.postMessage({
162
+ jsonrpc: '2.0',
163
+ method: 'sunpeak/fence-ack',
164
+ params: { fenceId: fenceId }
165
+ }, '*');
166
+ }
167
+ }, 150);
168
+ return;
169
+ }
170
+
171
+ // Forward all other messages to the inner iframe
172
+ if (innerWindow) {
173
+ try { innerWindow.postMessage(data, '*'); } catch(e) { /* detached */ }
174
+ }
175
+ } else if (innerWindow && event.source === innerWindow) {
176
+ // Messages from the app -> forward to host
177
+ try { window.parent.postMessage(data, '*'); } catch(e) { /* detached */ }
178
+ }
179
+ });
180
+
181
+ function createInnerFrame(params) {
182
+ if (innerFrame) innerFrame.remove();
183
+
184
+ innerFrame = document.createElement('iframe');
185
+ innerFrame.sandbox = params.sandbox ||
186
+ 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
187
+ if (params.allow) innerFrame.allow = params.allow;
188
+ document.body.appendChild(innerFrame);
189
+ innerWindow = innerFrame.contentWindow;
190
+
191
+ var doc = innerFrame.contentDocument;
192
+ if (doc && params.html) {
193
+ doc.open();
194
+ doc.write(params.html);
195
+ doc.close();
196
+ }
197
+ }
198
+
199
+ function createInnerFrameWithSrc(params) {
200
+ if (innerFrame) innerFrame.remove();
201
+
202
+ innerFrame = document.createElement('iframe');
203
+ innerFrame.sandbox =
204
+ 'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox';
205
+ if (params.allow) innerFrame.allow = params.allow;
206
+ innerFrame.src = params.src;
207
+ innerFrame.style.height = '100%';
208
+
209
+ if (params.theme) {
210
+ innerFrame.style.colorScheme = params.theme;
211
+ }
212
+
213
+ innerFrame.addEventListener('load', function() {
214
+ innerWindow = innerFrame.contentWindow;
215
+
216
+ // Inject platform runtime (e.g., mock window.openai for ChatGPT)
217
+ if (platformScript && innerWindow) {
218
+ try {
219
+ var pScript = innerFrame.contentDocument.createElement('script');
220
+ pScript.textContent = platformScript;
221
+ innerFrame.contentDocument.head.appendChild(pScript);
222
+ } catch(e) { /* cross-origin */ }
223
+ }
224
+
225
+ // Inject paint fence responder
226
+ try {
227
+ var fenceScript = innerFrame.contentDocument.createElement('script');
228
+ fenceScript.setAttribute('data-sunpeak-fence', '');
229
+ fenceScript.textContent = PAINT_FENCE_SCRIPT;
230
+ innerFrame.contentDocument.head.appendChild(fenceScript);
231
+ } catch(e) { /* cross-origin */ }
232
+
233
+ // Inject background rule
234
+ if (params.theme) {
235
+ try {
236
+ innerFrame.contentDocument.documentElement.style.colorScheme = params.theme;
237
+ var bgStyle = innerFrame.contentDocument.createElement('style');
238
+ bgStyle.setAttribute('data-sunpeak-bg', '');
239
+ bgStyle.textContent = 'html { background-color: var(--color-background-primary, Canvas); }';
240
+ innerFrame.contentDocument.head.appendChild(bgStyle);
241
+ } catch(e) { /* cross-origin */ }
242
+ }
243
+
244
+ // Inject style variables
245
+ if (params.styleVars) {
246
+ try {
247
+ var root = innerFrame.contentDocument.documentElement;
248
+ for (var key in params.styleVars) {
249
+ if (params.styleVars[key]) root.style.setProperty(key, params.styleVars[key]);
250
+ }
251
+ } catch(e) { /* cross-origin */ }
252
+ }
253
+
254
+ innerFrame.style.opacity = '1';
255
+ innerFrame.style.transition = 'opacity 100ms';
256
+ });
257
+
258
+ innerFrame.style.opacity = '0';
259
+ document.body.appendChild(innerFrame);
260
+ innerWindow = innerFrame.contentWindow;
261
+ }
262
+
263
+ var PAINT_FENCE_SCRIPT = 'window.addEventListener("message",function(e){' +
264
+ 'if(e.data&&e.data.method==="sunpeak/fence"){' +
265
+ 'var fid=e.data.params&&e.data.params.fenceId;' +
266
+ 'requestAnimationFrame(function(){' +
267
+ 'e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");' +
268
+ '});}});';
269
+
270
+ // Signal readiness to the host
271
+ setTimeout(function() {
272
+ window.parent.postMessage({
273
+ jsonrpc: '2.0',
274
+ method: 'ui/notifications/sandbox-proxy-ready',
275
+ params: {}
276
+ }, '*');
277
+ }, 0);
278
+ })();
279
+ </script>
280
+ </body>
281
+ </html>`;
282
+ }
283
+
284
+ /**
285
+ * Mock OpenAI runtime script — same as mock-openai-runtime.ts MOCK_OPENAI_RUNTIME_SCRIPT.
286
+ * Duplicated here to avoid TypeScript import in the Node.js dev server.
287
+ */
288
+ const MOCK_OPENAI_SCRIPT = [
289
+ 'window.openai={',
290
+ 'uploadFile:function(f){console.log("[Simulator] uploadFile:",f.name);',
291
+ 'return Promise.resolve({fileId:"sim_file_"+Date.now()})},',
292
+ 'getFileDownloadUrl:function(p){console.log("[Simulator] getFileDownloadUrl:",p.fileId);',
293
+ 'return Promise.resolve({downloadUrl:"https://simulator.local/files/"+p.fileId})},',
294
+ 'requestModal:function(p){console.log("[Simulator] requestModal:",JSON.stringify(p));',
295
+ 'return Promise.resolve()},',
296
+ 'requestCheckout:function(s){console.log("[Simulator] requestCheckout:",JSON.stringify(s));',
297
+ 'return Promise.resolve({id:"sim_order_"+Date.now(),checkout_session_id:s.id||"sim_session",status:"completed"})},',
298
+ 'requestClose:function(){console.log("[Simulator] requestClose")},',
299
+ 'requestDisplayMode:function(p){console.log("[Simulator] requestDisplayMode:",p.mode);',
300
+ 'return Promise.resolve()},',
301
+ 'sendFollowUpMessage:function(p){console.log("[Simulator] sendFollowUpMessage:",p.prompt)},',
302
+ 'openExternal:function(p){console.log("[Simulator] openExternal:",p.href);window.open(p.href,"_blank")}',
303
+ '};',
304
+ ].join('');
@@ -11,14 +11,10 @@ interface ConversationProps {
11
11
  appName?: string;
12
12
  appIcon?: string;
13
13
  userMessage?: string;
14
- /**
15
- * Whether the content is transitioning between display modes.
16
- * When true, the content area is hidden (opacity 0) to prevent the pip
17
- * border from flashing at a stale height before the iframe resizes.
18
- */
19
- isTransitioning?: boolean;
20
14
  /** Optional action element rendered in the conversation header (e.g., Run button) */
21
15
  headerAction?: React.ReactNode;
16
+ /** Called when the content container width changes */
17
+ onContentWidthChange?: (width: number) => void;
22
18
  }
23
19
  /**
24
20
  * Conversation layout that renders children (iframe) at a stable tree position.
@@ -33,5 +29,5 @@ interface ConversationProps {
33
29
  * - **fullscreen**: content wrapper becomes `position: fixed` covering the viewport;
34
30
  * fullscreen chrome (header/footer) rendered as a separate fixed overlay
35
31
  */
36
- export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, headerAction, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, headerAction, onContentWidthChange, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
37
33
  export {};
@@ -83,7 +83,7 @@
83
83
  }
84
84
 
85
85
  /* Bundled component styles */
86
- /*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
86
+ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
87
87
  @layer properties {
88
88
  @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
89
89
  *, :before, :after, ::backdrop {
@@ -151,6 +151,7 @@
151
151
  --tw-backdrop-saturate: initial;
152
152
  --tw-backdrop-sepia: initial;
153
153
  --tw-duration: initial;
154
+ --tw-ease: initial;
154
155
  }
155
156
  }
156
157
  }
@@ -191,6 +192,7 @@
191
192
  --shadow-sm: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
192
193
  --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
193
194
  --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
195
+ --ease-out: cubic-bezier(0, 0, .2, 1);
194
196
  --animate-spin: spin 1s linear infinite;
195
197
  --blur-sm: 8px;
196
198
  --default-transition-duration: .15s;
@@ -1010,7 +1012,7 @@
1010
1012
  }
1011
1013
 
1012
1014
  .transform {
1013
- transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
1015
+ transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
1014
1016
  }
1015
1017
 
1016
1018
  .animate-spin {
@@ -1027,7 +1029,7 @@
1027
1029
 
1028
1030
  .touch-pan-y {
1029
1031
  --tw-pan-y: pan-y;
1030
- touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
1032
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
1031
1033
  }
1032
1034
 
1033
1035
  .resize {
@@ -1779,7 +1781,7 @@
1779
1781
  }
1780
1782
 
1781
1783
  .ring {
1782
- --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1784
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1783
1785
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1784
1786
  }
1785
1787
 
@@ -1800,17 +1802,17 @@
1800
1802
 
1801
1803
  .blur {
1802
1804
  --tw-blur: blur(8px);
1803
- filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
1805
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
1804
1806
  }
1805
1807
 
1806
1808
  .filter {
1807
- filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
1809
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
1808
1810
  }
1809
1811
 
1810
1812
  .backdrop-blur-sm {
1811
1813
  --tw-backdrop-blur: blur(var(--blur-sm));
1812
- -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
1813
- backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
1814
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
1815
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
1814
1816
  }
1815
1817
 
1816
1818
  .transition {
@@ -1847,6 +1849,11 @@
1847
1849
  transition-duration: .2s;
1848
1850
  }
1849
1851
 
1852
+ .ease-out {
1853
+ --tw-ease: var(--ease-out);
1854
+ transition-timing-function: var(--ease-out);
1855
+ }
1856
+
1850
1857
  .outline-none {
1851
1858
  --tw-outline-style: none;
1852
1859
  outline-style: none;
@@ -2037,6 +2044,12 @@
2037
2044
  }
2038
2045
  }
2039
2046
 
2047
+ @media (min-width: 1440px) {
2048
+ .min-\[1440px\]\:max-w-\[48rem\] {
2049
+ max-width: 48rem;
2050
+ }
2051
+ }
2052
+
2040
2053
  @media (min-width: 40rem) {
2041
2054
  .sm\:start-0 {
2042
2055
  inset-inline-start: calc(var(--spacing) * 0);
@@ -2171,7 +2184,7 @@
2171
2184
  }
2172
2185
 
2173
2186
  .xl\:ring {
2174
- --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2187
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2175
2188
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2176
2189
  }
2177
2190
  }
@@ -2679,8 +2692,14 @@
2679
2692
  inherits: false
2680
2693
  }
2681
2694
 
2695
+ @property --tw-ease {
2696
+ syntax: "*";
2697
+ inherits: false
2698
+ }
2699
+
2682
2700
  @keyframes spin {
2683
2701
  to {
2684
2702
  transform: rotate(360deg);
2685
2703
  }
2686
2704
  }
2705
+ /*$vite$:1*/
@@ -1,25 +1,56 @@
1
- "use strict";
2
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulator = require("../simulator-DRUsm6IZ.cjs");
4
- const simulatorUrl = require("../simulator-url-DcSYRl-P.cjs");
5
- const discovery = require("../discovery-D1gpaVz4.cjs");
6
- exports.IframeResource = simulator.IframeResource;
7
- exports.McpAppHost = simulator.McpAppHost;
8
- exports.SCREEN_WIDTHS = simulator.SCREEN_WIDTHS;
9
- exports.Simulator = simulator.Simulator;
10
- exports.ThemeProvider = simulator.ThemeProvider;
11
- exports.extractResourceCSP = simulator.extractResourceCSP;
12
- exports.resolveServerToolResult = simulator.resolveServerToolResult;
13
- exports.useThemeContext = simulator.useThemeContext;
14
- exports.createSimulatorUrl = simulatorUrl.createSimulatorUrl;
15
- exports.buildDevSimulations = discovery.buildDevSimulations;
16
- exports.buildResourceMap = discovery.buildResourceMap;
17
- exports.buildSimulations = discovery.buildSimulations;
18
- exports.createResourceExports = discovery.createResourceExports;
19
- exports.extractResourceKey = discovery.extractResourceKey;
20
- exports.extractSimulationKey = discovery.extractSimulationKey;
21
- exports.findResourceDirs = discovery.findResourceDirs;
22
- exports.findResourceKey = discovery.findResourceKey;
23
- exports.getComponentName = discovery.getComponentName;
24
- exports.toPascalCase = discovery.toPascalCase;
25
- //# sourceMappingURL=index.cjs.map
2
+ const require_chunk = require("../chunk-9hOWP6kD.cjs");
3
+ require("../protocol-jbxhzcnS.cjs");
4
+ const require_simulator = require("../simulator-BYIH-xqQ.cjs");
5
+ const require_discovery = require("../discovery-BxKCIgG5.cjs");
6
+ const require_simulator_url = require("../simulator-url-BDGD4vZD.cjs");
7
+ //#region src/chatgpt/index.ts
8
+ var chatgpt_exports = /* @__PURE__ */ require_chunk.__exportAll({
9
+ IframeResource: () => require_simulator.IframeResource,
10
+ McpAppHost: () => require_simulator.McpAppHost,
11
+ SCREEN_WIDTHS: () => require_simulator.SCREEN_WIDTHS,
12
+ Simulator: () => require_simulator.Simulator,
13
+ ThemeProvider: () => require_simulator.ThemeProvider,
14
+ buildDevSimulations: () => require_discovery.buildDevSimulations,
15
+ buildResourceMap: () => require_discovery.buildResourceMap,
16
+ buildSimulations: () => require_discovery.buildSimulations,
17
+ createResourceExports: () => require_discovery.createResourceExports,
18
+ createSimulatorUrl: () => require_simulator_url.createSimulatorUrl,
19
+ extractResourceCSP: () => require_simulator.extractResourceCSP,
20
+ extractResourceKey: () => require_discovery.extractResourceKey,
21
+ extractSimulationKey: () => require_discovery.extractSimulationKey,
22
+ findResourceDirs: () => require_discovery.findResourceDirs,
23
+ findResourceKey: () => require_discovery.findResourceKey,
24
+ getComponentName: () => require_discovery.getComponentName,
25
+ resolveServerToolResult: () => require_simulator.resolveServerToolResult,
26
+ toPascalCase: () => require_discovery.toPascalCase,
27
+ useThemeContext: () => require_simulator.useThemeContext
28
+ });
29
+ //#endregion
30
+ exports.IframeResource = require_simulator.IframeResource;
31
+ exports.McpAppHost = require_simulator.McpAppHost;
32
+ exports.SCREEN_WIDTHS = require_simulator.SCREEN_WIDTHS;
33
+ exports.Simulator = require_simulator.Simulator;
34
+ exports.ThemeProvider = require_simulator.ThemeProvider;
35
+ exports.buildDevSimulations = require_discovery.buildDevSimulations;
36
+ exports.buildResourceMap = require_discovery.buildResourceMap;
37
+ exports.buildSimulations = require_discovery.buildSimulations;
38
+ Object.defineProperty(exports, "chatgpt_exports", {
39
+ enumerable: true,
40
+ get: function() {
41
+ return chatgpt_exports;
42
+ }
43
+ });
44
+ exports.createResourceExports = require_discovery.createResourceExports;
45
+ exports.createSimulatorUrl = require_simulator_url.createSimulatorUrl;
46
+ exports.extractResourceCSP = require_simulator.extractResourceCSP;
47
+ exports.extractResourceKey = require_discovery.extractResourceKey;
48
+ exports.extractSimulationKey = require_discovery.extractSimulationKey;
49
+ exports.findResourceDirs = require_discovery.findResourceDirs;
50
+ exports.findResourceKey = require_discovery.findResourceKey;
51
+ exports.getComponentName = require_discovery.getComponentName;
52
+ exports.resolveServerToolResult = require_simulator.resolveServerToolResult;
53
+ exports.toPascalCase = require_discovery.toPascalCase;
54
+ exports.useThemeContext = require_simulator.useThemeContext;
55
+
56
+ //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/chatgpt/index.ts"],"sourcesContent":["/**\n * ChatGPT-specific exports for the Sunpeak simulator.\n *\n * @module sunpeak/chatgpt\n */\n\n// Register ChatGPT host shell (side effect)\nimport './chatgpt-host';\n\n// Simulator\nexport { Simulator } from '../simulator/simulator';\n\n// Simulator types\nexport type { Simulation, ServerToolMock } from '../types/simulation';\nexport { resolveServerToolResult } from '../types/simulation';\nexport type { ScreenWidth, SimulatorConfig } from '../simulator/simulator-types';\nexport { SCREEN_WIDTHS } from '../simulator/simulator-types';\n\n// Host bridge (for building custom simulators or test harnesses)\nexport { McpAppHost } from '../simulator/mcp-app-host';\nexport type { McpAppHostOptions } from '../simulator/mcp-app-host';\n\n// Iframe rendering (used internally by simulator)\nexport { IframeResource, extractResourceCSP } from '../simulator/iframe-resource';\nexport type { ResourceCSP } from '../simulator/iframe-resource';\n\n// Theme provider\nexport * from '../simulator/theme-provider';\n\n// URL helpers\nexport { createSimulatorUrl } from '../simulator/simulator-url';\nexport type { SimulatorUrlParams } from '../simulator/simulator-url';\n\n// Discovery utilities for building simulations\nexport {\n buildDevSimulations,\n buildSimulations,\n buildResourceMap,\n createResourceExports,\n toPascalCase,\n extractResourceKey,\n extractSimulationKey,\n findResourceKey,\n getComponentName,\n findResourceDirs,\n} from '../lib/discovery';\nexport type {\n BuildSimulationsOptions,\n BuildDevSimulationsOptions,\n ResourceMetadata,\n ResourceDirInfo,\n FsOps,\n} from '../lib/discovery';\n"],"mappings":""}