sunpeak 0.15.4 → 0.16.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.
- package/README.md +51 -48
- package/bin/commands/build.mjs +13 -4
- package/bin/commands/dev.mjs +64 -19
- package/bin/commands/new.mjs +13 -3
- package/bin/lib/extract-resource.mjs +1 -1
- package/bin/lib/extract-tool.mjs +78 -0
- package/bin/lib/patterns.mjs +2 -26
- package/dist/chatgpt/index.cjs +3 -6
- package/dist/chatgpt/index.cjs.map +1 -1
- package/dist/chatgpt/index.d.ts +1 -1
- package/dist/chatgpt/index.js +6 -9
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/discovery-CH80W5l9.js +217 -0
- package/dist/discovery-CH80W5l9.js.map +1 -0
- package/dist/discovery-DmB8_4QL.cjs +216 -0
- package/dist/discovery-DmB8_4QL.cjs.map +1 -0
- package/dist/{index-CutQgPzR.js → index-BjnAsaqp.js} +3 -6
- package/dist/index-BjnAsaqp.js.map +1 -0
- package/dist/{index-Cngntkp2.cjs → index-BvQ_ZuOO.cjs} +3 -6
- package/dist/{index-Cngntkp2.cjs.map → index-BvQ_ZuOO.cjs.map} +1 -1
- package/dist/{index-B0dxRJvS.cjs → index-C9CVbGFt.cjs} +3 -6
- package/dist/index-C9CVbGFt.cjs.map +1 -0
- package/dist/{index-Ce_5ZIdJ.js → index-CTGEqlgk.js} +3 -6
- package/dist/{index-Ce_5ZIdJ.js.map → index-CTGEqlgk.js.map} +1 -1
- package/dist/index.cjs +48 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3404 -3361
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery-cli.cjs +58 -5
- package/dist/lib/discovery-cli.cjs.map +1 -1
- package/dist/lib/discovery-cli.d.ts +3 -2
- package/dist/lib/discovery-cli.js +61 -8
- package/dist/lib/discovery-cli.js.map +1 -1
- package/dist/lib/discovery.d.ts +42 -43
- package/dist/lib/extract-tool.d.ts +12 -0
- package/dist/mcp/favicon.d.ts +1 -1
- package/dist/mcp/index.cjs +3 -2
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +3 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/types.d.ts +24 -1
- package/dist/platform/chatgpt/index.cjs +1 -1
- package/dist/platform/chatgpt/index.js +1 -1
- package/dist/simulator/index.cjs +2 -5
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.ts +1 -1
- package/dist/simulator/index.js +5 -8
- package/dist/simulator/simulator-url.d.ts +9 -9
- package/dist/{simulator-DcfQBRXE.cjs → simulator-B56j5P8W.cjs} +8 -2
- package/dist/{simulator-DcfQBRXE.cjs.map → simulator-B56j5P8W.cjs.map} +1 -1
- package/dist/{simulator-CxrtnguM.js → simulator-C0H_k092.js} +8 -2
- package/dist/{simulator-CxrtnguM.js.map → simulator-C0H_k092.js.map} +1 -1
- package/dist/simulator-url-CuLqtnSS.js.map +1 -1
- package/dist/simulator-url-rgg_KYOg.cjs.map +1 -1
- package/dist/types/resource-config.d.ts +7 -5
- package/dist/{use-app-D_TeaMFG.js → use-app-BThbgFFT.js} +51 -22
- package/dist/{use-app-D_TeaMFG.js.map → use-app-BThbgFFT.js.map} +1 -1
- package/dist/{use-app-BnoSPiUT.cjs → use-app-BuufpXTQ.cjs} +49 -20
- package/dist/{use-app-BnoSPiUT.cjs.map → use-app-BuufpXTQ.cjs.map} +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +8 -4
- package/template/.sunpeak/resource-loader.tsx +2 -1
- package/template/README.md +14 -10
- package/template/package.json +2 -1
- package/template/src/resources/albums/{albums-resource.test.tsx → albums.test.tsx} +1 -1
- package/template/src/resources/albums/{albums-resource.tsx → albums.tsx} +0 -1
- package/template/src/resources/carousel/{carousel-resource.test.tsx → carousel.test.tsx} +1 -1
- package/template/src/resources/carousel/{carousel-resource.tsx → carousel.tsx} +0 -1
- package/template/src/resources/index.ts +4 -4
- package/template/src/resources/map/{map-resource.test.tsx → map.test.tsx} +1 -1
- package/template/src/resources/map/{map-resource.tsx → map.tsx} +0 -1
- package/template/src/resources/review/{review-resource.test.tsx → review.test.tsx} +1 -1
- package/template/src/resources/review/{review-resource.tsx → review.tsx} +1 -2
- package/template/src/server.ts +15 -0
- package/template/src/tools/review-diff.ts +24 -0
- package/template/src/tools/review-post.ts +26 -0
- package/template/src/tools/review-purchase.ts +31 -0
- package/template/src/tools/show-albums.ts +22 -0
- package/template/src/tools/show-carousel.ts +25 -0
- package/template/src/tools/show-map.ts +29 -0
- package/template/tests/e2e/albums.spec.ts +6 -6
- package/template/tests/e2e/carousel.spec.ts +6 -6
- package/template/tests/e2e/map.spec.ts +11 -11
- package/template/tests/simulations/{review/review-diff-simulation.json → review-diff.json} +1 -31
- package/template/tests/simulations/{review/review-post-simulation.json → review-post.json} +1 -37
- package/template/tests/simulations/{review/review-purchase-simulation.json → review-purchase.json} +1 -38
- package/template/tests/simulations/{albums/albums-show-simulation.json → show-albums.json} +1 -24
- package/template/tests/simulations/{carousel/carousel-show-simulation.json → show-carousel.json} +1 -24
- package/template/tests/simulations/{map/map-show-simulation.json → show-map.json} +1 -35
- package/dist/discovery-CRR3SlyI.cjs +0 -156
- package/dist/discovery-CRR3SlyI.cjs.map +0 -1
- package/dist/discovery-DzV3HLXs.js +0 -157
- package/dist/discovery-DzV3HLXs.js.map +0 -1
- package/dist/index-B0dxRJvS.cjs.map +0 -1
- package/dist/index-CutQgPzR.js.map +0 -1
package/README.md
CHANGED
|
@@ -61,21 +61,26 @@ Next.js for MCP Apps. Using an example App `my-app` with a `Review` UI (MCP reso
|
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
my-app/
|
|
64
|
-
├── src/
|
|
65
|
-
│
|
|
66
|
-
│
|
|
64
|
+
├── src/
|
|
65
|
+
│ ├── resources/
|
|
66
|
+
│ │ └── review/
|
|
67
|
+
│ │ └── review.tsx # Review UI component + resource metadata.
|
|
68
|
+
│ ├── tools/
|
|
69
|
+
│ │ ├── review-diff.ts # Tool with handler, schema, and resource reference.
|
|
70
|
+
│ │ └── review-post.ts # Multiple tools can share one resource.
|
|
71
|
+
│ └── server.ts # Optional: auth, server config.
|
|
67
72
|
├── tests/simulations/
|
|
68
|
-
│
|
|
69
|
-
│
|
|
70
|
-
│ └── review-{scenario2}-simulation.json # Mock state for testing.
|
|
73
|
+
│ ├── review-diff.json # Mock state for testing.
|
|
74
|
+
│ └── review-post.json # Mock state for testing.
|
|
71
75
|
└── package.json
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
1. Project scaffold: Complete development setup with the `sunpeak` library.
|
|
75
79
|
2. UI components: Production-ready components following MCP App design guidelines.
|
|
76
80
|
3. Convention over configuration:
|
|
77
|
-
1. Create a UI by creating a
|
|
78
|
-
2. Create
|
|
81
|
+
1. Create a UI by creating a `.tsx` file in `src/resources/{name}/` that exports a `ResourceConfig` and a React component ([example below](#resource-component)).
|
|
82
|
+
2. Create a tool by creating a `.ts` file in `src/tools/` that exports `tool` (metadata with resource reference), `schema` (Zod), and a `default` handler ([example below](#tool-file)).
|
|
83
|
+
3. Create test state (`Simulation`s) by creating a `.json` file in `tests/simulations/` ([example below](#simulation)).
|
|
79
84
|
|
|
80
85
|
### The `sunpeak` CLI
|
|
81
86
|
|
|
@@ -92,23 +97,15 @@ Example `Resource`, `Simulation`, and testing file (using the `Simulator`) for a
|
|
|
92
97
|
|
|
93
98
|
### `Resource` Component
|
|
94
99
|
|
|
95
|
-
```bash
|
|
96
|
-
my-app/
|
|
97
|
-
├── src/resources/
|
|
98
|
-
│ └── review/
|
|
99
|
-
│ └── review-resource.tsx # This!
|
|
100
|
-
```
|
|
101
|
-
|
|
102
100
|
Each resource `.tsx` file exports both the React component and the MCP resource metadata:
|
|
103
101
|
|
|
104
102
|
```tsx
|
|
105
|
-
// src/resources/review/review
|
|
103
|
+
// src/resources/review/review.tsx
|
|
106
104
|
|
|
107
105
|
import { useToolData } from 'sunpeak';
|
|
108
106
|
import type { ResourceConfig } from 'sunpeak';
|
|
109
107
|
|
|
110
108
|
export const resource: ResourceConfig = {
|
|
111
|
-
name: 'review',
|
|
112
109
|
description: 'Visualize and review a code change',
|
|
113
110
|
_meta: { ui: { csp: { resourceDomains: ['https://cdn.example.com'] } } },
|
|
114
111
|
};
|
|
@@ -120,48 +117,54 @@ export function ReviewResource() {
|
|
|
120
117
|
}
|
|
121
118
|
```
|
|
122
119
|
|
|
123
|
-
###
|
|
120
|
+
### Tool File
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
│ └── review-{scenario2}-simulation.json # These!
|
|
130
|
-
```
|
|
122
|
+
Each tool `.ts` file exports metadata (with a direct resource reference), a Zod schema, and a handler:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// src/tools/review-diff.ts
|
|
131
126
|
|
|
132
|
-
|
|
127
|
+
import { z } from 'zod';
|
|
128
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
133
129
|
|
|
134
|
-
|
|
130
|
+
export const tool: AppToolConfig = {
|
|
131
|
+
resource: 'review',
|
|
132
|
+
title: 'Diff Review',
|
|
133
|
+
description: 'Show a review dialog for a proposed code diff',
|
|
134
|
+
annotations: { readOnlyHint: false },
|
|
135
|
+
_meta: { ui: { visibility: ['model', 'app'] } },
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const schema = {
|
|
139
|
+
changesetId: z.string().describe('Unique identifier for the changeset'),
|
|
140
|
+
title: z.string().describe('Title describing the changes'),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default async function (args: Record<string, unknown>, extra: ToolHandlerExtra) {
|
|
144
|
+
return { structuredContent: { title: args.title, sections: [] } };
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `Simulation`
|
|
135
149
|
|
|
136
|
-
|
|
150
|
+
Simulation files provide fixture data for testing. Each references a tool by filename and contains the mock input/output:
|
|
137
151
|
|
|
138
152
|
```jsonc
|
|
139
|
-
// tests/simulations/review-diff
|
|
153
|
+
// tests/simulations/review-diff.json
|
|
140
154
|
|
|
141
155
|
{
|
|
142
|
-
//
|
|
143
|
-
"
|
|
144
|
-
"name": "review-diff",
|
|
145
|
-
"description": "Show a review dialog for a proposed code diff",
|
|
146
|
-
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
|
|
147
|
-
"title": "Diff Review",
|
|
148
|
-
"annotations": { "readOnlyHint": false },
|
|
149
|
-
"_meta": {
|
|
150
|
-
"ui": { "visibility": ["model", "app"] },
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
// Tool input arguments (sent to CallTool).
|
|
156
|
+
"tool": "review-diff", // References src/tools/review-diff.ts
|
|
157
|
+
"userMessage": "Refactor the auth module to use JWT tokens.",
|
|
154
158
|
"toolInput": {
|
|
155
159
|
"changesetId": "cs_789",
|
|
156
|
-
"title": "Refactor Authentication Module"
|
|
160
|
+
"title": "Refactor Authentication Module"
|
|
157
161
|
},
|
|
158
|
-
// Tool result data (CallToolResult response).
|
|
159
162
|
"toolResult": {
|
|
160
163
|
"structuredContent": {
|
|
161
164
|
"title": "Refactor Authentication Module",
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
+
"sections": [...]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
165
168
|
}
|
|
166
169
|
```
|
|
167
170
|
|
|
@@ -217,6 +220,6 @@ npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app
|
|
|
217
220
|
|
|
218
221
|
## Resources
|
|
219
222
|
|
|
220
|
-
- [MCP Apps](https://
|
|
221
|
-
- [MCP Apps SDK
|
|
222
|
-
- [
|
|
223
|
+
- [MCP Apps Documentation](https://sunpeak.ai/docs/mcp-apps/introduction)
|
|
224
|
+
- [MCP Apps SDK](https://github.com/modelcontextprotocol/ext-apps)
|
|
225
|
+
- [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
|
package/bin/commands/build.mjs
CHANGED
|
@@ -59,6 +59,11 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Read project identity from package.json for appInfo
|
|
63
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
64
|
+
const appName = pkg.name || 'sunpeak-app';
|
|
65
|
+
const appVersion = pkg.version || '0.1.0';
|
|
66
|
+
|
|
62
67
|
// Check if we're in the sunpeak workspace (directory is named "template")
|
|
63
68
|
const isTemplate = path.basename(projectRoot) === 'template';
|
|
64
69
|
const parentSrc = path.resolve(projectRoot, '../src');
|
|
@@ -153,7 +158,8 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
153
158
|
.filter(entry => entry.isDirectory())
|
|
154
159
|
.map(entry => {
|
|
155
160
|
const kebabName = entry.name;
|
|
156
|
-
|
|
161
|
+
|
|
162
|
+
const resourceFile = `${kebabName}.tsx`;
|
|
157
163
|
const resourcePath = path.join(resourcesDir, kebabName, resourceFile);
|
|
158
164
|
|
|
159
165
|
// Skip directories without a resource file
|
|
@@ -163,10 +169,11 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
163
169
|
|
|
164
170
|
// Convert kebab-case to PascalCase: 'review' -> 'Review', 'my-widget' -> 'MyWidget'
|
|
165
171
|
const pascalName = toPascalCase(kebabName);
|
|
172
|
+
const componentFile = resourceFile.replace(/\.tsx$/, '');
|
|
166
173
|
|
|
167
174
|
return {
|
|
168
175
|
componentName: `${pascalName}Resource`,
|
|
169
|
-
componentFile
|
|
176
|
+
componentFile,
|
|
170
177
|
kebabName,
|
|
171
178
|
resourceDir: path.join(resourcesDir, kebabName),
|
|
172
179
|
entry: `.tmp/index-${kebabName}.tsx`,
|
|
@@ -180,7 +187,7 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
180
187
|
|
|
181
188
|
if (resourceFiles.length === 0) {
|
|
182
189
|
console.error('Error: No resource directories found in src/resources/');
|
|
183
|
-
console.error('Each resource should be a directory like: src/resources/review/review
|
|
190
|
+
console.error('Each resource should be a directory like: src/resources/review/review.tsx');
|
|
184
191
|
process.exit(1);
|
|
185
192
|
}
|
|
186
193
|
|
|
@@ -218,7 +225,7 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
218
225
|
// Create entry file from template in temp directory
|
|
219
226
|
const entryContent = template
|
|
220
227
|
.replace('// RESOURCE_IMPORT', `import { ${componentName}, resource } from '../src/resources/${kebabName}/${componentFile}';`)
|
|
221
|
-
.replace('// RESOURCE_MOUNT', `createRoot(root).render(<AppProvider appInfo={{ name:
|
|
228
|
+
.replace('// RESOURCE_MOUNT', `createRoot(root).render(<AppProvider appInfo={{ name: ${JSON.stringify(appName)}, version: ${JSON.stringify(appVersion)} }}><${componentName} /></AppProvider>);`);
|
|
222
229
|
|
|
223
230
|
const entryPath = path.join(projectRoot, entry);
|
|
224
231
|
writeFileSync(entryPath, entryContent);
|
|
@@ -283,6 +290,8 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
283
290
|
const destJson = path.join(distOutDir, `${kebabName}.json`);
|
|
284
291
|
|
|
285
292
|
const meta = await extractResourceExport(srcTsx);
|
|
293
|
+
// Inject name from directory key if not explicitly set
|
|
294
|
+
meta.name = meta.name ?? kebabName;
|
|
286
295
|
// Generate URI using resource name and build timestamp
|
|
287
296
|
meta.uri = `ui://${meta.name}-${timestamp}`;
|
|
288
297
|
writeFileSync(destJson, JSON.stringify(meta, null, 2));
|
package/bin/commands/dev.mjs
CHANGED
|
@@ -179,7 +179,7 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
179
179
|
sunpeakDiscovery = await import(pathToFileURL(join(sunpeakBase, 'dist/lib/discovery-cli.js')).href);
|
|
180
180
|
}
|
|
181
181
|
const { FAVICON_BUFFER: faviconBuffer, runMCPServer } = sunpeakMcp;
|
|
182
|
-
const { findResourceDirs,
|
|
182
|
+
const { findResourceDirs, findSimulationFilesFlat, findToolFiles, extractResourceExport, extractToolExport } = sunpeakDiscovery;
|
|
183
183
|
|
|
184
184
|
// Vite plugin to serve the sunpeak favicon
|
|
185
185
|
const sunpeakFaviconPlugin = () => ({
|
|
@@ -229,28 +229,73 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
229
229
|
// Discover simulations using sunpeak's discovery utilities
|
|
230
230
|
const resourcesDir = join(projectRoot, 'src/resources');
|
|
231
231
|
const simulationsDir = join(projectRoot, 'tests/simulations');
|
|
232
|
-
const
|
|
232
|
+
const toolsDir = join(projectRoot, 'src/tools');
|
|
233
233
|
|
|
234
|
-
const
|
|
235
|
-
|
|
234
|
+
const resourceDirs = findResourceDirs(resourcesDir, (key) => `${key}.tsx`, fs);
|
|
235
|
+
|
|
236
|
+
// Build resource metadata map
|
|
237
|
+
const resourceMap = new Map();
|
|
238
|
+
for (const { key, resourcePath } of resourceDirs) {
|
|
236
239
|
const resource = await extractResourceExport(resourcePath);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
240
|
+
// Inject name from directory key if not explicitly set
|
|
241
|
+
resource.name = resource.name ?? key;
|
|
242
|
+
resourceMap.set(key, resource);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Discover tool files and extract metadata
|
|
246
|
+
const toolFiles = findToolFiles(toolsDir, fs);
|
|
247
|
+
const toolMap = new Map();
|
|
248
|
+
for (const { name: toolName, path: toolPath } of toolFiles) {
|
|
249
|
+
try {
|
|
250
|
+
const { tool } = await extractToolExport(toolPath);
|
|
251
|
+
toolMap.set(toolName, tool);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.warn(`Warning: Could not extract metadata from tool ${toolName}: ${err.message}`);
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
// Discover simulations from flat directory
|
|
258
|
+
const simulations = [];
|
|
259
|
+
const simFiles = findSimulationFilesFlat(simulationsDir, fs);
|
|
260
|
+
|
|
261
|
+
for (const { name: simName, path: simPath } of simFiles) {
|
|
262
|
+
const simulation = JSON.parse(readFileSync(simPath, 'utf-8'));
|
|
263
|
+
const toolName = typeof simulation.tool === 'string' ? simulation.tool : simName;
|
|
264
|
+
|
|
265
|
+
// Look up tool metadata
|
|
266
|
+
const tool = toolMap.get(toolName);
|
|
267
|
+
if (!tool) {
|
|
268
|
+
console.warn(`Warning: Tool "${toolName}" not found for simulation "${simName}". Skipping.`);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// tool.resource is the resource name string — find matching resource key
|
|
273
|
+
const resourceName = tool.resource;
|
|
274
|
+
const resourceKey = resourceName
|
|
275
|
+
? Array.from(resourceMap.keys()).find((k) => resourceMap.get(k).name === resourceName)
|
|
276
|
+
: undefined;
|
|
277
|
+
|
|
278
|
+
if (!resourceKey) {
|
|
279
|
+
console.warn(`Warning: No resource found for tool "${toolName}" in simulation "${simName}". Skipping.`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Determine source path for the resource
|
|
284
|
+
const resourceDir = resourceDirs.find((d) => d.key === resourceKey);
|
|
285
|
+
const srcPath = resourceDir
|
|
286
|
+
? `/src/resources/${resourceKey}/${basename(resourceDir.resourcePath)}`
|
|
287
|
+
: undefined;
|
|
288
|
+
|
|
289
|
+
simulations.push({
|
|
290
|
+
...simulation,
|
|
291
|
+
...(typeof simulation.tool === 'string' ? { tool: { name: toolName, ...tool } } : {}),
|
|
292
|
+
name: simName,
|
|
293
|
+
distPath: join(projectRoot, `dist/${resourceKey}/${resourceKey}.html`),
|
|
294
|
+
srcPath,
|
|
295
|
+
resource: resourceMap.get(resourceKey),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
254
299
|
// Start MCP server with its own Vite instance (unless --prod-mcp is set)
|
|
255
300
|
if (simulations.length > 0) {
|
|
256
301
|
const mcpMode = prodMcp ? 'production build' : 'Vite HMR';
|
|
@@ -294,7 +339,7 @@ const Component = ResourceModule.default || ResourceModule['${componentName}'];
|
|
|
294
339
|
if (!Component) {
|
|
295
340
|
document.getElementById('root').innerHTML = '<pre style="color:red;padding:16px">Component not found: ${componentName}\\nExports: ' + Object.keys(ResourceModule).join(', ') + '</pre>';
|
|
296
341
|
} else {
|
|
297
|
-
const appInfo = { name:
|
|
342
|
+
const appInfo = { name: ${JSON.stringify(pkg.name || 'sunpeak-app')}, version: ${JSON.stringify(pkg.version || '0.1.0')} };
|
|
298
343
|
root.render(
|
|
299
344
|
createElement(AppProvider, { appInfo }, createElement(Component))
|
|
300
345
|
);
|
package/bin/commands/new.mjs
CHANGED
|
@@ -153,9 +153,19 @@ export async function init(projectName, resourcesArg, deps = defaultDeps) {
|
|
|
153
153
|
if (src.includes('/resources/') && name === resource) {
|
|
154
154
|
return false;
|
|
155
155
|
}
|
|
156
|
-
// Skip simulation
|
|
157
|
-
if (src.includes('/tests/simulations/') && name
|
|
158
|
-
|
|
156
|
+
// Skip flat simulation files for excluded resources: tests/simulations/*.json
|
|
157
|
+
if (src.includes('/tests/simulations/') && name.endsWith('.json')) {
|
|
158
|
+
const baseName = name.replace(/\.json$/, '');
|
|
159
|
+
if (baseName === resource || baseName.startsWith(resource + '-') || baseName.endsWith('-' + resource)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Skip tool files for excluded resources: src/tools/*.ts
|
|
164
|
+
if (src.includes('/src/tools/') && name.endsWith('.ts')) {
|
|
165
|
+
const baseName = name.replace(/\.ts$/, '');
|
|
166
|
+
if (baseName === resource || baseName.startsWith(resource + '-') || baseName.endsWith('-' + resource)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
159
169
|
}
|
|
160
170
|
// Skip e2e test files for excluded resources
|
|
161
171
|
if (src.includes('/tests/e2e/') && name === `${resource}.spec.ts`) {
|
|
@@ -55,7 +55,7 @@ export async function extractResourceExport(tsxPath) {
|
|
|
55
55
|
if (!resource) {
|
|
56
56
|
throw new Error(
|
|
57
57
|
`No "resource" export found in ${tsxPath}. ` +
|
|
58
|
-
`Add: export const resource = {
|
|
58
|
+
`Add: export const resource: ResourceConfig = { title: '...', ... };`
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract the `tool` named export from a tool .ts file.
|
|
5
|
+
*
|
|
6
|
+
* Uses esbuild in ESM mode to compile TypeScript and tree-shake to just the
|
|
7
|
+
* `tool` export. ESM tree-shaking drops unused exports (schema, handler) so
|
|
8
|
+
* their dependencies (zod, etc.) are never evaluated.
|
|
9
|
+
*
|
|
10
|
+
* `schema` and `default` handler are loaded at runtime via Vite SSR.
|
|
11
|
+
*/
|
|
12
|
+
export async function extractToolExport(tsPath) {
|
|
13
|
+
const esbuild = await import('esbuild');
|
|
14
|
+
const absolutePath = path.resolve(tsPath);
|
|
15
|
+
const dir = path.dirname(absolutePath);
|
|
16
|
+
const base = path.basename(absolutePath);
|
|
17
|
+
|
|
18
|
+
const result = await esbuild.build({
|
|
19
|
+
stdin: {
|
|
20
|
+
contents: `export { tool } from './${base}';`,
|
|
21
|
+
resolveDir: dir,
|
|
22
|
+
loader: 'ts',
|
|
23
|
+
},
|
|
24
|
+
bundle: true,
|
|
25
|
+
write: false,
|
|
26
|
+
format: 'esm',
|
|
27
|
+
treeShaking: true,
|
|
28
|
+
loader: { '.tsx': 'tsx', '.ts': 'ts', '.jsx': 'jsx' },
|
|
29
|
+
logLevel: 'silent',
|
|
30
|
+
plugins: [
|
|
31
|
+
{
|
|
32
|
+
name: 'externalize-node-modules',
|
|
33
|
+
setup(build) {
|
|
34
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
35
|
+
if (args.kind !== 'import-statement') return;
|
|
36
|
+
if (!args.path.startsWith('.') && !args.path.startsWith('/')) {
|
|
37
|
+
return { external: true };
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!result.outputFiles?.length) {
|
|
47
|
+
throw new Error(`Failed to extract tool from ${tsPath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Strip import statements and export block so we can eval as plain JS.
|
|
51
|
+
// `tool` is pure data (no dependencies), so stripping imports is safe.
|
|
52
|
+
// Other top-level code (schema, etc.) may reference stripped imports but
|
|
53
|
+
// we only need the `tool` variable — errors in other code are caught and ignored.
|
|
54
|
+
const code = result.outputFiles[0].text
|
|
55
|
+
.replace(/^import\s+.*$/gm, '')
|
|
56
|
+
.replace(/^export\s*\{[^}]*\}\s*;?\s*$/m, '');
|
|
57
|
+
let tool;
|
|
58
|
+
try {
|
|
59
|
+
const fn = new Function(code + '\nreturn tool;');
|
|
60
|
+
tool = fn();
|
|
61
|
+
} catch {
|
|
62
|
+
// If other top-level code crashes (e.g. schema using stripped zod),
|
|
63
|
+
// extract just the tool variable declaration and eval that alone.
|
|
64
|
+
const toolMatch = result.outputFiles[0].text.match(/var tool\s*=\s*(\{[\s\S]*?\n\});/);
|
|
65
|
+
if (toolMatch) {
|
|
66
|
+
tool = new Function('return ' + toolMatch[1])();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!tool) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`No "tool" export found in ${tsPath}. ` +
|
|
73
|
+
`Add: export const tool: AppToolConfig = { resource, ... };`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { tool };
|
|
78
|
+
}
|
package/bin/lib/patterns.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Auto-discover available resources from template/src/resources directories.
|
|
14
|
-
* Each subdirectory containing a {name}
|
|
14
|
+
* Each subdirectory containing a {name}.tsx file is a valid resource.
|
|
15
15
|
* @returns {string[]} Array of resource names
|
|
16
16
|
*/
|
|
17
17
|
export function discoverResources() {
|
|
@@ -21,10 +21,7 @@ export function discoverResources() {
|
|
|
21
21
|
}
|
|
22
22
|
return readdirSync(resourcesDir, { withFileTypes: true })
|
|
23
23
|
.filter((entry) => entry.isDirectory())
|
|
24
|
-
.filter((entry) => {
|
|
25
|
-
const resourceFile = join(resourcesDir, entry.name, `${entry.name}-resource.tsx`);
|
|
26
|
-
return existsSync(resourceFile);
|
|
27
|
-
})
|
|
24
|
+
.filter((entry) => existsSync(join(resourcesDir, entry.name, `${entry.name}.tsx`)))
|
|
28
25
|
.map((entry) => entry.name);
|
|
29
26
|
}
|
|
30
27
|
|
|
@@ -42,24 +39,3 @@ export function toPascalCase(str) {
|
|
|
42
39
|
.join('');
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
/**
|
|
46
|
-
* Check if a filename is a simulation file for a given resource.
|
|
47
|
-
* @param {string} filename
|
|
48
|
-
* @param {string} resourceKey
|
|
49
|
-
* @returns {boolean}
|
|
50
|
-
* @example isSimulationFile('albums-show-simulation.json', 'albums') // true
|
|
51
|
-
*/
|
|
52
|
-
export function isSimulationFile(filename, resourceKey) {
|
|
53
|
-
return filename.startsWith(`${resourceKey}-`) && filename.endsWith('-simulation.json');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Extract the simulation name from a simulation filename.
|
|
58
|
-
* @param {string} filename
|
|
59
|
-
* @param {string} resourceKey
|
|
60
|
-
* @returns {string}
|
|
61
|
-
* @example extractSimulationName('albums-show-simulation.json', 'albums') // 'show'
|
|
62
|
-
*/
|
|
63
|
-
export function extractSimulationName(filename, resourceKey) {
|
|
64
|
-
return filename.replace(`${resourceKey}-`, '').replace('-simulation.json', '');
|
|
65
|
-
}
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const simulator = require("../simulator-
|
|
4
|
-
const chatgpt_index = require("../index-
|
|
3
|
+
const simulator = require("../simulator-B56j5P8W.cjs");
|
|
4
|
+
const chatgpt_index = require("../index-BvQ_ZuOO.cjs");
|
|
5
5
|
const simulatorUrl = require("../simulator-url-rgg_KYOg.cjs");
|
|
6
|
-
const discovery = require("../discovery-
|
|
6
|
+
const discovery = require("../discovery-DmB8_4QL.cjs");
|
|
7
7
|
exports.IframeResource = simulator.IframeResource;
|
|
8
8
|
exports.McpAppHost = simulator.McpAppHost;
|
|
9
9
|
exports.SCREEN_WIDTHS = simulator.SCREEN_WIDTHS;
|
|
@@ -19,11 +19,8 @@ exports.buildSimulations = discovery.buildSimulations;
|
|
|
19
19
|
exports.createResourceExports = discovery.createResourceExports;
|
|
20
20
|
exports.extractResourceKey = discovery.extractResourceKey;
|
|
21
21
|
exports.extractSimulationKey = discovery.extractSimulationKey;
|
|
22
|
-
exports.extractSimulationName = discovery.extractSimulationName;
|
|
23
22
|
exports.findResourceDirs = discovery.findResourceDirs;
|
|
24
23
|
exports.findResourceKey = discovery.findResourceKey;
|
|
25
|
-
exports.findSimulationFiles = discovery.findSimulationFiles;
|
|
26
24
|
exports.getComponentName = discovery.getComponentName;
|
|
27
|
-
exports.isSimulationFile = discovery.isSimulationFile;
|
|
28
25
|
exports.toPascalCase = discovery.toPascalCase;
|
|
29
26
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/chatgpt/index.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ export type { ResourceCSP } from '../simulator/iframe-resource';
|
|
|
10
10
|
export * from '../simulator/theme-provider';
|
|
11
11
|
export { createSimulatorUrl } from '../simulator/simulator-url';
|
|
12
12
|
export type { SimulatorUrlParams } from '../simulator/simulator-url';
|
|
13
|
-
export { buildDevSimulations, buildSimulations, buildResourceMap, createResourceExports, toPascalCase, extractResourceKey, extractSimulationKey, findResourceKey, getComponentName, findResourceDirs,
|
|
13
|
+
export { buildDevSimulations, buildSimulations, buildResourceMap, createResourceExports, toPascalCase, extractResourceKey, extractSimulationKey, findResourceKey, getComponentName, findResourceDirs, } from '../lib/discovery';
|
|
14
14
|
export type { BuildSimulationsOptions, BuildDevSimulationsOptions, ResourceMetadata, ResourceDirInfo, FsOps, } from '../lib/discovery';
|
package/dist/chatgpt/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { I, M, a, S, T, j, m } from "../simulator-
|
|
2
|
-
import { C } from "../index-
|
|
1
|
+
import { I, M, a, S, T, j, m } from "../simulator-C0H_k092.js";
|
|
2
|
+
import { C } from "../index-CTGEqlgk.js";
|
|
3
3
|
import { c } from "../simulator-url-CuLqtnSS.js";
|
|
4
|
-
import { b, a as a2, c as c2, d, e, f, g, h, i,
|
|
4
|
+
import { b, a as a2, c as c2, d, e, f, g, h, i, t } from "../discovery-CH80W5l9.js";
|
|
5
5
|
export {
|
|
6
6
|
C as ChatGPTSimulator,
|
|
7
7
|
I as IframeResource,
|
|
@@ -17,12 +17,9 @@ export {
|
|
|
17
17
|
j as extractResourceCSP,
|
|
18
18
|
e as extractResourceKey,
|
|
19
19
|
f as extractSimulationKey,
|
|
20
|
-
g as
|
|
21
|
-
h as
|
|
22
|
-
i as
|
|
23
|
-
j2 as findSimulationFiles,
|
|
24
|
-
k as getComponentName,
|
|
25
|
-
l as isSimulationFile,
|
|
20
|
+
g as findResourceDirs,
|
|
21
|
+
h as findResourceKey,
|
|
22
|
+
i as getComponentName,
|
|
26
23
|
t as toPascalCase,
|
|
27
24
|
m as useThemeContext
|
|
28
25
|
};
|
package/dist/claude/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const simulator = require("../simulator-
|
|
3
|
+
const simulator = require("../simulator-B56j5P8W.cjs");
|
|
4
4
|
exports.ClaudeSimulator = simulator.Simulator;
|
|
5
5
|
//# sourceMappingURL=index.cjs.map
|
package/dist/claude/index.js
CHANGED