sunpeak 0.9.12 → 0.10.3

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 (92) hide show
  1. package/README.md +2 -2
  2. package/bin/commands/build.mjs +56 -30
  3. package/bin/commands/deploy.mjs +17 -17
  4. package/bin/commands/push.mjs +115 -64
  5. package/bin/lib/patterns.mjs +40 -0
  6. package/bin/sunpeak.js +50 -106
  7. package/dist/index.cjs +149 -11
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +165 -27
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/discovery.d.ts +76 -13
  12. package/dist/mcp/entry.cjs +24 -27
  13. package/dist/mcp/entry.cjs.map +1 -1
  14. package/dist/mcp/entry.js +25 -28
  15. package/dist/mcp/entry.js.map +1 -1
  16. package/package.json +1 -1
  17. package/template/.sunpeak/dev.tsx +5 -5
  18. package/template/README.md +54 -50
  19. package/template/dist/{albums.json → albums/albums.json} +1 -1
  20. package/template/dist/{carousel.json → carousel/carousel.json} +1 -1
  21. package/template/dist/{map.json → map/map.json} +1 -1
  22. package/template/dist/{review.json → review/review.json} +1 -1
  23. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
  24. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +11 -11
  25. package/template/node_modules/.vite/deps/_metadata.json +26 -26
  26. package/template/node_modules/.vite/deps/{chunk-N6DVYEXK.js → chunk-LVZJNEGE.js} +7 -7
  27. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  28. package/template/src/resources/{albums-resource.test.tsx → albums/albums-resource.test.tsx} +1 -1
  29. package/template/src/resources/{albums-resource.tsx → albums/albums-resource.tsx} +1 -1
  30. package/template/src/resources/albums/albums-show-simulation.json +131 -0
  31. package/template/src/{components/album → resources/albums/components}/album-card.tsx +1 -1
  32. package/template/src/{components/album → resources/albums/components}/album-carousel.tsx +1 -1
  33. package/template/src/{components/album → resources/albums/components}/film-strip.tsx +1 -1
  34. package/template/src/{components/album → resources/albums/components}/fullscreen-viewer.tsx +1 -1
  35. package/template/src/resources/{carousel-resource.test.tsx → carousel/carousel-resource.test.tsx} +1 -1
  36. package/template/src/resources/{carousel-resource.tsx → carousel/carousel-resource.tsx} +1 -1
  37. package/template/src/resources/carousel/carousel-show-simulation.json +68 -0
  38. package/template/src/{components/carousel → resources/carousel/components}/card.tsx +1 -1
  39. package/template/src/{components/carousel → resources/carousel/components}/carousel.tsx +1 -1
  40. package/template/src/resources/index.ts +5 -5
  41. package/template/src/{components/map → resources/map/components}/map-view.tsx +1 -1
  42. package/template/src/{components/map → resources/map/components}/map.tsx +1 -1
  43. package/template/src/{components/map → resources/map/components}/place-card.tsx +1 -1
  44. package/template/src/{components/map → resources/map/components}/place-carousel.tsx +1 -1
  45. package/template/src/{components/map → resources/map/components}/place-inspector.tsx +1 -1
  46. package/template/src/{components/map → resources/map/components}/place-list.tsx +1 -1
  47. package/template/src/resources/{map-resource.test.tsx → map/map-resource.test.tsx} +1 -1
  48. package/template/src/resources/{map-resource.tsx → map/map-resource.tsx} +1 -1
  49. package/template/src/resources/map/map-show-simulation.json +123 -0
  50. package/template/src/resources/review/review-diff-simulation.json +80 -0
  51. package/template/src/resources/review/review-post-simulation.json +56 -0
  52. package/template/src/resources/review/review-purchase-simulation.json +88 -0
  53. package/dist/discovery-a4WId9PC.cjs +0 -125
  54. package/dist/discovery-a4WId9PC.cjs.map +0 -1
  55. package/dist/discovery-ft3cd2dW.js +0 -126
  56. package/dist/discovery-ft3cd2dW.js.map +0 -1
  57. package/template/src/components/index.ts +0 -3
  58. package/template/src/simulations/index.ts +0 -16
  59. /package/template/{src/simulations → dist/albums}/albums-show-simulation.json +0 -0
  60. /package/template/dist/{albums.js → albums/albums.js} +0 -0
  61. /package/template/{src/simulations → dist/carousel}/carousel-show-simulation.json +0 -0
  62. /package/template/dist/{carousel.js → carousel/carousel.js} +0 -0
  63. /package/template/{src/simulations → dist/map}/map-show-simulation.json +0 -0
  64. /package/template/dist/{map.js → map/map.js} +0 -0
  65. /package/template/{src/simulations → dist/review}/review-diff-simulation.json +0 -0
  66. /package/template/{src/simulations → dist/review}/review-post-simulation.json +0 -0
  67. /package/template/{src/simulations → dist/review}/review-purchase-simulation.json +0 -0
  68. /package/template/dist/{review.js → review/review.js} +0 -0
  69. /package/template/node_modules/.vite/deps/{chunk-N6DVYEXK.js.map → chunk-LVZJNEGE.js.map} +0 -0
  70. /package/template/src/resources/{albums-resource.json → albums/albums-resource.json} +0 -0
  71. /package/template/src/{components/album → resources/albums/components}/album-card.test.tsx +0 -0
  72. /package/template/src/{components/album → resources/albums/components}/album-carousel.test.tsx +0 -0
  73. /package/template/src/{components/album → resources/albums/components}/albums.test.tsx +0 -0
  74. /package/template/src/{components/album → resources/albums/components}/albums.tsx +0 -0
  75. /package/template/src/{components/album → resources/albums/components}/film-strip.test.tsx +0 -0
  76. /package/template/src/{components/album → resources/albums/components}/fullscreen-viewer.test.tsx +0 -0
  77. /package/template/src/{components/album → resources/albums/components}/index.ts +0 -0
  78. /package/template/src/resources/{carousel-resource.json → carousel/carousel-resource.json} +0 -0
  79. /package/template/src/{components/carousel → resources/carousel/components}/card.test.tsx +0 -0
  80. /package/template/src/{components/carousel → resources/carousel/components}/carousel.test.tsx +0 -0
  81. /package/template/src/{components/carousel → resources/carousel/components}/index.ts +0 -0
  82. /package/template/src/{components/map → resources/map/components}/index.ts +0 -0
  83. /package/template/src/{components/map → resources/map/components}/map-view.test.tsx +0 -0
  84. /package/template/src/{components/map → resources/map/components}/place-card.test.tsx +0 -0
  85. /package/template/src/{components/map → resources/map/components}/place-carousel.test.tsx +0 -0
  86. /package/template/src/{components/map → resources/map/components}/place-inspector.test.tsx +0 -0
  87. /package/template/src/{components/map → resources/map/components}/place-list.test.tsx +0 -0
  88. /package/template/src/{components/map → resources/map/components}/types.ts +0 -0
  89. /package/template/src/resources/{map-resource.json → map/map-resource.json} +0 -0
  90. /package/template/src/resources/{review-resource.json → review/review-resource.json} +0 -0
  91. /package/template/src/resources/{review-resource.test.tsx → review/review-resource.test.tsx} +0 -0
  92. /package/template/src/resources/{review-resource.tsx → review/review-resource.tsx} +0 -0
@@ -35,25 +35,16 @@ type GlobModules = Record<string, unknown>;
35
35
  * Extracts components and exports them with PascalCase names.
36
36
  *
37
37
  * @example
38
- * const modules = import.meta.glob('./*-resource.tsx', { eager: true });
38
+ * const modules = import.meta.glob('./**\/*-resource.tsx', { eager: true });
39
39
  * export default createResourceExports(modules);
40
40
  */
41
41
  export declare function createResourceExports(modules: GlobModules): Record<string, React.ComponentType>;
42
- /**
43
- * Process simulation modules from import.meta.glob() result.
44
- * Builds a map of simulation key -> simulation data.
45
- *
46
- * @example
47
- * const modules = import.meta.glob('./*-simulation.json', { eager: true });
48
- * export const SIMULATIONS = createSimulationIndex(modules);
49
- */
50
- export declare function createSimulationIndex(modules: GlobModules): Record<string, unknown>;
51
42
  /**
52
43
  * Build a resource metadata map from import.meta.glob() result.
53
44
  * Used for connecting simulations to their resource definitions.
54
45
  *
55
46
  * @example
56
- * const modules = import.meta.glob('../src/resources/*-resource.json', { eager: true });
47
+ * const modules = import.meta.glob('../src/resources/**\/*-resource.json', { eager: true });
57
48
  * const resourcesMap = buildResourceMap(modules);
58
49
  */
59
50
  export declare function buildResourceMap<T>(modules: GlobModules): Map<string, T>;
@@ -101,10 +92,82 @@ export interface BuildDevSimulationsOptions {
101
92
  *
102
93
  * @example
103
94
  * const simulations = buildDevSimulations({
104
- * simulationModules: import.meta.glob('../src/simulations/*-simulation.json', { eager: true }),
105
- * resourceModules: import.meta.glob('../src/resources/*-resource.json', { eager: true }),
95
+ * simulationModules: import.meta.glob('../src/resources/**\/*-simulation.json', { eager: true }),
96
+ * resourceModules: import.meta.glob('../src/resources/**\/*-resource.json', { eager: true }),
106
97
  * resourceComponents: resourceComponents,
107
98
  * });
108
99
  */
109
100
  export declare function buildDevSimulations(options: BuildDevSimulationsOptions): Record<string, Simulation>;
101
+ /**
102
+ * Information about a discovered resource directory
103
+ */
104
+ export interface ResourceDirInfo {
105
+ /** Resource key (directory name), e.g., 'albums', 'carousel' */
106
+ key: string;
107
+ /** Full path to the resource directory */
108
+ dir: string;
109
+ /** Full path to the main resource file (tsx or json depending on context) */
110
+ resourcePath: string;
111
+ }
112
+ /**
113
+ * File system operations interface for dependency injection in tests
114
+ */
115
+ export interface FsOps {
116
+ readdirSync: (path: string, options: {
117
+ withFileTypes: true;
118
+ }) => Array<{
119
+ name: string;
120
+ isDirectory: () => boolean;
121
+ }>;
122
+ existsSync: (path: string) => boolean;
123
+ }
124
+ /**
125
+ * Find all resource directories in a base directory.
126
+ * Each valid resource directory contains a file matching the expected pattern.
127
+ *
128
+ * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')
129
+ * @param filePattern - Function to generate expected filename from resource key
130
+ * @param fs - File system operations (for testing)
131
+ *
132
+ * @example
133
+ * // Find source resources (tsx files)
134
+ * const resources = findResourceDirs('src/resources', key => `${key}-resource.tsx`);
135
+ *
136
+ * @example
137
+ * // Find built resources (js files)
138
+ * const resources = findResourceDirs('dist', key => `${key}.js`);
139
+ */
140
+ export declare function findResourceDirs(baseDir: string, filePattern: (key: string) => string, fs: FsOps): ResourceDirInfo[];
141
+ /**
142
+ * Check if a filename is a simulation file for a given resource.
143
+ * Matches pattern: {resourceKey}-*-simulation.json
144
+ *
145
+ * @example
146
+ * isSimulationFile('albums-show-simulation.json', 'albums') // true
147
+ * isSimulationFile('albums-show-simulation.json', 'carousel') // false
148
+ * isSimulationFile('albums-resource.json', 'albums') // false
149
+ */
150
+ export declare function isSimulationFile(filename: string, resourceKey: string): boolean;
151
+ /**
152
+ * Extract the simulation name from a simulation filename.
153
+ * Given "{resourceKey}-{name}-simulation.json", returns "{name}".
154
+ *
155
+ * @example
156
+ * extractSimulationName('albums-show-simulation.json', 'albums') // 'show'
157
+ * extractSimulationName('carousel-hero-simulation.json', 'carousel') // 'hero'
158
+ */
159
+ export declare function extractSimulationName(filename: string, resourceKey: string): string;
160
+ /**
161
+ * Find all simulation files in a resource directory.
162
+ *
163
+ * @param resourceDir - Path to the resource directory
164
+ * @param resourceKey - Resource key (e.g., 'albums')
165
+ * @param fs - File system operations (for testing)
166
+ * @returns Array of { filename, name } objects
167
+ */
168
+ export declare function findSimulationFiles(resourceDir: string, resourceKey: string, fs: Pick<FsOps, 'readdirSync' | 'existsSync'>): Array<{
169
+ filename: string;
170
+ name: string;
171
+ path: string;
172
+ }>;
110
173
  export {};
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  const server = require("../server-CSybLAYo.cjs");
4
- const discovery = require("../discovery-a4WId9PC.cjs");
5
4
  const path = require("path");
6
5
  const fs = require("fs");
7
6
  const projectRoot = process.cwd();
@@ -9,36 +8,34 @@ async function startServer() {
9
8
  const pkgPath = path.join(projectRoot, "package.json");
10
9
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
11
10
  const resourcesDir = path.join(projectRoot, "src/resources");
12
- const resourceFiles = fs.readdirSync(resourcesDir).filter((f) => f.endsWith("-resource.json"));
11
+ const resourceDirs = fs.readdirSync(resourcesDir, { withFileTypes: true }).filter(
12
+ (entry) => entry.isDirectory()
13
+ );
13
14
  const resourcesMap = /* @__PURE__ */ new Map();
14
- for (const filename of resourceFiles) {
15
- const key = filename.replace(/-resource\.json$/, "");
16
- const resourcePath = path.join(resourcesDir, filename);
17
- const resource = JSON.parse(fs.readFileSync(resourcePath, "utf-8"));
18
- resourcesMap.set(key, resource);
19
- }
20
- const resourceKeys = Array.from(resourcesMap.keys());
21
- const simulationsDir = path.join(projectRoot, "src/simulations");
22
- const simulationFiles = fs.readdirSync(simulationsDir).filter((f) => f.endsWith("-simulation.json"));
23
15
  const simulations = [];
24
- for (const filename of simulationFiles) {
25
- const simulationKey = filename.replace(/-simulation\.json$/, "");
26
- const simulationPath = path.join(simulationsDir, filename);
27
- const simulation = JSON.parse(fs.readFileSync(simulationPath, "utf-8"));
28
- const resourceKey = discovery.findResourceKey(simulationKey, resourceKeys);
29
- if (!resourceKey) {
30
- console.warn(
31
- `No matching resource found for simulation "${simulationKey}". Expected a resource file like src/resources/${simulationKey.split("-")[0]}-resource.json`
32
- );
16
+ for (const entry of resourceDirs) {
17
+ const resourceKey = entry.name;
18
+ const resourceDir = path.join(resourcesDir, resourceKey);
19
+ const resourcePath = path.join(resourceDir, `${resourceKey}-resource.json`);
20
+ if (!fs.existsSync(resourcePath)) {
33
21
  continue;
34
22
  }
35
- const resource = resourcesMap.get(resourceKey);
36
- simulations.push({
37
- ...simulation,
38
- name: simulationKey,
39
- distPath: path.join(projectRoot, `dist/${resourceKey}.js`),
40
- resource
41
- });
23
+ const resource = JSON.parse(fs.readFileSync(resourcePath, "utf-8"));
24
+ resourcesMap.set(resourceKey, resource);
25
+ const dirFiles = fs.readdirSync(resourceDir);
26
+ for (const file of dirFiles) {
27
+ if (file.endsWith("-simulation.json")) {
28
+ const simulationKey = file.replace(/-simulation\.json$/, "");
29
+ const simulationPath = path.join(resourceDir, file);
30
+ const simulation = JSON.parse(fs.readFileSync(simulationPath, "utf-8"));
31
+ simulations.push({
32
+ ...simulation,
33
+ name: simulationKey,
34
+ distPath: path.join(projectRoot, `dist/${resourceKey}/${resourceKey}.js`),
35
+ resource
36
+ });
37
+ }
38
+ }
42
39
  }
43
40
  server.runMCPServer({
44
41
  name: pkg.name || "Sunpeak",
@@ -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 { findResourceKey } from '../lib/discovery.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\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 name: simulationKey,\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","findResourceKey","runMCPServer"],"mappings":";;;;;;AAgBA,MAAM,cAAc,QAAQ,IAAA;AAE5B,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,cAAcE,UAAAA,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,MAAM;AAAA,MACN,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEAC,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 * - resources/{resource}/{resource}-{scenario}-simulation.json (e.g., resources/albums/albums-show-simulation.json)\n * - resources/{resource}/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { existsSync, 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\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 resources and simulations from subdirectories\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceDirs = readdirSync(resourcesDir, { withFileTypes: true }).filter((entry) =>\n entry.isDirectory()\n );\n\n const resourcesMap = new Map<string, Resource>();\n const simulations: SimulationWithDist[] = [];\n\n for (const entry of resourceDirs) {\n const resourceKey = entry.name;\n const resourceDir = path.join(resourcesDir, resourceKey);\n const resourcePath = path.join(resourceDir, `${resourceKey}-resource.json`);\n\n // Skip directories without a resource file\n if (!existsSync(resourcePath)) {\n continue;\n }\n\n // Load resource\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(resourceKey, resource);\n\n // Discover simulation files in the same directory\n const dirFiles = readdirSync(resourceDir);\n for (const file of dirFiles) {\n if (file.endsWith('-simulation.json')) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = file.replace(/-simulation\\.json$/, '');\n const simulationPath = path.join(resourceDir, file);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n simulations.push({\n ...simulation,\n name: simulationKey,\n distPath: path.join(projectRoot, `dist/${resourceKey}/${resourceKey}.js`),\n resource,\n });\n }\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","existsSync","runMCPServer"],"mappings":";;;;;AAeA,MAAM,cAAc,QAAQ,IAAA;AAE5B,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,eAAeC,GAAAA,YAAY,cAAc,EAAE,eAAe,KAAA,CAAM,EAAE;AAAA,IAAO,CAAC,UAC9E,MAAM,YAAA;AAAA,EAAY;AAGpB,QAAM,mCAAmB,IAAA;AACzB,QAAM,cAAoC,CAAA;AAE1C,aAAW,SAAS,cAAc;AAChC,UAAM,cAAc,MAAM;AAC1B,UAAM,cAAc,KAAK,KAAK,cAAc,WAAW;AACvD,UAAM,eAAe,KAAK,KAAK,aAAa,GAAG,WAAW,gBAAgB;AAG1E,QAAI,CAACC,GAAAA,WAAW,YAAY,GAAG;AAC7B;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,MAAMF,GAAAA,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,aAAa,QAAQ;AAGtC,UAAM,WAAWC,GAAAA,YAAY,WAAW;AACxC,eAAW,QAAQ,UAAU;AAC3B,UAAI,KAAK,SAAS,kBAAkB,GAAG;AAErC,cAAM,gBAAgB,KAAK,QAAQ,sBAAsB,EAAE;AAC3D,cAAM,iBAAiB,KAAK,KAAK,aAAa,IAAI;AAClD,cAAM,aAAa,KAAK,MAAMD,GAAAA,aAAa,gBAAgB,OAAO,CAAC;AAEnE,oBAAY,KAAK;AAAA,UACf,GAAG;AAAA,UACH,MAAM;AAAA,UACN,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,IAAI,WAAW,KAAK;AAAA,UACxE;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEAG,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;"}
package/dist/mcp/entry.js CHANGED
@@ -1,43 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  import { r as runMCPServer } from "../server-310A1k9o.js";
3
- import { f as findResourceKey } from "../discovery-ft3cd2dW.js";
4
3
  import path from "path";
5
- import { readFileSync, readdirSync } from "fs";
4
+ import { readFileSync, readdirSync, existsSync } from "fs";
6
5
  const projectRoot = process.cwd();
7
6
  async function startServer() {
8
7
  const pkgPath = path.join(projectRoot, "package.json");
9
8
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
10
9
  const resourcesDir = path.join(projectRoot, "src/resources");
11
- const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith("-resource.json"));
10
+ const resourceDirs = readdirSync(resourcesDir, { withFileTypes: true }).filter(
11
+ (entry) => entry.isDirectory()
12
+ );
12
13
  const resourcesMap = /* @__PURE__ */ new Map();
13
- for (const filename of resourceFiles) {
14
- const key = filename.replace(/-resource\.json$/, "");
15
- const resourcePath = path.join(resourcesDir, filename);
16
- const resource = JSON.parse(readFileSync(resourcePath, "utf-8"));
17
- resourcesMap.set(key, resource);
18
- }
19
- const resourceKeys = Array.from(resourcesMap.keys());
20
- const simulationsDir = path.join(projectRoot, "src/simulations");
21
- const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith("-simulation.json"));
22
14
  const simulations = [];
23
- for (const filename of simulationFiles) {
24
- const simulationKey = filename.replace(/-simulation\.json$/, "");
25
- const simulationPath = path.join(simulationsDir, filename);
26
- const simulation = JSON.parse(readFileSync(simulationPath, "utf-8"));
27
- const resourceKey = findResourceKey(simulationKey, resourceKeys);
28
- if (!resourceKey) {
29
- console.warn(
30
- `No matching resource found for simulation "${simulationKey}". Expected a resource file like src/resources/${simulationKey.split("-")[0]}-resource.json`
31
- );
15
+ for (const entry of resourceDirs) {
16
+ const resourceKey = entry.name;
17
+ const resourceDir = path.join(resourcesDir, resourceKey);
18
+ const resourcePath = path.join(resourceDir, `${resourceKey}-resource.json`);
19
+ if (!existsSync(resourcePath)) {
32
20
  continue;
33
21
  }
34
- const resource = resourcesMap.get(resourceKey);
35
- simulations.push({
36
- ...simulation,
37
- name: simulationKey,
38
- distPath: path.join(projectRoot, `dist/${resourceKey}.js`),
39
- resource
40
- });
22
+ const resource = JSON.parse(readFileSync(resourcePath, "utf-8"));
23
+ resourcesMap.set(resourceKey, resource);
24
+ const dirFiles = readdirSync(resourceDir);
25
+ for (const file of dirFiles) {
26
+ if (file.endsWith("-simulation.json")) {
27
+ const simulationKey = file.replace(/-simulation\.json$/, "");
28
+ const simulationPath = path.join(resourceDir, file);
29
+ const simulation = JSON.parse(readFileSync(simulationPath, "utf-8"));
30
+ simulations.push({
31
+ ...simulation,
32
+ name: simulationKey,
33
+ distPath: path.join(projectRoot, `dist/${resourceKey}/${resourceKey}.js`),
34
+ resource
35
+ });
36
+ }
37
+ }
41
38
  }
42
39
  runMCPServer({
43
40
  name: pkg.name || "Sunpeak",
@@ -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 { findResourceKey } from '../lib/discovery.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\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 name: simulationKey,\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":";;;;;AAgBA,MAAM,cAAc,QAAQ,IAAA;AAE5B,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,MAAM;AAAA,MACN,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 * - resources/{resource}/{resource}-{scenario}-simulation.json (e.g., resources/albums/albums-show-simulation.json)\n * - resources/{resource}/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { existsSync, 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\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 resources and simulations from subdirectories\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceDirs = readdirSync(resourcesDir, { withFileTypes: true }).filter((entry) =>\n entry.isDirectory()\n );\n\n const resourcesMap = new Map<string, Resource>();\n const simulations: SimulationWithDist[] = [];\n\n for (const entry of resourceDirs) {\n const resourceKey = entry.name;\n const resourceDir = path.join(resourcesDir, resourceKey);\n const resourcePath = path.join(resourceDir, `${resourceKey}-resource.json`);\n\n // Skip directories without a resource file\n if (!existsSync(resourcePath)) {\n continue;\n }\n\n // Load resource\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(resourceKey, resource);\n\n // Discover simulation files in the same directory\n const dirFiles = readdirSync(resourceDir);\n for (const file of dirFiles) {\n if (file.endsWith('-simulation.json')) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = file.replace(/-simulation\\.json$/, '');\n const simulationPath = path.join(resourceDir, file);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n simulations.push({\n ...simulation,\n name: simulationKey,\n distPath: path.join(projectRoot, `dist/${resourceKey}/${resourceKey}.js`),\n resource,\n });\n }\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;AAE5B,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,eAAe,YAAY,cAAc,EAAE,eAAe,KAAA,CAAM,EAAE;AAAA,IAAO,CAAC,UAC9E,MAAM,YAAA;AAAA,EAAY;AAGpB,QAAM,mCAAmB,IAAA;AACzB,QAAM,cAAoC,CAAA;AAE1C,aAAW,SAAS,cAAc;AAChC,UAAM,cAAc,MAAM;AAC1B,UAAM,cAAc,KAAK,KAAK,cAAc,WAAW;AACvD,UAAM,eAAe,KAAK,KAAK,aAAa,GAAG,WAAW,gBAAgB;AAG1E,QAAI,CAAC,WAAW,YAAY,GAAG;AAC7B;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,aAAa,QAAQ;AAGtC,UAAM,WAAW,YAAY,WAAW;AACxC,eAAW,QAAQ,UAAU;AAC3B,UAAI,KAAK,SAAS,kBAAkB,GAAG;AAErC,cAAM,gBAAgB,KAAK,QAAQ,sBAAsB,EAAE;AAC3D,cAAM,iBAAiB,KAAK,KAAK,aAAa,IAAI;AAClD,cAAM,aAAa,KAAK,MAAM,aAAa,gBAAgB,OAAO,CAAC;AAEnE,oBAAY,KAAK;AAAA,UACf,GAAG;AAAA,UACH,MAAM;AAAA,UACN,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,IAAI,WAAW,KAAK;AAAA,UACxE;AAAA,QAAA,CACD;AAAA,MACH;AAAA,IACF;AAAA,EACF;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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.9.12",
3
+ "version": "0.10.3",
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
- * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)
8
- * - resources/{resource}-resource.json
9
- * - resources/{Resource}Resource component (PascalCase)
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)
10
10
  */
11
11
  import { StrictMode } from 'react';
12
12
  import { createRoot } from 'react-dom/client';
@@ -16,8 +16,8 @@ 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/simulations/*-simulation.json', { eager: true }),
20
- resourceModules: import.meta.glob('../src/resources/*-resource.json', { eager: true }),
19
+ simulationModules: import.meta.glob('../src/resources/*/*-simulation.json', { eager: true }),
20
+ resourceModules: import.meta.glob('../src/resources/*/*-resource.json', { eager: true }),
21
21
  resourceComponents: resourceComponents as Record<string, React.ComponentType>,
22
22
  });
23
23
 
@@ -16,48 +16,34 @@ That's it! Edit the resource files in [./src/resources/](./src/resources/) to bu
16
16
  ## Commands
17
17
 
18
18
  ```bash
19
- sunpeak dev # Start development server
20
- sunpeak build # Build all resources for production
21
- sunpeak mcp # Start MCP server with auto-reload
22
- pnpm test # Run tests with Vitest
19
+ # Core commands:
20
+ pnpm test # Run tests with Vitest.
21
+ pnpm test:e2e # Run end-to-end tests with Playwright.
22
+ sunpeak dev # Start development server.
23
+ sunpeak build # Build all resources for production.
24
+ sunpeak mcp # Start MCP server for ChatGPT testing with mock data.
25
+
26
+ # sunpeak repository (think ECR for ChatGPT Apps):
27
+ sunpeak login # Authenticate with the sunpeak repository.
28
+ sunpeak push # Push built resources to the sunpeak repository.
29
+ sunpeak pull # Pull built resources from the sunpeak repository (for your prod MCP server).
23
30
  ```
24
31
 
25
32
  The template includes a minimal test setup with Vitest. You can add additional tooling (linting, formatting, type-checking) as needed for your project.
26
33
 
27
- ### Customization
34
+ ## Project Structure
28
35
 
29
- **Do not customize (required by `sunpeak` commands):**
36
+ - `src/resources/` - Directory containing all your MCP Resources (ChatGPT App UIs).
37
+ - Each resource is a subdirectory containing all files for that resource.
38
+ - Example: `src/resources/albums/` contains:
39
+ - `albums-resource.tsx` - The React component.
40
+ - `albums-resource.json` - Resource metadata (name, title, description, etc.).
41
+ - `albums-resource.test.tsx` - Unit tests for the resource.
42
+ - `albums-show-simulation.json` - Simulation data for testing.
43
+ - `components/` - UI components used by the resource.
44
+ - `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.).
30
45
 
31
- - `src/resources/` - Resource files must be here
32
- - `src/simulations/` - Simulation files must be here
33
- - Resource file naming: `*-resource.tsx` (e.g., `review-resource.tsx`)
34
- - Simulation file naming: `*-simulation.json` (e.g., `review-purchase-simulation.json`)
35
- - `src/index-resource.tsx` - Build template (must have `// RESOURCE_IMPORT` and `// RESOURCE_MOUNT` comments)
36
-
37
- **You can customize:**
38
-
39
- - Package.json scripts - Add your own tooling (lint, format, typecheck, etc.)
40
- - Component structure within `src/components/`
41
- - Package manager (pnpm, npm, or yarn auto-detected)
42
- - Pretty much everything else!
43
-
44
- ## Testing
45
-
46
- ### Testing Locally
47
-
48
- Run the test suite:
49
-
50
- ```bash
51
- pnpm test
52
- ```
53
-
54
- For manual QA of the UI:
55
-
56
- ```bash
57
- sunpeak dev
58
- ```
59
-
60
- ### Testing in ChatGPT
46
+ ## Testing in ChatGPT
61
47
 
62
48
  Test your app directly in ChatGPT using the built-in MCP server:
63
49
 
@@ -83,28 +69,46 @@ Build your app for production:
83
69
  sunpeak build
84
70
  ```
85
71
 
86
- This creates optimized builds in `dist/`:
72
+ This creates optimized builds in `dist/`, organized by resource:
87
73
 
88
- - `dist/albums.js`
89
- - `dist/albums.json`
90
- - `dist/review.js`
91
- - `dist/review.json`
92
- - _(One .js file per resource in src/resources/)_
93
- - _(One .json file per resource in src/resources/)_
74
+ ```bash
75
+ dist/
76
+ ├── albums/
77
+ │ ├── albums.js # Built resource component.
78
+ │ ├── albums.json # Resource metadata.
79
+ │ └── albums-show-simulation.json # Resource mock data for testing.
80
+ ├── review/
81
+ │ ├── review.js
82
+ │ ├── review.json
83
+ │ ├── review-diff-simulation.json
84
+ │ ├── review-post-simulation.json
85
+ │ └── review-purchase-simulation.json
86
+ └── ...
87
+ ```
88
+
89
+ Each resource folder contains:
94
90
 
95
- Each `.js` file is a self-contained bundle with CSS inlined. Host these files and reference them as resources in your production MCP server.
91
+ - **`.js` file**: Self-contained bundle with CSS inlined
92
+ - **`.json` file**: Resource metadata with unique `uri` for cache-busting
93
+ - **`*-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.
96
94
 
97
- Each `.json` file is a copy of each of your resource MCP metadata files `src/resource/*-resource.json`. The copy is identical, but with a unique `uri` field added that will cache-bust earlier resource versions pre-loaded in ChatGPT.
95
+ Host these files and reference them as resources in your production MCP server.
96
+ Use the sunpeak resource repository for built-in resource hosting.
98
97
 
99
98
  ## Add a new UI (Resource)
100
99
 
101
- To add a new UI (MCP Resource), simply create the following files:
100
+ To add a new UI (MCP Resource), create a new directory under `src/resources/` with the following files:
102
101
 
103
- - `src/resources/NAME-resource.tsx`
104
- - `src/resources/NAME-resource.json`
105
- - `src/simulations/NAME-TOOLNAME-simulation.json`
102
+ ```
103
+ src/resources/NAME/
104
+ ├── NAME-resource.tsx # React component (required)
105
+ ├── NAME-resource.json # Resource metadata (required)
106
+ ├── NAME-resource.test.tsx # Unit tests (optional)
107
+ ├── NAME-SCENARIO-simulation.json # Simulation data (optional)
108
+ └── components/ # UI components (optional)
109
+ ```
106
110
 
107
- Only the resource files 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` or `sunpeak mcp`.
111
+ 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` or `sunpeak mcp`.
108
112
 
109
113
  ## Resources
110
114
 
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://albums-mkbms4dx"
15
+ "uri": "ui://albums-mkd2wyc7"
16
16
  }
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://carousel-mkbms4dx"
15
+ "uri": "ui://carousel-mkd2wyc7"
16
16
  }
@@ -18,5 +18,5 @@
18
18
  ]
19
19
  }
20
20
  },
21
- "uri": "ui://map-mkbms4dx"
21
+ "uri": "ui://map-mkd2wyc7"
22
22
  }
@@ -12,5 +12,5 @@
12
12
  ]
13
13
  }
14
14
  },
15
- "uri": "ui://review-mkbms4dx"
15
+ "uri": "ui://review-mkd2wyc7"
16
16
  }
@@ -2,13 +2,13 @@ import {
2
2
  Button,
3
3
  ButtonLink,
4
4
  CopyButton
5
- } from "./chunk-N6DVYEXK.js";
6
- import "./chunk-JAGHY6H6.js";
5
+ } from "./chunk-LVZJNEGE.js";
6
+ import "./chunk-UM3ZGDFR.js";
7
7
  import "./chunk-QPJAV452.js";
8
+ import "./chunk-JAGHY6H6.js";
8
9
  import "./chunk-DYQDWJMS.js";
9
10
  import "./chunk-EGRHWZRV.js";
10
11
  import "./chunk-CNYJBM5F.js";
11
- import "./chunk-UM3ZGDFR.js";
12
12
  import "./chunk-JGVISENQ.js";
13
13
  import "./chunk-BUOVMFCD.js";
14
14
  import "./chunk-ILHRZGIS.js";
@@ -5,13 +5,21 @@ import {
5
5
  Button,
6
6
  LoadingIndicator,
7
7
  TransitionGroup
8
- } from "./chunk-N6DVYEXK.js";
8
+ } from "./chunk-LVZJNEGE.js";
9
9
  import {
10
- useTimeout
11
- } from "./chunk-JAGHY6H6.js";
10
+ Check_default,
11
+ ChevronDownVector_default,
12
+ DropdownVector_default,
13
+ Info_default,
14
+ Search_default,
15
+ X_default
16
+ } from "./chunk-UM3ZGDFR.js";
12
17
  import {
13
18
  o
14
19
  } from "./chunk-QPJAV452.js";
20
+ import {
21
+ useTimeout
22
+ } from "./chunk-JAGHY6H6.js";
15
23
  import {
16
24
  handlePressableMouseEnter,
17
25
  preventDefaultHandler,
@@ -28,14 +36,6 @@ import "./chunk-XZTIOEPG.js";
28
36
  import {
29
37
  clsx_default
30
38
  } from "./chunk-CNYJBM5F.js";
31
- import {
32
- Check_default,
33
- ChevronDownVector_default,
34
- DropdownVector_default,
35
- Info_default,
36
- Search_default,
37
- X_default
38
- } from "./chunk-UM3ZGDFR.js";
39
39
  import {
40
40
  require_jsx_runtime
41
41
  } from "./chunk-JGVISENQ.js";