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.
- package/bin/commands/build.mjs +0 -1
- package/bin/commands/dev.mjs +98 -5
- package/bin/commands/start.mjs +3 -2
- package/bin/lib/live/browser-auth.mjs +53 -17
- package/bin/lib/live/global-setup.mjs +107 -99
- package/bin/lib/live/host-page.mjs +63 -11
- package/bin/lib/live/live-config.mjs +1 -1
- package/bin/lib/sandbox-server.mjs +304 -0
- package/dist/chatgpt/chatgpt-conversation.d.ts +3 -7
- package/dist/chatgpt/globals.css +28 -9
- package/dist/chatgpt/index.cjs +55 -24
- package/dist/chatgpt/index.cjs.map +1 -1
- package/dist/chatgpt/index.js +31 -25
- package/dist/chatgpt/index.js.map +1 -1
- package/dist/chunk-9hOWP6kD.cjs +64 -0
- package/dist/chunk-D6g4UhsZ.js +35 -0
- package/dist/claude/claude-conversation.d.ts +3 -2
- package/dist/claude/index.cjs +4 -4
- package/dist/claude/index.js +3 -5
- package/dist/discovery-BxKCIgG5.cjs +332 -0
- package/dist/discovery-BxKCIgG5.cjs.map +1 -0
- package/dist/discovery-Du4LHrih.js +261 -0
- package/dist/discovery-Du4LHrih.js.map +1 -0
- package/dist/host/chatgpt/index.cjs +171 -65
- package/dist/host/chatgpt/index.cjs.map +1 -1
- package/dist/host/chatgpt/index.js +170 -70
- package/dist/host/chatgpt/index.js.map +1 -1
- package/dist/host/index.cjs +47 -19
- package/dist/host/index.cjs.map +1 -1
- package/dist/host/index.js +47 -24
- package/dist/host/index.js.map +1 -1
- package/dist/index.cjs +3103 -3725
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3026 -3746
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery-cli.cjs +117 -131
- package/dist/lib/discovery-cli.cjs.map +1 -1
- package/dist/lib/discovery-cli.js +107 -111
- package/dist/lib/discovery-cli.js.map +1 -1
- package/dist/mcp/favicon.d.ts +3 -1
- package/dist/mcp/index.cjs +9821 -10270
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +9801 -10268
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/production-server.d.ts +7 -1
- package/dist/mcp/types.d.ts +30 -1
- package/dist/protocol-DJmRaBzO.js +11080 -0
- package/dist/{protocol-DkDHRwOW.cjs.map → protocol-DJmRaBzO.js.map} +1 -1
- package/dist/protocol-jbxhzcnS.cjs +11493 -0
- package/dist/protocol-jbxhzcnS.cjs.map +1 -0
- package/dist/simulator/hosts.d.ts +11 -2
- package/dist/simulator/iframe-resource.d.ts +8 -1
- package/dist/simulator/index.cjs +79 -36
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.js +43 -37
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/mcp-app-host.d.ts +17 -0
- package/dist/simulator/sandbox-proxy.d.ts +38 -0
- package/dist/simulator/simulator.d.ts +7 -1
- package/dist/simulator/use-simulator-state.d.ts +2 -4
- package/dist/simulator-BYIH-xqQ.cjs +3701 -0
- package/dist/simulator-BYIH-xqQ.cjs.map +1 -0
- package/dist/simulator-CmgNnWBO.js +3575 -0
- package/dist/simulator-CmgNnWBO.js.map +1 -0
- package/dist/simulator-url-BDGD4vZD.cjs +69 -0
- package/dist/simulator-url-BDGD4vZD.cjs.map +1 -0
- package/dist/simulator-url-Bkxj43yT.js +64 -0
- package/dist/simulator-url-Bkxj43yT.js.map +1 -0
- package/dist/style.css +28 -9
- package/dist/use-app-D2h-aiyr.cjs +940 -0
- package/dist/use-app-D2h-aiyr.cjs.map +1 -0
- package/dist/use-app-X7JbGskk.js +598 -0
- package/dist/use-app-X7JbGskk.js.map +1 -0
- package/package.json +8 -8
- package/template/.sunpeak/dev.tsx +9 -3
- package/template/node_modules/.bin/vite +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/package.json +5 -5
- package/template/playwright.config.ts +10 -5
- package/template/src/server.ts +16 -2
- package/template/src/tools/show-albums.ts +17 -0
- package/template/tests/e2e/albums.spec.ts +37 -5
- package/template/tests/e2e/carousel.spec.ts +6 -6
- package/template/tests/e2e/global-setup.ts +6 -21
- package/template/tests/e2e/map.spec.ts +11 -11
- package/template/tests/e2e/review.spec.ts +24 -24
- package/dist/claude/index.cjs.map +0 -1
- package/dist/claude/index.js.map +0 -1
- package/dist/discovery-BVqD-JsT.js +0 -224
- package/dist/discovery-BVqD-JsT.js.map +0 -1
- package/dist/discovery-D1gpaVz4.cjs +0 -223
- package/dist/discovery-D1gpaVz4.cjs.map +0 -1
- package/dist/index-B7Qw3Vhh.js +0 -29
- package/dist/index-B7Qw3Vhh.js.map +0 -1
- package/dist/index-BEHP_bM8.js +0 -41
- package/dist/index-BEHP_bM8.js.map +0 -1
- package/dist/index-SfudQ9Y_.cjs +0 -28
- package/dist/index-SfudQ9Y_.cjs.map +0 -1
- package/dist/index-XKHXfBiD.cjs +0 -40
- package/dist/index-XKHXfBiD.cjs.map +0 -1
- package/dist/protocol-DkDHRwOW.cjs +0 -12221
- package/dist/protocol-uge7qFev.js +0 -12223
- package/dist/protocol-uge7qFev.js.map +0 -1
- package/dist/simulator-BCq2iOT-.js +0 -3262
- package/dist/simulator-BCq2iOT-.js.map +0 -1
- package/dist/simulator-DRUsm6IZ.cjs +0 -3277
- package/dist/simulator-DRUsm6IZ.cjs.map +0 -1
- package/dist/simulator-url-DcSYRl-P.cjs +0 -53
- package/dist/simulator-url-DcSYRl-P.cjs.map +0 -1
- package/dist/simulator-url-j_XV3EoP.js +0 -54
- package/dist/simulator-url-j_XV3EoP.js.map +0 -1
- package/dist/use-app-C9gpzIQO.js +0 -349
- package/dist/use-app-C9gpzIQO.js.map +0 -1
- package/dist/use-app-D09O2swh.cjs +0 -348
- 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
|
-
|
|
101
|
-
|
|
129
|
+
// Wait for the page to settle (Cloudflare challenge or UI loading)
|
|
130
|
+
await this.page.waitForTimeout(5_000);
|
|
102
131
|
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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,
|
|
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 {};
|
package/dist/chatgpt/globals.css
CHANGED
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/* Bundled component styles */
|
|
86
|
-
/*! tailwindcss v4.2.
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1813
|
-
backdrop-filter: var(--tw-backdrop-blur,
|
|
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,
|
|
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*/
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,25 +1,56 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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","
|
|
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":""}
|