sunpeak 0.8.6 → 0.8.8

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 (34) hide show
  1. package/bin/commands/build.mjs +3 -3
  2. package/bin/commands/pull.mjs +2 -2
  3. package/bin/sunpeak.js +4 -6
  4. package/dist/mcp/entry.cjs.map +1 -1
  5. package/dist/mcp/entry.js.map +1 -1
  6. package/dist/style.css +0 -37
  7. package/package.json +1 -1
  8. package/template/.sunpeak/dev.tsx +2 -2
  9. package/template/README.md +5 -5
  10. package/template/dist/albums.js +1 -1
  11. package/template/dist/albums.json +1 -1
  12. package/template/dist/carousel.js +1 -1
  13. package/template/dist/carousel.json +1 -1
  14. package/template/dist/map.js +1 -1
  15. package/template/dist/map.json +1 -1
  16. package/template/dist/review.js +1 -1
  17. package/template/dist/review.json +1 -1
  18. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
  19. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +4 -4
  20. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +20 -20
  21. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
  22. package/template/node_modules/.vite/deps/_metadata.json +35 -35
  23. package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js → chunk-N6DVYEXK.js} +8 -8
  24. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  25. package/template/src/resources/index.ts +4 -4
  26. package/template/src/resources/map-resource.test.tsx +95 -0
  27. package/template/src/resources/review-resource.test.tsx +538 -0
  28. package/template/dist/counter.js +0 -49
  29. package/template/dist/counter.json +0 -15
  30. package/template/src/resources/counter-resource.json +0 -12
  31. package/template/src/resources/counter-resource.test.tsx +0 -116
  32. package/template/src/resources/counter-resource.tsx +0 -101
  33. package/template/src/simulations/counter-show-simulation.json +0 -20
  34. /package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js.map → chunk-N6DVYEXK.js.map} +0 -0
@@ -87,10 +87,10 @@ export async function build(projectRoot = process.cwd()) {
87
87
  const resourceFiles = readdirSync(resourcesDir)
88
88
  .filter(file => file.endsWith('-resource.tsx'))
89
89
  .map(file => {
90
- // Extract kebab-case name: 'counter-resource.tsx' -> 'counter'
90
+ // Extract kebab-case name: 'review-resource.tsx' -> 'review'
91
91
  const kebabName = file.replace('-resource.tsx', '');
92
92
 
93
- // Convert kebab-case to PascalCase: 'counter' -> 'Counter', 'my-widget' -> 'MyWidget'
93
+ // Convert kebab-case to PascalCase: 'review' -> 'Review', 'my-widget' -> 'MyWidget'
94
94
  const pascalName = kebabName
95
95
  .split('-')
96
96
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
@@ -107,7 +107,7 @@ export async function build(projectRoot = process.cwd()) {
107
107
 
108
108
  if (resourceFiles.length === 0) {
109
109
  console.error('Error: No resource files found in src/resources/');
110
- console.error('Resource files should be named like: counter-resource.tsx');
110
+ console.error('Resource files should be named like: review-resource.tsx');
111
111
  process.exit(1);
112
112
  }
113
113
 
@@ -111,7 +111,7 @@ Options:
111
111
 
112
112
  Examples:
113
113
  sunpeak pull -r myorg/my-app -t prod Pull all resources tagged "prod"
114
- sunpeak pull -r myorg/my-app -t prod -n counter Pull only the "counter" resource
114
+ sunpeak pull -r myorg/my-app -t prod -n review Pull only the "review" resource
115
115
  sunpeak pull -r myorg/my-app -t v1.0.0 Pull a specific version
116
116
  `);
117
117
  return;
@@ -247,7 +247,7 @@ Options:
247
247
 
248
248
  Examples:
249
249
  sunpeak pull -r myorg/my-app -t prod Pull all resources tagged "prod"
250
- sunpeak pull -r myorg/my-app -t prod -n counter Pull only the "counter" resource
250
+ sunpeak pull -r myorg/my-app -t prod -n review Pull only the "review" resource
251
251
  sunpeak pull -r myorg/my-app -t v1.0.0 Pull a specific version
252
252
  `);
253
253
  process.exit(0);
package/bin/sunpeak.js CHANGED
@@ -28,7 +28,7 @@ function checkPackageJson() {
28
28
  }
29
29
 
30
30
  function parseResourcesInput(input) {
31
- const VALID_RESOURCES = ['albums', 'carousel', 'counter', 'map', 'review'];
31
+ const VALID_RESOURCES = ['albums', 'carousel', 'map', 'review'];
32
32
 
33
33
  // If no input, return all resources
34
34
  if (!input || input.trim() === '') {
@@ -59,7 +59,6 @@ function updateIndexFiles(targetDir, selectedResources) {
59
59
  const resourceMap = {
60
60
  albums: { component: 'album', resourceClass: 'AlbumsResource' },
61
61
  carousel: { component: 'carousel', resourceClass: 'CarouselResource' },
62
- counter: { component: null, resourceClass: 'CounterResource' },
63
62
  map: { component: 'map', resourceClass: 'MapResource' },
64
63
  review: { component: null, resourceClass: 'ReviewResource' },
65
64
  };
@@ -139,7 +138,7 @@ async function init(projectName, resourcesArg) {
139
138
  console.log(`☀️ 🏔️ Resources: ${resourcesArg}`);
140
139
  } else {
141
140
  resourcesInput = await prompt(
142
- '☀️ 🏔️ Resources (UIs) to include [albums, carousel, counter, map, review]: '
141
+ '☀️ 🏔️ Resources (UIs) to include [albums, carousel, map, review]: '
143
142
  );
144
143
  }
145
144
  const selectedResources = parseResourcesInput(resourcesInput);
@@ -161,7 +160,6 @@ async function init(projectName, resourcesArg) {
161
160
  const resourceComponentMap = {
162
161
  albums: 'album',
163
162
  carousel: 'carousel',
164
- counter: null, // Counter doesn't have a component directory
165
163
  map: 'map',
166
164
  review: null, // Review doesn't have a component directory
167
165
  };
@@ -177,7 +175,7 @@ async function init(projectName, resourcesArg) {
177
175
  }
178
176
 
179
177
  // Filter resource files based on selection
180
- const VALID_RESOURCES = ['albums', 'carousel', 'counter', 'map', 'review'];
178
+ const VALID_RESOURCES = ['albums', 'carousel', 'map', 'review'];
181
179
  const excludedResources = VALID_RESOURCES.filter((r) => !selectedResources.includes(r));
182
180
 
183
181
  for (const resource of excludedResources) {
@@ -416,7 +414,7 @@ Usage:
416
414
  sunpeak upgrade Upgrade sunpeak to latest version
417
415
  sunpeak --version Show version number
418
416
 
419
- Resources: albums, carousel, counter, map, review (comma/space separated)
417
+ Resources: albums, carousel, map, review (comma/space separated)
420
418
  Example: sunpeak new my-app "albums,carousel"
421
419
 
422
420
  For more information, visit: https://sunpeak.ai/
@@ -1 +1 @@
1
- {"version":3,"file":"entry.cjs","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * e.g., 'albums-show' matches 'albums' (not 'album' if both exist)\n */\nfunction findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'counter-resource.json' -> 'counter'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":["readFileSync","readdirSync","runMCPServer"],"mappings":";;;;;AAeA,MAAM,cAAc,QAAQ,IAAA;AAO5B,SAAS,gBAAgB,eAAuB,cAA4C;AAE1F,QAAM,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACnE,aAAW,eAAe,QAAQ;AAChC,QAAI,kBAAkB,eAAe,cAAc,WAAW,cAAc,GAAG,GAAG;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAMA,GAAAA,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgBC,eAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAMD,GAAAA,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkBC,eAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAMD,GAAAA,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAc,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEAE,sBAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
1
+ {"version":3,"file":"entry.cjs","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * e.g., 'albums-show' matches 'albums' (not 'album' if both exist)\n */\nfunction findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'review-resource.json' -> 'review'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":["readFileSync","readdirSync","runMCPServer"],"mappings":";;;;;AAeA,MAAM,cAAc,QAAQ,IAAA;AAO5B,SAAS,gBAAgB,eAAuB,cAA4C;AAE1F,QAAM,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACnE,aAAW,eAAe,QAAQ;AAChC,QAAI,kBAAkB,eAAe,cAAc,WAAW,cAAc,GAAG,GAAG;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAMA,GAAAA,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgBC,eAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAMD,GAAAA,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkBC,eAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAMD,GAAAA,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAc,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEAE,sBAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
@@ -1 +1 @@
1
- {"version":3,"file":"entry.js","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * e.g., 'albums-show' matches 'albums' (not 'album' if both exist)\n */\nfunction findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'counter-resource.json' -> 'counter'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":[],"mappings":";;;;AAeA,MAAM,cAAc,QAAQ,IAAA;AAO5B,SAAS,gBAAgB,eAAuB,cAA4C;AAE1F,QAAM,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACnE,aAAW,eAAe,QAAQ;AAChC,QAAI,kBAAkB,eAAe,cAAc,WAAW,cAAc,GAAG,GAAG;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgB,YAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkB,YAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAM,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAc,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEA,eAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
1
+ {"version":3,"file":"entry.js","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * e.g., 'albums-show' matches 'albums' (not 'album' if both exist)\n */\nfunction findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'review-resource.json' -> 'review'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":[],"mappings":";;;;AAeA,MAAM,cAAc,QAAQ,IAAA;AAO5B,SAAS,gBAAgB,eAAuB,cAA4C;AAE1F,QAAM,SAAS,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACnE,aAAW,eAAe,QAAQ;AAChC,QAAI,kBAAkB,eAAe,cAAc,WAAW,cAAc,GAAG,GAAG;AAChF,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgB,YAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkB,YAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAM,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAc,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEA,eAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
package/dist/style.css CHANGED
@@ -80,7 +80,6 @@
80
80
  --breakpoint-lg: 1024px;
81
81
  --breakpoint-xl: 1280px;
82
82
  --breakpoint-2xl: 1536px;
83
- --container-md: 28rem;
84
83
  --leading-tight: 1.25;
85
84
  --leading-normal: 1.5;
86
85
  --blur-sm: 8px;
@@ -127,14 +126,6 @@
127
126
  --text-2xl--line-height: var(--font-heading-lg-line-height);
128
127
  --text-2xl--font-weight: var(--font-text-lg-weight);
129
128
  --text-2xl--letter-spacing: var(--font-heading-lg-tracking);
130
- --text-3xl: var(--font-heading-xl-size);
131
- --text-3xl--line-height: var(--font-heading-xl-line-height);
132
- --text-3xl--font-weight: var(--font-text-lg-weight);
133
- --text-3xl--letter-spacing: var(--font-heading-xl-tracking);
134
- --text-6xl: var(--font-heading-4xl-size);
135
- --text-6xl--line-height: var(--font-heading-4xl-line-height);
136
- --text-6xl--font-weight: var(--font-text-lg-weight);
137
- --text-6xl--letter-spacing: var(--font-heading-4xl-tracking);
138
129
  --tracking-wide: var(--font-tracking-wide);
139
130
  --tracking-normal: var(--font-tracking-normal);
140
131
  --tracking-tight: var(--font-tracking-tight);
@@ -3575,10 +3566,6 @@
3575
3566
  max-width: 100%;
3576
3567
  }
3577
3568
 
3578
- .max-w-md {
3579
- max-width: var(--container-md);
3580
- }
3581
-
3582
3569
  .min-w-0 {
3583
3570
  min-width: calc(var(--spacing) * 0);
3584
3571
  }
@@ -3725,12 +3712,6 @@
3725
3712
  margin-block-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)));
3726
3713
  }
3727
3714
 
3728
- :where(.space-y-6 > :not(:last-child)) {
3729
- --tw-space-y-reverse: 0;
3730
- margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
3731
- margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
3732
- }
3733
-
3734
3715
  :where(.divide-y > :not(:last-child)) {
3735
3716
  --tw-divide-y-reverse: 0;
3736
3717
  border-bottom-style: var(--tw-border-style);
@@ -3996,10 +3977,6 @@
3996
3977
  padding: calc(var(--spacing) * 5);
3997
3978
  }
3998
3979
 
3999
- .p-8 {
4000
- padding: calc(var(--spacing) * 8);
4001
- }
4002
-
4003
3980
  .p-\[1px\] {
4004
3981
  padding: 1px;
4005
3982
  }
@@ -4103,20 +4080,6 @@
4103
4080
  font-weight: var(--tw-font-weight, var(--text-2xl--font-weight));
4104
4081
  }
4105
4082
 
4106
- .text-3xl {
4107
- font-size: var(--text-3xl);
4108
- line-height: var(--tw-leading, var(--text-3xl--line-height));
4109
- letter-spacing: var(--tw-tracking, var(--text-3xl--letter-spacing));
4110
- font-weight: var(--tw-font-weight, var(--text-3xl--font-weight));
4111
- }
4112
-
4113
- .text-6xl {
4114
- font-size: var(--text-6xl);
4115
- line-height: var(--tw-leading, var(--text-6xl--line-height));
4116
- letter-spacing: var(--tw-tracking, var(--text-6xl--letter-spacing));
4117
- font-weight: var(--tw-font-weight, var(--text-6xl--font-weight));
4118
- }
4119
-
4120
4083
  .text-base {
4121
4084
  font-size: var(--text-base);
4122
4085
  line-height: var(--tw-leading, var(--text-base--line-height));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "The ChatGPT App framework. Quickstart, build, & test your ChatGPT App locally!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -23,7 +23,7 @@ const resourceModules = import.meta.glob('../src/resources/*-resource.json', { e
23
23
  type ResourceData = { name: string; [key: string]: unknown };
24
24
  const resourcesMap = new Map<string, ResourceData>();
25
25
  for (const [path, module] of Object.entries(resourceModules)) {
26
- // Extract key from path: '../src/resources/counter-resource.json' -> 'counter'
26
+ // Extract key from path: '../src/resources/review-resource.json' -> 'review'
27
27
  const match = path.match(/\/([^/]+)-resource\.json$/);
28
28
  const key = match?.[1];
29
29
  if (key) {
@@ -52,7 +52,7 @@ function findResourceKey(simulationKey: string): string | undefined {
52
52
 
53
53
  /**
54
54
  * Convert resource name to component name
55
- * Example: 'carousel' -> 'CarouselResource', 'counter' -> 'CounterResource'
55
+ * Example: 'carousel' -> 'CarouselResource', 'review' -> 'ReviewResource'
56
56
  */
57
57
  function getResourceComponent(name: string): React.ComponentType {
58
58
  const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
@@ -30,8 +30,8 @@ The template includes a minimal test setup with Vitest. You can add additional t
30
30
 
31
31
  - `src/resources/` - Resource files must be here
32
32
  - `src/simulations/` - Simulation files must be here
33
- - Resource file naming: `*-resource.tsx` (e.g., `counter-resource.tsx`)
34
- - Simulation file naming: `*-simulation.tsx` (e.g., `counter-simulation.tsx`)
33
+ - Resource file naming: `*-resource.tsx` (e.g., `review-resource.tsx`)
34
+ - Simulation file naming: `*-simulation.json` (e.g., `review-purchase-simulation.json`)
35
35
  - `src/index-resource.tsx` - Build template (must have `// RESOURCE_IMPORT` and `// RESOURCE_MOUNT` comments)
36
36
 
37
37
  **You can customize:**
@@ -71,7 +71,7 @@ ngrok http 6766
71
71
 
72
72
  You can then connect to the tunnel forwarding URL at the `/mcp` path from ChatGPT **in developer mode** to see your UI in action: `User > Settings > Apps & Connectors > Create`
73
73
 
74
- Once your app is connected, send the name of the app and a tool, like `/sunpeak show counter`, to ChatGPT.
74
+ Once your app is connected, send the name of the app and a tool, like `/sunpeak show review`, to ChatGPT.
75
75
 
76
76
  When you make changes to the UI, refresh your app in ChatGPT after the MCP server has finished rebuilding your app: `User > Settings > Apps & Connectors > My App > Refresh`
77
77
 
@@ -87,8 +87,8 @@ This creates optimized builds in `dist/`:
87
87
 
88
88
  - `dist/albums.js`
89
89
  - `dist/albums.json`
90
- - `dist/counter.js`
91
- - `dist/counter.json`
90
+ - `dist/review.js`
91
+ - `dist/review.json`
92
92
  - _(One .js file per resource in src/resources/)_
93
93
  - _(One .json file per resource in src/resources/)_
94
94