sunpeak 0.16.17 → 0.16.21

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 (145) hide show
  1. package/README.md +2 -2
  2. package/bin/commands/build.mjs +1 -1
  3. package/bin/commands/dev.mjs +137 -19
  4. package/bin/commands/new.mjs +21 -2
  5. package/bin/commands/start.mjs +1 -1
  6. package/dist/chatgpt/chatgpt-conversation.d.ts +3 -1
  7. package/dist/chatgpt/globals.css +37 -8
  8. package/dist/chatgpt/index.cjs +3 -5
  9. package/dist/chatgpt/index.cjs.map +1 -1
  10. package/dist/chatgpt/index.d.ts +0 -1
  11. package/dist/chatgpt/index.js +3 -5
  12. package/dist/chatgpt/index.js.map +1 -1
  13. package/dist/claude/claude-conversation.d.ts +3 -1
  14. package/dist/claude/index.cjs +2 -2
  15. package/dist/claude/index.d.ts +1 -1
  16. package/dist/claude/index.js +2 -2
  17. package/dist/{discovery-DvIQWTez.js → discovery-BVqD-JsT.js} +4 -2
  18. package/dist/{discovery-DvIQWTez.js.map → discovery-BVqD-JsT.js.map} +1 -1
  19. package/dist/{discovery-SviNiBkF.cjs → discovery-D1gpaVz4.cjs} +4 -2
  20. package/dist/{discovery-SviNiBkF.cjs.map → discovery-D1gpaVz4.cjs.map} +1 -1
  21. package/dist/hooks/index.d.ts +10 -1
  22. package/dist/hooks/safe-area.d.ts +6 -2
  23. package/dist/hooks/use-device-capabilities.d.ts +3 -0
  24. package/dist/hooks/use-platform.d.ts +3 -0
  25. package/dist/hooks/use-styles.d.ts +2 -0
  26. package/dist/hooks/use-time-zone.d.ts +1 -0
  27. package/dist/hooks/use-tool-info.d.ts +3 -0
  28. package/dist/hooks/use-user-agent.d.ts +1 -0
  29. package/dist/hooks/use-viewport.d.ts +3 -0
  30. package/dist/{platform → host}/chatgpt/index.cjs +1 -1
  31. package/dist/host/chatgpt/index.cjs.map +1 -0
  32. package/dist/{platform → host}/chatgpt/index.d.ts +2 -2
  33. package/dist/{platform → host}/chatgpt/index.js +1 -1
  34. package/dist/host/chatgpt/index.js.map +1 -0
  35. package/dist/{platform → host}/chatgpt/use-create-file.d.ts +2 -2
  36. package/dist/{platform → host}/chatgpt/use-file-download.d.ts +2 -2
  37. package/dist/{platform → host}/chatgpt/use-open-modal.d.ts +2 -2
  38. package/dist/{platform → host}/chatgpt/use-request-checkout.d.ts +2 -2
  39. package/dist/{platform → host}/index.cjs +5 -3
  40. package/dist/host/index.cjs.map +1 -0
  41. package/dist/{platform → host}/index.d.ts +15 -11
  42. package/dist/{platform → host}/index.js +5 -3
  43. package/dist/host/index.js.map +1 -0
  44. package/dist/{index-CsYoMHyn.js → index-B4aC3vjH.js} +4 -4
  45. package/dist/index-B4aC3vjH.js.map +1 -0
  46. package/dist/{index-DHcaJ5PU.cjs → index-CKabCJyV.cjs} +4 -4
  47. package/dist/index-CKabCJyV.cjs.map +1 -0
  48. package/dist/index-CX6Z4bED.js +29 -0
  49. package/dist/index-CX6Z4bED.js.map +1 -0
  50. package/dist/index-bKBBCBK6.cjs +28 -0
  51. package/dist/index-bKBBCBK6.cjs.map +1 -0
  52. package/dist/index.cjs +233 -6297
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.ts +4 -2
  55. package/dist/index.js +228 -6292
  56. package/dist/index.js.map +1 -1
  57. package/dist/lib/discovery-cli.cjs +1 -1
  58. package/dist/lib/discovery-cli.js +1 -1
  59. package/dist/mcp/index.cjs +680 -6766
  60. package/dist/mcp/index.cjs.map +1 -1
  61. package/dist/mcp/index.js +682 -6768
  62. package/dist/mcp/index.js.map +1 -1
  63. package/dist/{protocol-CfvM5B6z.cjs → protocol-DkDHRwOW.cjs} +50 -5
  64. package/dist/{protocol-CfvM5B6z.cjs.map → protocol-DkDHRwOW.cjs.map} +1 -1
  65. package/dist/{protocol-CF-P_kw5.js → protocol-uge7qFev.js} +102 -57
  66. package/dist/{protocol-CF-P_kw5.js.map → protocol-uge7qFev.js.map} +1 -1
  67. package/dist/simulator/hosts.d.ts +2 -0
  68. package/dist/simulator/index.cjs +3 -3
  69. package/dist/simulator/index.js +3 -3
  70. package/dist/simulator/simple-sidebar.d.ts +18 -4
  71. package/dist/simulator/simulator-url.d.ts +8 -0
  72. package/dist/simulator/simulator.d.ts +13 -1
  73. package/dist/simulator/use-simulator-state.d.ts +10 -6
  74. package/dist/simulator-D8t-r7HH.js +3222 -0
  75. package/dist/simulator-D8t-r7HH.js.map +1 -0
  76. package/dist/simulator-FFNttkqL.cjs +3237 -0
  77. package/dist/simulator-FFNttkqL.cjs.map +1 -0
  78. package/dist/{simulator-url-rgg_KYOg.cjs → simulator-url-DcSYRl-P.cjs} +7 -1
  79. package/dist/simulator-url-DcSYRl-P.cjs.map +1 -0
  80. package/dist/{simulator-url-CuLqtnSS.js → simulator-url-j_XV3EoP.js} +7 -1
  81. package/dist/simulator-url-j_XV3EoP.js.map +1 -0
  82. package/dist/style.css +37 -8
  83. package/dist/use-app-C9gpzIQO.js +349 -0
  84. package/dist/use-app-C9gpzIQO.js.map +1 -0
  85. package/dist/use-app-D09O2swh.cjs +348 -0
  86. package/dist/use-app-D09O2swh.cjs.map +1 -0
  87. package/package.json +26 -14
  88. package/template/.sunpeak/dev.tsx +28 -2
  89. package/template/node_modules/.bin/vite +2 -2
  90. package/template/node_modules/.bin/vitest +2 -2
  91. package/template/package.json +5 -5
  92. package/template/playwright.config.ts +6 -3
  93. package/template/src/resources/albums/albums.test.tsx +1 -0
  94. package/template/src/resources/albums/albums.tsx +5 -2
  95. package/template/src/resources/albums/components/albums.test.tsx +22 -18
  96. package/template/src/resources/albums/components/albums.tsx +63 -7
  97. package/template/src/resources/albums/components/fullscreen-viewer.test.tsx +3 -25
  98. package/template/src/resources/albums/components/fullscreen-viewer.tsx +2 -3
  99. package/template/src/resources/carousel/carousel.test.tsx +12 -16
  100. package/template/src/resources/carousel/carousel.tsx +47 -5
  101. package/template/src/resources/map/components/map.tsx +65 -9
  102. package/template/src/resources/map/map.test.tsx +0 -1
  103. package/template/src/resources/review/review.test.tsx +25 -27
  104. package/template/src/resources/review/review.tsx +85 -63
  105. package/template/src/tools/review-diff.test.ts +73 -0
  106. package/template/src/tools/review-diff.ts +29 -2
  107. package/template/src/tools/review-post.test.ts +100 -0
  108. package/template/src/tools/review-post.ts +30 -2
  109. package/template/src/tools/review-purchase.test.ts +111 -0
  110. package/template/src/tools/review-purchase.ts +35 -2
  111. package/template/src/tools/review.test.ts +40 -0
  112. package/template/src/tools/review.ts +4 -1
  113. package/template/src/tools/show-albums.test.ts +42 -0
  114. package/template/src/tools/show-albums.ts +22 -2
  115. package/template/src/tools/show-carousel.test.ts +45 -0
  116. package/template/src/tools/show-carousel.ts +19 -2
  117. package/template/src/tools/show-map.test.ts +74 -0
  118. package/template/src/tools/show-map.ts +21 -2
  119. package/template/tests/e2e/albums.spec.ts +75 -0
  120. package/template/tests/e2e/carousel.spec.ts +65 -0
  121. package/template/tests/e2e/global-setup.ts +25 -0
  122. package/template/tests/e2e/map.spec.ts +60 -0
  123. package/template/tests/e2e/review.spec.ts +72 -11
  124. package/dist/chatgpt/chatgpt-simulator.d.ts +0 -10
  125. package/dist/index-BFD3bAHd.cjs +0 -547
  126. package/dist/index-BFD3bAHd.cjs.map +0 -1
  127. package/dist/index-CsYoMHyn.js.map +0 -1
  128. package/dist/index-DHcaJ5PU.cjs.map +0 -1
  129. package/dist/index-wUvmyoCx.js +0 -532
  130. package/dist/index-wUvmyoCx.js.map +0 -1
  131. package/dist/platform/chatgpt/index.cjs.map +0 -1
  132. package/dist/platform/chatgpt/index.js.map +0 -1
  133. package/dist/platform/index.cjs.map +0 -1
  134. package/dist/platform/index.js.map +0 -1
  135. package/dist/simulator-BEFsuj9Z.cjs +0 -8872
  136. package/dist/simulator-BEFsuj9Z.cjs.map +0 -1
  137. package/dist/simulator-Da9iAupa.js +0 -8857
  138. package/dist/simulator-Da9iAupa.js.map +0 -1
  139. package/dist/simulator-url-CuLqtnSS.js.map +0 -1
  140. package/dist/simulator-url-rgg_KYOg.cjs.map +0 -1
  141. package/dist/use-app-CaTJmpgj.cjs +0 -6449
  142. package/dist/use-app-CaTJmpgj.cjs.map +0 -1
  143. package/dist/use-app-DTTzqi-0.js +0 -6450
  144. package/dist/use-app-DTTzqi-0.js.map +0 -1
  145. /package/dist/{platform → host}/chatgpt/openai-types.d.ts +0 -0
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  Local-first MCP Apps framework.
20
20
 
21
- Quickstart, build, test, and ship your Claude or ChatGPT App!
21
+ Quickstart, build, test, and ship your Claude Connector or ChatGPT App!
22
22
 
23
23
  [Demo (Hosted)](https://sunpeak.ai/simulator) ~
24
24
  [Demo (Video)](https://cdn.sunpeak.ai/sunpeak-demo-prod.mp4) ~
@@ -49,7 +49,7 @@ sunpeak new
49
49
 
50
50
  ### The `sunpeak` library
51
51
 
52
- 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`.
52
+ 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.). Host-specific hooks like `useUploadFile`, `useRequestModal`, and `useRequestCheckout` are available via `sunpeak/host/chatgpt`, with `isChatGPT()` / `isClaude()` host detection via `sunpeak/host`.
53
53
  2. Multi-host simulator: React component replicating host runtimes (ChatGPT, Claude) to **test Apps locally and automatically** via UI, props, or URL parameters.
54
54
  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**).
55
55
 
@@ -355,7 +355,7 @@ ${jsContents}
355
355
 
356
356
  // Find tool files
357
357
  const toolFiles = existsSync(toolsDir)
358
- ? readdirSync(toolsDir).filter(f => f.endsWith('.ts'))
358
+ ? readdirSync(toolsDir).filter(f => f.endsWith('.ts') && !f.endsWith('.test.ts'))
359
359
  : [];
360
360
 
361
361
  const hasServerEntry = existsSync(serverEntryPath);
@@ -62,7 +62,7 @@ async function importFromProject(require, moduleName) {
62
62
  *
63
63
  * When a file changes during a build, the current build is killed and restarted.
64
64
  */
65
- function startBuildWatcher(projectRoot, resourcesDir, mcpHandle) {
65
+ function startBuildWatcher(projectRoot, resourcesDir, mcpHandle, { skipInitialBuild = false } = {}) {
66
66
  let activeChild = null;
67
67
  const sunpeakBin = join(dirname(new URL(import.meta.url).pathname), '..', 'sunpeak.js');
68
68
 
@@ -94,8 +94,10 @@ function startBuildWatcher(projectRoot, resourcesDir, mcpHandle) {
94
94
  });
95
95
  };
96
96
 
97
- // Initial build
98
- runBuild();
97
+ // Initial build (skip when --prod-resources already ran a synchronous build)
98
+ if (!skipInitialBuild) {
99
+ runBuild();
100
+ }
99
101
 
100
102
  // Watch src/resources/ for changes using fs.watch (recursive supported on macOS/Windows)
101
103
  let debounceTimer = null;
@@ -150,6 +152,13 @@ export async function dev(projectRoot = process.cwd(), args = []) {
150
152
  // Parse --no-begging flag
151
153
  const noBegging = args.includes('--no-begging');
152
154
 
155
+ // Parse --prod-tools and --prod-resources flags
156
+ const isProdTools = args.includes('--prod-tools');
157
+ const isProdResources = args.includes('--prod-resources');
158
+
159
+ if (isProdTools) console.log('Prod Tools enabled by default (toggle in simulator sidebar)');
160
+ if (isProdResources) console.log('Prod Resources: resources will use production-built HTML from dist/');
161
+
153
162
  console.log(`Starting Vite dev server on port ${port}...`);
154
163
 
155
164
  // Check if we're in the sunpeak workspace (directory is named "template")
@@ -197,10 +206,96 @@ export async function dev(projectRoot = process.cwd(), args = []) {
197
206
  },
198
207
  });
199
208
 
209
+ // Vite plugin that proxies callServerTool to real tool handlers
210
+ const sunpeakCallToolPlugin = () => ({
211
+ name: 'sunpeak-call-tool',
212
+ configureServer(server) {
213
+ server.middlewares.use('/__sunpeak/call-tool', async (req, res) => {
214
+ if (req.method !== 'POST') {
215
+ res.statusCode = 405;
216
+ res.end('Method not allowed');
217
+ return;
218
+ }
219
+ const chunks = [];
220
+ for await (const chunk of req) chunks.push(chunk);
221
+ const body = JSON.parse(Buffer.concat(chunks).toString());
222
+ const { name, arguments: args } = body;
223
+ const toolEntry = toolPathMap.get(name);
224
+ if (!toolEntry) {
225
+ res.setHeader('Content-Type', 'application/json');
226
+ res.end(JSON.stringify({
227
+ content: [{ type: 'text', text: `[Prod Tools] Tool "${name}" not found` }],
228
+ isError: true,
229
+ }));
230
+ return;
231
+ }
232
+ try {
233
+ // Re-load the handler module on each call so edits take effect without restart
234
+ const mod = await toolLoaderServer.ssrLoadModule(`./${toolEntry.relativePath}`);
235
+ const handler = mod.default;
236
+ if (typeof handler !== 'function') {
237
+ res.setHeader('Content-Type', 'application/json');
238
+ res.end(JSON.stringify({
239
+ content: [{ type: 'text', text: `[Prod Tools] Tool "${name}" has no default export handler` }],
240
+ isError: true,
241
+ }));
242
+ return;
243
+ }
244
+ let result = await handler(args ?? {}, {});
245
+ if (typeof result === 'string') {
246
+ result = { content: [{ type: 'text', text: result }] };
247
+ }
248
+ res.setHeader('Content-Type', 'application/json');
249
+ res.end(JSON.stringify(result));
250
+ } catch (err) {
251
+ res.setHeader('Content-Type', 'application/json');
252
+ res.end(JSON.stringify({
253
+ content: [{ type: 'text', text: `[Prod Tools] Tool error: ${err.message}` }],
254
+ isError: true,
255
+ }));
256
+ }
257
+ });
258
+ },
259
+ });
260
+
261
+ // Vite plugin that serves production-built HTML from dist/
262
+ const sunpeakDistPlugin = () => ({
263
+ name: 'sunpeak-dist',
264
+ configureServer(server) {
265
+ server.middlewares.use('/dist', (req, res, next) => {
266
+ const filePath = join(projectRoot, 'dist', req.url);
267
+ if (filePath.endsWith('.html')) {
268
+ if (existsSync(filePath)) {
269
+ res.setHeader('Content-Type', 'text/html');
270
+ res.setHeader('Cache-Control', 'no-cache');
271
+ res.end(readFileSync(filePath));
272
+ } else {
273
+ // Return 404 instead of falling through to Vite's SPA fallback,
274
+ // which would serve the simulator's own index.html.
275
+ res.statusCode = 404;
276
+ res.end();
277
+ }
278
+ return;
279
+ }
280
+ next();
281
+ });
282
+ },
283
+ });
284
+
200
285
  // Create and start Vite dev server programmatically
201
286
  const server = await createServer({
202
287
  root: projectRoot,
203
- plugins: [react(), tailwindcss(), sunpeakFaviconPlugin()],
288
+ plugins: [
289
+ react(),
290
+ tailwindcss(),
291
+ sunpeakFaviconPlugin(),
292
+ sunpeakCallToolPlugin(),
293
+ sunpeakDistPlugin(),
294
+ ],
295
+ define: {
296
+ '__SUNPEAK_PROD_TOOLS__': JSON.stringify(isProdTools),
297
+ '__SUNPEAK_PROD_RESOURCES__': JSON.stringify(isProdResources),
298
+ },
204
299
  resolve: {
205
300
  alias: {
206
301
  '@': path.resolve(projectRoot, 'src'),
@@ -216,6 +311,23 @@ export async function dev(projectRoot = process.cwd(), args = []) {
216
311
  },
217
312
  });
218
313
 
314
+ // --prod-resources: Run initial production build so dist/ is ready before server starts
315
+ if (isProdResources) {
316
+ console.log('Building production resources...');
317
+ const sunpeakBin = join(dirname(new URL(import.meta.url).pathname), '..', 'sunpeak.js');
318
+ const { execSync } = await import('child_process');
319
+ try {
320
+ execSync(`${process.execPath} ${sunpeakBin} build`, {
321
+ cwd: projectRoot,
322
+ stdio: 'inherit',
323
+ env: { ...process.env, NODE_ENV: 'production' },
324
+ });
325
+ } catch {
326
+ console.error('Build failed. Run `sunpeak build` manually to debug.');
327
+ process.exit(1);
328
+ }
329
+ }
330
+
219
331
  await server.listen();
220
332
  server.printUrls();
221
333
  server.bindCLIShortcuts({ print: true });
@@ -270,19 +382,21 @@ export async function dev(projectRoot = process.cwd(), args = []) {
270
382
  logLevel: 'silent',
271
383
  });
272
384
 
273
- // Load real handlers and schemas for backend-only tools
385
+ // Build path map for prod-tools handler reloading (re-imports on each call for HMR).
386
+ // Also do an initial load to validate handlers and populate toolHandlerMap for the MCP server.
387
+ const toolPathMap = new Map();
274
388
  const toolHandlerMap = new Map();
275
389
  for (const [toolName, { tool, path: toolPath }] of toolMap) {
276
- if (!tool.resource) {
277
- try {
278
- const relativePath = path.relative(projectRoot, toolPath);
279
- const mod = await toolLoaderServer.ssrLoadModule(`./${relativePath}`);
280
- if (typeof mod.default === 'function') {
281
- toolHandlerMap.set(toolName, { handler: mod.default });
282
- }
283
- } catch (err) {
284
- console.warn(`Warning: Could not load handler for backend-only tool "${toolName}": ${err.message}`);
390
+ void tool; // Used for metadata; handler loaded unconditionally
391
+ const relativePath = path.relative(projectRoot, toolPath);
392
+ toolPathMap.set(toolName, { relativePath });
393
+ try {
394
+ const mod = await toolLoaderServer.ssrLoadModule(`./${relativePath}`);
395
+ if (typeof mod.default === 'function') {
396
+ toolHandlerMap.set(toolName, { handler: mod.default });
285
397
  }
398
+ } catch (err) {
399
+ console.warn(`Warning: Could not load handler for tool "${toolName}": ${err.message}`);
286
400
  }
287
401
  }
288
402
 
@@ -328,8 +442,10 @@ export async function dev(projectRoot = process.cwd(), args = []) {
328
442
  srcPath,
329
443
  resource: resourceMap.get(resourceKey),
330
444
  } : {}),
331
- // Attach real handler + schema for backend-only tools (consumed by callServerTool)
332
- ...(toolHandlerMap.has(toolName) ? {
445
+ // Attach real handler for tools consumed by the MCP server.
446
+ // Backend-only tools (no resource) always need handlers for callServerTool.
447
+ // UI tools only get handlers in --prod-tools mode (otherwise simulation mock data is used).
448
+ ...((toolHandlerMap.has(toolName) && (!resourceKey || isProdTools)) ? {
333
449
  handler: toolHandlerMap.get(toolName).handler,
334
450
  } : {}),
335
451
  });
@@ -422,7 +538,7 @@ if (import.meta.hot) {
422
538
  },
423
539
  server: {
424
540
  middlewareMode: true,
425
- hmr: { port: 24679 },
541
+ hmr: { port: Number(process.env.SUNPEAK_HMR_PORT || 24679) },
426
542
  allowedHosts: true,
427
543
  watch: {
428
544
  // Only watch files that affect the UI bundle (not JSON, tests, etc.)
@@ -449,7 +565,9 @@ if (import.meta.hot) {
449
565
  version: pkg.version || '0.1.0',
450
566
  simulations,
451
567
  port: 8000,
452
- viteServer: mcpViteServer,
568
+ // In --prod-resources mode, don't pass viteServer so the MCP server serves pre-built HTML.
569
+ // Otherwise, pass it so ChatGPT gets Vite HMR.
570
+ viteServer: isProdResources ? undefined : mcpViteServer,
453
571
  });
454
572
 
455
573
  // Build production bundles and watch for changes.
@@ -457,7 +575,7 @@ if (import.meta.hot) {
457
575
  // reach the local Vite dev server. The watcher rebuilds on source file changes
458
576
  // so the prod output stays fresh without manual `sunpeak build`.
459
577
  // On successful builds, mcpHandle.invalidateResources() notifies tunnel sessions.
460
- startBuildWatcher(projectRoot, resourcesDir, mcpHandle);
578
+ startBuildWatcher(projectRoot, resourcesDir, mcpHandle, { skipInitialBuild: isProdResources });
461
579
 
462
580
  // Handle signals - close all servers
463
581
  process.on('SIGINT', async () => {
@@ -73,6 +73,7 @@ export const defaultDeps = {
73
73
  execAsync,
74
74
  promptName: defaultPromptName,
75
75
  selectResources: defaultSelectResources,
76
+ confirm: clack.confirm,
76
77
  intro: clack.intro,
77
78
  outro: clack.outro,
78
79
  spinner: clack.spinner,
@@ -191,9 +192,9 @@ export async function init(projectName, resourcesArg, deps = defaultDeps) {
191
192
  return false;
192
193
  }
193
194
  }
194
- // Skip tool files for excluded resources: src/tools/*.ts
195
+ // Skip tool files (and their tests) for excluded resources: src/tools/*.ts
195
196
  if (src.includes('/src/tools/') && name.endsWith('.ts')) {
196
- const baseName = name.replace(/\.ts$/, '');
197
+ const baseName = name.replace(/\.(test\.)?ts$/, '');
197
198
  if (baseName === resource || baseName.startsWith(resource + '-') || baseName.endsWith('-' + resource)) {
198
199
  return false;
199
200
  }
@@ -266,6 +267,24 @@ export async function init(projectName, resourcesArg, deps = defaultDeps) {
266
267
  s.stop(`Install failed. You can try running "${pm} install" manually.`);
267
268
  }
268
269
 
270
+ // Offer to install the sunpeak skill (only in interactive mode)
271
+ if (resourcesArg === undefined) {
272
+ const installSkill = await d.confirm({
273
+ message: 'Install the sunpeak skill? (helps your coding agent build your app)',
274
+ initialValue: true,
275
+ });
276
+ if (!clack.isCancel(installSkill) && installSkill) {
277
+ try {
278
+ d.execSync('npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app', {
279
+ cwd: targetDir,
280
+ stdio: 'inherit',
281
+ });
282
+ } catch {
283
+ d.console.log('Skill install skipped. You can install later with: npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app');
284
+ }
285
+ }
286
+ }
287
+
269
288
  const runCmd = pm === 'npm' ? 'npm run' : pm;
270
289
 
271
290
  d.outro(`Done! To get started:
@@ -114,7 +114,7 @@ export async function start(projectRoot = process.cwd(), args = []) {
114
114
  const tools = [];
115
115
 
116
116
  if (existsSync(toolsDir)) {
117
- const toolFiles = readdirSync(toolsDir).filter(f => f.endsWith('.js'));
117
+ const toolFiles = readdirSync(toolsDir).filter(f => f.endsWith('.js') && !f.endsWith('.test.js'));
118
118
 
119
119
  for (const file of toolFiles) {
120
120
  const toolName = file.replace(/\.js$/, '');
@@ -17,6 +17,8 @@ interface ConversationProps {
17
17
  * border from flashing at a stale height before the iframe resizes.
18
18
  */
19
19
  isTransitioning?: boolean;
20
+ /** Optional action element rendered in the conversation header (e.g., Run button) */
21
+ headerAction?: React.ReactNode;
20
22
  }
21
23
  /**
22
24
  * Conversation layout that renders children (iframe) at a stable tree position.
@@ -31,5 +33,5 @@ interface ConversationProps {
31
33
  * - **fullscreen**: content wrapper becomes `position: fixed` covering the viewport;
32
34
  * fullscreen chrome (header/footer) rendered as a separate fixed overlay
33
35
  */
34
- export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
36
+ export declare function Conversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, headerAction, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
35
37
  export {};
@@ -618,6 +618,10 @@
618
618
  left: 340px;
619
619
  }
620
620
 
621
+ .-z-10 {
622
+ z-index: calc(10 * -1);
623
+ }
624
+
621
625
  .z-10 {
622
626
  z-index: 10;
623
627
  }
@@ -642,6 +646,18 @@
642
646
  z-index: 51;
643
647
  }
644
648
 
649
+ .z-\[200\] {
650
+ z-index: 200;
651
+ }
652
+
653
+ .col-span-3 {
654
+ grid-column: span 3 / span 3;
655
+ }
656
+
657
+ .col-span-4 {
658
+ grid-column: span 4 / span 4;
659
+ }
660
+
645
661
  .container {
646
662
  width: 100%;
647
663
  }
@@ -1034,10 +1050,18 @@
1034
1050
  grid-template-columns: repeat(4, minmax(0, 1fr));
1035
1051
  }
1036
1052
 
1053
+ .grid-cols-7 {
1054
+ grid-template-columns: repeat(7, minmax(0, 1fr));
1055
+ }
1056
+
1037
1057
  .grid-cols-\[1fr_auto_1fr\] {
1038
1058
  grid-template-columns: 1fr auto 1fr;
1039
1059
  }
1040
1060
 
1061
+ .grid-cols-\[2fr_1fr\] {
1062
+ grid-template-columns: 2fr 1fr;
1063
+ }
1064
+
1041
1065
  .flex-col {
1042
1066
  flex-direction: column;
1043
1067
  }
@@ -1460,6 +1484,10 @@
1460
1484
  padding: calc(var(--spacing) * 5);
1461
1485
  }
1462
1486
 
1487
+ .p-8 {
1488
+ padding: calc(var(--spacing) * 8);
1489
+ }
1490
+
1463
1491
  .p-\[1px\] {
1464
1492
  padding: 1px;
1465
1493
  }
@@ -1520,10 +1548,6 @@
1520
1548
  padding-block: calc(var(--spacing) * 4);
1521
1549
  }
1522
1550
 
1523
- .py-8 {
1524
- padding-block: calc(var(--spacing) * 8);
1525
- }
1526
-
1527
1551
  .pt-0 {
1528
1552
  padding-top: calc(var(--spacing) * 0);
1529
1553
  }
@@ -1552,10 +1576,6 @@
1552
1576
  padding-bottom: calc(var(--spacing) * 10);
1553
1577
  }
1554
1578
 
1555
- .pl-4 {
1556
- padding-left: calc(var(--spacing) * 4);
1557
- }
1558
-
1559
1579
  .text-center {
1560
1580
  text-align: center;
1561
1581
  }
@@ -1710,6 +1730,10 @@
1710
1730
  text-transform: uppercase;
1711
1731
  }
1712
1732
 
1733
+ .no-underline {
1734
+ text-decoration-line: none;
1735
+ }
1736
+
1713
1737
  .antialiased {
1714
1738
  -webkit-font-smoothing: antialiased;
1715
1739
  -moz-osx-font-smoothing: grayscale;
@@ -1774,6 +1798,11 @@
1774
1798
  outline-width: 1px;
1775
1799
  }
1776
1800
 
1801
+ .blur {
1802
+ --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, );
1804
+ }
1805
+
1777
1806
  .filter {
1778
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, );
1779
1808
  }
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulator = require("../simulator-BEFsuj9Z.cjs");
4
- const chatgpt_index = require("../index-BFD3bAHd.cjs");
5
- const simulatorUrl = require("../simulator-url-rgg_KYOg.cjs");
6
- const discovery = require("../discovery-SviNiBkF.cjs");
3
+ const simulator = require("../simulator-FFNttkqL.cjs");
4
+ const simulatorUrl = require("../simulator-url-DcSYRl-P.cjs");
5
+ const discovery = require("../discovery-D1gpaVz4.cjs");
7
6
  exports.IframeResource = simulator.IframeResource;
8
7
  exports.McpAppHost = simulator.McpAppHost;
9
8
  exports.SCREEN_WIDTHS = simulator.SCREEN_WIDTHS;
@@ -12,7 +11,6 @@ exports.ThemeProvider = simulator.ThemeProvider;
12
11
  exports.extractResourceCSP = simulator.extractResourceCSP;
13
12
  exports.resolveServerToolResult = simulator.resolveServerToolResult;
14
13
  exports.useThemeContext = simulator.useThemeContext;
15
- exports.ChatGPTSimulator = chatgpt_index.ChatGPTSimulator;
16
14
  exports.createSimulatorUrl = simulatorUrl.createSimulatorUrl;
17
15
  exports.buildDevSimulations = discovery.buildDevSimulations;
18
16
  exports.buildResourceMap = discovery.buildResourceMap;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,3 @@
1
- export { ChatGPTSimulator } from './chatgpt-simulator';
2
1
  export { Simulator } from '../simulator/simulator';
3
2
  export type { Simulation, ServerToolMock } from '../types/simulation';
4
3
  export { resolveServerToolResult } from '../types/simulation';
@@ -1,9 +1,7 @@
1
- import { I, M, a, S, T, j, m, n } from "../simulator-Da9iAupa.js";
2
- import { C } from "../index-wUvmyoCx.js";
3
- import { c } from "../simulator-url-CuLqtnSS.js";
4
- import { b, a as a2, c as c2, d, e, f, g, h, i, t } from "../discovery-DvIQWTez.js";
1
+ import { I, M, a, S, T, j, m, n } from "../simulator-D8t-r7HH.js";
2
+ import { c } from "../simulator-url-j_XV3EoP.js";
3
+ import { b, a as a2, c as c2, d, e, f, g, h, i, t } from "../discovery-BVqD-JsT.js";
5
4
  export {
6
- C as ChatGPTSimulator,
7
5
  I as IframeResource,
8
6
  M as McpAppHost,
9
7
  a as SCREEN_WIDTHS,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -12,6 +12,8 @@ interface ClaudeConversationProps {
12
12
  appIcon?: string;
13
13
  userMessage?: string;
14
14
  isTransitioning?: boolean;
15
+ /** Optional action element rendered in the conversation header (e.g., Run button) */
16
+ headerAction?: React.ReactNode;
15
17
  }
16
18
  /**
17
19
  * Claude conversation shell — mimics Claude's chat UI chrome.
@@ -19,5 +21,5 @@ interface ClaudeConversationProps {
19
21
  * All three display modes (inline, pip, fullscreen) share the same React tree
20
22
  * shape so that the iframe never unmounts when switching modes.
21
23
  */
22
- export declare function ClaudeConversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, }: ClaudeConversationProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function ClaudeConversation({ children, screenWidth, displayMode, platform, onRequestDisplayMode, appName, appIcon, userMessage, isTransitioning, headerAction, }: ClaudeConversationProps): import("react/jsx-runtime").JSX.Element;
23
25
  export {};
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulator = require("../simulator-BEFsuj9Z.cjs");
4
- exports.ClaudeSimulator = simulator.Simulator;
3
+ const simulator = require("../simulator-FFNttkqL.cjs");
4
+ exports.Simulator = simulator.Simulator;
5
5
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- export { Simulator as ClaudeSimulator } from '../simulator/simulator';
1
+ export { Simulator } from '../simulator/simulator';
@@ -1,5 +1,5 @@
1
- import { S } from "../simulator-Da9iAupa.js";
1
+ import { S } from "../simulator-D8t-r7HH.js";
2
2
  export {
3
- S as ClaudeSimulator
3
+ S as Simulator
4
4
  };
5
5
  //# sourceMappingURL=index.js.map
@@ -190,7 +190,9 @@ function findToolFiles(toolsDir, fs) {
190
190
  return [];
191
191
  }
192
192
  const entries = fs.readdirSync(toolsDir, { withFileTypes: true });
193
- return entries.filter((entry) => !entry.isDirectory() && entry.name.endsWith(".ts")).map((entry) => ({
193
+ return entries.filter(
194
+ (entry) => !entry.isDirectory() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")
195
+ ).map((entry) => ({
194
196
  name: entry.name.replace(/\.ts$/, ""),
195
197
  path: `${toolsDir}/${entry.name}`
196
198
  }));
@@ -219,4 +221,4 @@ export {
219
221
  findToolFiles as k,
220
222
  toPascalCase as t
221
223
  };
222
- //# sourceMappingURL=discovery-DvIQWTez.js.map
224
+ //# sourceMappingURL=discovery-BVqD-JsT.js.map