sunpeak 0.11.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +84 -12
  2. package/bin/commands/build.mjs +1 -11
  3. package/bin/commands/deploy.mjs +7 -1
  4. package/bin/commands/dev.mjs +3 -1
  5. package/bin/commands/new.mjs +211 -0
  6. package/bin/commands/pull.mjs +1 -1
  7. package/bin/commands/push.mjs +36 -13
  8. package/bin/lib/patterns.mjs +25 -0
  9. package/bin/sunpeak.js +13 -222
  10. package/package.json +1 -1
  11. package/template/.sunpeak/dev.tsx +4 -4
  12. package/template/README.md +21 -22
  13. package/template/dist/albums/albums.json +1 -1
  14. package/template/dist/carousel/carousel.json +1 -1
  15. package/template/dist/map/map.json +1 -1
  16. package/template/dist/review/review.json +1 -1
  17. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +7 -7
  18. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
  19. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +7 -7
  20. package/template/node_modules/.vite/deps/_metadata.json +30 -30
  21. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  22. package/template/vitest.config.ts +1 -1
  23. package/template/src/resources/albums/albums-show-simulation.json +0 -131
  24. package/template/src/resources/carousel/carousel-show-simulation.json +0 -68
  25. package/template/src/resources/map/map-show-simulation.json +0 -123
  26. package/template/src/resources/review/review-diff-simulation.json +0 -80
  27. package/template/src/resources/review/review-post-simulation.json +0 -56
  28. package/template/src/resources/review/review-purchase-simulation.json +0 -88
  29. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Avatar.js +0 -0
  30. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Avatar.js.map +0 -0
  31. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Button.js +10 -10
  32. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Button.js.map +0 -0
  33. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Checkbox.js +0 -0
  34. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Checkbox.js.map +0 -0
  35. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Icon.js +0 -0
  36. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Icon.js.map +0 -0
  37. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Input.js +1 -1
  38. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Input.js.map +0 -0
  39. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_SegmentedControl.js +4 -4
  40. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_SegmentedControl.js.map +0 -0
  41. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Select.js +9 -9
  42. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Select.js.map +0 -0
  43. package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Textarea.js +1 -1
  44. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_components_Textarea.js.map +0 -0
  45. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_theme.js +0 -0
  46. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/@openai_apps-sdk-ui_theme.js.map +0 -0
  47. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-2UDYPUBJ.js +0 -0
  48. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-2UDYPUBJ.js.map +0 -0
  49. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-6QVG4F2X.js +0 -0
  50. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-6QVG4F2X.js.map +0 -0
  51. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-BUOVMFCD.js +0 -0
  52. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-BUOVMFCD.js.map +0 -0
  53. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-CNYJBM5F.js +0 -0
  54. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-CNYJBM5F.js.map +0 -0
  55. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-EGRHWZRV.js +0 -0
  56. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-EGRHWZRV.js.map +0 -0
  57. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-ILHRZGIS.js +0 -0
  58. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-ILHRZGIS.js.map +0 -0
  59. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-JAGHY6H6.js +0 -0
  60. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-JAGHY6H6.js.map +0 -0
  61. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-JGVISENQ.js +0 -0
  62. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-JGVISENQ.js.map +0 -0
  63. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-P5LK4A7U.js +0 -0
  64. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-P5LK4A7U.js.map +0 -0
  65. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-QPJAV452.js +0 -0
  66. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-QPJAV452.js.map +0 -0
  67. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-RYYR2YMB.js +0 -0
  68. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-RYYR2YMB.js.map +0 -0
  69. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-UM3ZGDFR.js +0 -0
  70. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-UM3ZGDFR.js.map +0 -0
  71. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-XZTIOEPG.js +0 -0
  72. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/chunk-XZTIOEPG.js.map +0 -0
  73. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/clsx.js +0 -0
  74. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/clsx.js.map +0 -0
  75. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/embla-carousel-react.js +0 -0
  76. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/embla-carousel-react.js.map +0 -0
  77. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/embla-carousel-wheel-gestures.js +0 -0
  78. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/embla-carousel-wheel-gestures.js.map +0 -0
  79. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/mapbox-gl.js +0 -0
  80. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/mapbox-gl.js.map +0 -0
  81. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/package.json +0 -0
  82. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react-dom.js +0 -0
  83. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react-dom.js.map +0 -0
  84. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react-dom_client.js +0 -0
  85. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react-dom_client.js.map +0 -0
  86. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react.js +0 -0
  87. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react.js.map +0 -0
  88. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react_jsx-dev-runtime.js +0 -0
  89. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react_jsx-dev-runtime.js.map +0 -0
  90. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react_jsx-runtime.js +0 -0
  91. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/react_jsx-runtime.js.map +0 -0
  92. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/tailwind-merge.js +0 -0
  93. /package/template/node_modules/.vite-mcp/{deps_temp_c8c8077a → deps_temp_992accd8}/tailwind-merge.js.map +0 -0
  94. /package/template/{src/test → tests}/setup.ts +0 -0
  95. /package/template/{dist → tests/simulations}/albums/albums-show-simulation.json +0 -0
  96. /package/template/{dist → tests/simulations}/carousel/carousel-show-simulation.json +0 -0
  97. /package/template/{dist → tests/simulations}/map/map-show-simulation.json +0 -0
  98. /package/template/{dist → tests/simulations}/review/review-diff-simulation.json +0 -0
  99. /package/template/{dist → tests/simulations}/review/review-post-simulation.json +0 -0
  100. /package/template/{dist → tests/simulations}/review/review-purchase-simulation.json +0 -0
package/bin/sunpeak.js CHANGED
@@ -1,49 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import {
4
- existsSync,
5
- mkdirSync,
6
- cpSync,
7
- readFileSync,
8
- writeFileSync,
9
- renameSync,
10
- readdirSync,
11
- } from 'fs';
12
- import { join, dirname, basename } from 'path';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
13
5
  import { fileURLToPath } from 'url';
14
- import { createInterface } from 'readline';
6
+ import { discoverResources } from './lib/patterns.mjs';
15
7
 
16
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
9
  const COMMANDS_DIR = join(__dirname, 'commands');
18
10
 
19
- /**
20
- * Auto-discover available resources from template/src/resources directories.
21
- * Each subdirectory containing a {name}-resource.tsx file is a valid resource.
22
- */
23
- function discoverResources() {
24
- const resourcesDir = join(__dirname, '..', 'template', 'src', 'resources');
25
- if (!existsSync(resourcesDir)) {
26
- return [];
27
- }
28
- return readdirSync(resourcesDir, { withFileTypes: true })
29
- .filter((entry) => entry.isDirectory())
30
- .filter((entry) => {
31
- const resourceFile = join(resourcesDir, entry.name, `${entry.name}-resource.tsx`);
32
- return existsSync(resourceFile);
33
- })
34
- .map((entry) => entry.name);
35
- }
36
-
37
- function prompt(question) {
38
- const rl = createInterface({ input: process.stdin, output: process.stdout });
39
- return new Promise((resolve) => {
40
- rl.question(question, (answer) => {
41
- rl.close();
42
- resolve(answer.trim());
43
- });
44
- });
45
- }
46
-
47
11
  function checkPackageJson() {
48
12
  const pkgPath = join(process.cwd(), 'package.json');
49
13
  if (!existsSync(pkgPath)) {
@@ -53,148 +17,6 @@ function checkPackageJson() {
53
17
  }
54
18
  }
55
19
 
56
- function parseResourcesInput(input, validResources) {
57
- // If no input, return all resources
58
- if (!input || input.trim() === '') {
59
- return validResources;
60
- }
61
-
62
- // Split by comma or space and trim
63
- const tokens = input
64
- .toLowerCase()
65
- .split(/[,\s]+/)
66
- .map((s) => s.trim())
67
- .filter((s) => s.length > 0);
68
-
69
- // Validate tokens
70
- const invalid = tokens.filter((t) => !validResources.includes(t));
71
- if (invalid.length > 0) {
72
- console.error(`Error: Invalid resource(s): ${invalid.join(', ')}`);
73
- console.error(`Valid resources are: ${validResources.join(', ')}`);
74
- process.exit(1);
75
- }
76
-
77
- // Remove duplicates
78
- return [...new Set(tokens)];
79
- }
80
-
81
- async function init(projectName, resourcesArg) {
82
- // Discover available resources from template
83
- const availableResources = discoverResources();
84
- if (availableResources.length === 0) {
85
- console.error('Error: No resources found in template/src/resources/');
86
- process.exit(1);
87
- }
88
-
89
- if (!projectName) {
90
- projectName = await prompt('☀️ 🏔️ Project name [my-app]: ');
91
- if (!projectName) {
92
- projectName = 'my-app';
93
- }
94
- }
95
-
96
- if (projectName === 'template') {
97
- console.error('Error: "template" is a reserved name. Please choose another name.');
98
- process.exit(1);
99
- }
100
-
101
- // Use resources from args or ask for them
102
- let resourcesInput;
103
- if (resourcesArg) {
104
- resourcesInput = resourcesArg;
105
- console.log(`☀️ 🏔️ Resources: ${resourcesArg}`);
106
- } else {
107
- resourcesInput = await prompt(
108
- `☀️ 🏔️ Resources (UIs) to include [${availableResources.join(', ')}]: `
109
- );
110
- }
111
- const selectedResources = parseResourcesInput(resourcesInput, availableResources);
112
-
113
- const targetDir = join(process.cwd(), projectName);
114
-
115
- if (existsSync(targetDir)) {
116
- console.error(`Error: Directory "${projectName}" already exists`);
117
- process.exit(1);
118
- }
119
-
120
- const templateDir = join(__dirname, '..', 'template');
121
-
122
- console.log(`☀️ 🏔️ Creating ${projectName}...`);
123
-
124
- mkdirSync(targetDir, { recursive: true });
125
-
126
- // Filter resource directories based on selection
127
- const excludedResources = availableResources.filter((r) => !selectedResources.includes(r));
128
-
129
- cpSync(templateDir, targetDir, {
130
- recursive: true,
131
- filter: (src) => {
132
- const name = basename(src);
133
-
134
- // Skip node_modules and lock file
135
- if (name === 'node_modules' || name === 'pnpm-lock.yaml') {
136
- return false;
137
- }
138
-
139
- for (const resource of excludedResources) {
140
- // Skip entire resource directory: src/resources/{resource}/
141
- if (src.includes('/resources/') && name === resource) {
142
- return false;
143
- }
144
- // Skip e2e test files for excluded resources
145
- if (src.includes('/tests/e2e/') && name === `${resource}.spec.ts`) {
146
- return false;
147
- }
148
- }
149
-
150
- return true;
151
- },
152
- });
153
-
154
- // Rename underscore-prefixed files to dotfiles
155
- const dotfiles = ['_gitignore', '_prettierignore', '_prettierrc'];
156
- for (const file of dotfiles) {
157
- const srcPath = join(targetDir, file);
158
- const destPath = join(targetDir, file.replace(/^_/, '.'));
159
- if (existsSync(srcPath)) {
160
- renameSync(srcPath, destPath);
161
- }
162
- }
163
-
164
- // Read sunpeak version from root package.json
165
- const rootPkgPath = join(__dirname, '..', 'package.json');
166
- const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
167
- const sunpeakVersion = `^${rootPkg.version}`;
168
-
169
- // Update project package.json
170
- const pkgPath = join(targetDir, 'package.json');
171
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
172
- pkg.name = projectName;
173
-
174
- // Replace workspace:* with actual version
175
- if (pkg.dependencies?.sunpeak === 'workspace:*') {
176
- pkg.dependencies.sunpeak = sunpeakVersion;
177
- }
178
-
179
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
180
-
181
- console.log(`
182
- Done! To get started:
183
-
184
- cd ${projectName}
185
- pnpm install
186
- sunpeak dev
187
-
188
- That's it! Your project commands:
189
-
190
- sunpeak dev # Start dev server + MCP endpoint
191
- sunpeak build # Build for production
192
- pnpm test # Run tests
193
-
194
- See README.md for more details.
195
- `);
196
- }
197
-
198
20
  const [, , command, ...args] = process.argv;
199
21
 
200
22
  /**
@@ -206,40 +28,6 @@ function getVersion() {
206
28
  return pkg.version;
207
29
  }
208
30
 
209
- /**
210
- * Parse arguments for resource commands (push, pull, deploy)
211
- */
212
- function parseResourceArgs(args) {
213
- const options = { tags: [] };
214
- let i = 0;
215
-
216
- while (i < args.length) {
217
- const arg = args[i];
218
-
219
- if (arg === '--repository' || arg === '-r') {
220
- options.repository = args[++i];
221
- } else if (arg === '--tag' || arg === '-t') {
222
- options.tags.push(args[++i]);
223
- } else if (arg === '--name' || arg === '-n') {
224
- options.name = args[++i];
225
- } else if (arg === '--output' || arg === '-o') {
226
- options.output = args[++i];
227
- } else if (arg === '--help' || arg === '-h') {
228
- options.help = true;
229
- } else if (!arg.startsWith('-')) {
230
- // Positional argument - treat as file path
231
- options.file = arg;
232
- }
233
-
234
- i++;
235
- }
236
-
237
- // Set singular tag for commands that expect it (e.g., pull)
238
- options.tag = options.tags[0];
239
-
240
- return options;
241
- }
242
-
243
31
  // Main CLI handler
244
32
  (async () => {
245
33
  // Handle --version / -v flags early
@@ -267,7 +55,10 @@ function parseResourceArgs(args) {
267
55
 
268
56
  switch (command) {
269
57
  case 'new':
270
- await init(args[0], args[1]);
58
+ {
59
+ const { init } = await import(join(COMMANDS_DIR, 'new.mjs'));
60
+ await init(args[0], args[1]);
61
+ }
271
62
  break;
272
63
 
273
64
  case 'dev':
@@ -300,22 +91,22 @@ function parseResourceArgs(args) {
300
91
 
301
92
  case 'push':
302
93
  {
303
- const { push } = await import(join(COMMANDS_DIR, 'push.mjs'));
304
- await push(process.cwd(), parseResourceArgs(args));
94
+ const { push, parseArgs } = await import(join(COMMANDS_DIR, 'push.mjs'));
95
+ await push(process.cwd(), parseArgs(args));
305
96
  }
306
97
  break;
307
98
 
308
99
  case 'pull':
309
100
  {
310
- const { pull } = await import(join(COMMANDS_DIR, 'pull.mjs'));
311
- await pull(process.cwd(), parseResourceArgs(args));
101
+ const { pull, parseArgs } = await import(join(COMMANDS_DIR, 'pull.mjs'));
102
+ await pull(process.cwd(), parseArgs(args));
312
103
  }
313
104
  break;
314
105
 
315
106
  case 'deploy':
316
107
  {
317
- const { deploy } = await import(join(COMMANDS_DIR, 'deploy.mjs'));
318
- await deploy(process.cwd(), parseResourceArgs(args));
108
+ const { deploy, parseArgs } = await import(join(COMMANDS_DIR, 'deploy.mjs'));
109
+ await deploy(process.cwd(), parseArgs(args));
319
110
  }
320
111
  break;
321
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "description": "The ChatGPT App framework. Quickstart, build, & test your ChatGPT App locally!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -4,9 +4,9 @@
4
4
  * This file bootstraps the ChatGPT simulator for development
5
5
  *
6
6
  * Auto-discovers simulations and resources by file naming convention:
7
- * - resources/{resource}/{resource}-{scenario}-simulation.json (e.g., resources/albums/albums-show-simulation.json)
8
- * - resources/{resource}/{resource}-resource.json
9
- * - resources/{resource}/{Resource}Resource component (PascalCase)
7
+ * - tests/simulations/{resource}/{resource}-{scenario}-simulation.json
8
+ * - src/resources/{resource}/{resource}-resource.json
9
+ * - src/resources/{resource}/{Resource}Resource component (PascalCase)
10
10
  */
11
11
  import { StrictMode } from 'react';
12
12
  import { createRoot } from 'react-dom/client';
@@ -16,7 +16,7 @@ import '../src/styles/globals.css';
16
16
 
17
17
  // Build simulations from discovered files
18
18
  const simulations = buildDevSimulations({
19
- simulationModules: import.meta.glob('../src/resources/*/*-simulation.json', { eager: true }),
19
+ simulationModules: import.meta.glob('../tests/simulations/*/*-simulation.json', { eager: true }),
20
20
  resourceModules: import.meta.glob('../src/resources/*/*-resource.json', { eager: true }),
21
21
  resourceComponents: resourceComponents as Record<string, React.ComponentType>,
22
22
  });
@@ -1,6 +1,6 @@
1
1
  # sunpeak-app
2
2
 
3
- A ChatGPT App UI built with [sunpeak](https://github.com/Sunpeak-AI/sunpeak).
3
+ A ChatGPT App built with [sunpeak](https://github.com/Sunpeak-AI/sunpeak).
4
4
 
5
5
  For an initial overview of your new app and a detailed API reference, refer to the [documentation](https://docs.sunpeak.ai/template/project-structure).
6
6
 
@@ -32,15 +32,20 @@ The template includes a minimal test setup with Vitest. You can add additional t
32
32
 
33
33
  ## Project Structure
34
34
 
35
- - `src/resources/` - Directory containing all your MCP Resources (ChatGPT App UIs).
36
- - Each resource is a subdirectory containing all files for that resource.
37
- - Example: `src/resources/albums/` contains:
38
- - `albums-resource.tsx` - The React component.
39
- - `albums-resource.json` - Resource metadata (name, title, description, etc.).
40
- - `albums-resource.test.tsx` - Unit tests for the resource.
41
- - `albums-show-simulation.json` - Simulation data for testing.
42
- - `components/` - UI components used by the resource.
43
- - `tests/e2e/` - Directory containing end-to-end Playwright tests for each resource. Uses the ChatGPTSimulator to test your resources render properly with any state (tool calls, saved state, dark mode, pip display mode, etc.).
35
+ Using a Review page as an example, sunpeak projects look like:
36
+
37
+ ```bash
38
+ my-app/
39
+ ├── src/resources/
40
+ │ └── review/
41
+ │ ├── review-resource.tsx # Review UI component.
42
+ │ └── review-resource.json # Review UI MCP metadata.
43
+ ├── tests/simulations/
44
+ │ └── review/
45
+ │ ├── review-{scenario1}-simulation.json # Mock state for testing.
46
+ │ └── review-{scenario2}-simulation.json # Mock state for testing.
47
+ └── package.json
48
+ ```
44
49
 
45
50
  ## Testing in ChatGPT
46
51
 
@@ -58,8 +63,6 @@ You can then connect to the tunnel forwarding URL at the `/mcp` path from ChatGP
58
63
 
59
64
  Once your app is connected, send the name of the app and a tool, like `/sunpeak show review`, to ChatGPT.
60
65
 
61
- When you make changes to the UI, refresh your app in ChatGPT after the dev server has finished rebuilding: `User > Settings > Apps & Connectors > My App > Refresh`
62
-
63
66
  ## Build & Deploy
64
67
 
65
68
  Build your app for production:
@@ -73,15 +76,11 @@ This creates optimized builds in `dist/`, organized by resource:
73
76
  ```bash
74
77
  dist/
75
78
  ├── albums/
76
- │ ├── albums.js # Built resource component.
77
- ├── albums.json # Resource metadata.
78
- │ └── albums-show-simulation.json # Resource mock data for testing.
79
+ │ ├── albums.js # Built resource component.
80
+ └── albums.json # Resource metadata.
79
81
  ├── review/
80
82
  │ ├── review.js
81
- ├── review.json
82
- │ ├── review-diff-simulation.json
83
- │ ├── review-post-simulation.json
84
- │ └── review-purchase-simulation.json
83
+ └── review.json
85
84
  └── ...
86
85
  ```
87
86
 
@@ -89,7 +88,6 @@ Each resource folder contains:
89
88
 
90
89
  - **`.js` file**: Self-contained bundle with CSS inlined
91
90
  - **`.json` file**: Resource metadata with unique `uri` for cache-busting
92
- - **`*-simulation.json` files**: All affiliated simulation files for the resource. These are not needed for the production runtime, but are used in the sunpeak repository for testing.
93
91
 
94
92
  Host these files and reference them as resources in your production MCP server.
95
93
  Use the sunpeak resource repository for built-in resource hosting.
@@ -103,11 +101,12 @@ src/resources/NAME/
103
101
  ├── NAME-resource.tsx # React component (required)
104
102
  ├── NAME-resource.json # Resource metadata (required)
105
103
  ├── NAME-resource.test.tsx # Unit tests (optional)
106
- ├── NAME-SCENARIO-simulation.json # Simulation data (optional)
107
104
  └── components/ # UI components (optional)
108
105
  ```
109
106
 
110
- Only the resource files (`.tsx` and `.json`) are required to generate a production build and ship a UI. Create the simulation file if you want to preview your resource in `sunpeak dev`.
107
+ Only the resource files (`.tsx` and `.json`) are required to generate a production build and ship a UI.
108
+
109
+ Create the simulation file(s) in `tests/simulations/` if you want to preview your resource in `sunpeak dev`.
111
110
 
112
111
  ## Resources
113
112
 
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://albums-mkixiljp"
15
+ "uri": "ui://albums-mkk7xzee"
16
16
  }
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://carousel-mkixiljp"
15
+ "uri": "ui://carousel-mkk7xzee"
16
16
  }
@@ -18,5 +18,5 @@
18
18
  ]
19
19
  }
20
20
  },
21
- "uri": "ui://map-mkixiljp"
21
+ "uri": "ui://map-mkk7xzee"
22
22
  }
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://review-mkixiljp"
15
+ "uri": "ui://review-mkk7xzee"
16
16
  }
@@ -1,10 +1,9 @@
1
- import {
2
- Check_default,
3
- Copy_default
4
- } from "./chunk-UM3ZGDFR.js";
5
1
  import {
6
2
  useTimeout
7
3
  } from "./chunk-JAGHY6H6.js";
4
+ import {
5
+ o
6
+ } from "./chunk-QPJAV452.js";
8
7
  import {
9
8
  handlePressableMouseEnter,
10
9
  isDev,
@@ -16,12 +15,13 @@ import {
16
15
  toTransformProperty,
17
16
  waitForAnimationFrame
18
17
  } from "./chunk-RYYR2YMB.js";
19
- import {
20
- o
21
- } from "./chunk-QPJAV452.js";
22
18
  import {
23
19
  clsx_default
24
20
  } from "./chunk-CNYJBM5F.js";
21
+ import {
22
+ Check_default,
23
+ Copy_default
24
+ } from "./chunk-UM3ZGDFR.js";
25
25
  import {
26
26
  require_jsx_runtime
27
27
  } from "./chunk-JGVISENQ.js";
@@ -2,6 +2,7 @@ import {
2
2
  handlePressableMouseEnter,
3
3
  waitForAnimationFrame
4
4
  } from "./chunk-6QVG4F2X.js";
5
+ import "./chunk-EGRHWZRV.js";
5
6
  import {
6
7
  useResizeObserver
7
8
  } from "./chunk-JAGHY6H6.js";
@@ -9,7 +10,6 @@ import {
9
10
  dist_exports4 as dist_exports
10
11
  } from "./chunk-2UDYPUBJ.js";
11
12
  import "./chunk-XZTIOEPG.js";
12
- import "./chunk-EGRHWZRV.js";
13
13
  import {
14
14
  clsx_default
15
15
  } from "./chunk-CNYJBM5F.js";
@@ -1,3 +1,6 @@
1
+ import {
2
+ Input
3
+ } from "./chunk-P5LK4A7U.js";
1
4
  import {
2
5
  handlePressableMouseEnter,
3
6
  isDev,
@@ -6,22 +9,19 @@ import {
6
9
  toCssVariables,
7
10
  waitForAnimationFrame
8
11
  } from "./chunk-6QVG4F2X.js";
12
+ import "./chunk-EGRHWZRV.js";
9
13
  import {
10
14
  useTimeout
11
15
  } from "./chunk-JAGHY6H6.js";
16
+ import {
17
+ o
18
+ } from "./chunk-QPJAV452.js";
12
19
  import {
13
20
  dist_exports,
14
21
  dist_exports3 as dist_exports2,
15
22
  dist_exports5 as dist_exports3
16
23
  } from "./chunk-2UDYPUBJ.js";
17
24
  import "./chunk-XZTIOEPG.js";
18
- import {
19
- Input
20
- } from "./chunk-P5LK4A7U.js";
21
- import "./chunk-EGRHWZRV.js";
22
- import {
23
- o
24
- } from "./chunk-QPJAV452.js";
25
25
  import {
26
26
  clsx_default
27
27
  } from "./chunk-CNYJBM5F.js";