sunpeak 0.7.11 → 0.8.4
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 +2 -1
- package/bin/commands/deploy.mjs +18 -8
- package/bin/commands/dev.mjs +60 -4
- package/bin/commands/login.mjs +73 -55
- package/bin/commands/logout.mjs +26 -12
- package/bin/commands/mcp.mjs +1 -1
- package/bin/commands/pull.mjs +60 -39
- package/bin/commands/push.mjs +73 -49
- package/bin/commands/upgrade.mjs +203 -0
- package/bin/sunpeak.js +68 -35
- package/dist/chatgpt/chatgpt-simulator.d.ts +2 -1
- package/dist/index.cjs +13 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -14
- package/dist/index.js.map +1 -1
- package/dist/mcp/entry.cjs +41 -9
- package/dist/mcp/entry.cjs.map +1 -1
- package/dist/mcp/entry.js +42 -10
- package/dist/mcp/entry.js.map +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/{server-CziiHU7V.cjs → server-B9YgCQdS.cjs} +3 -2
- package/dist/{server-CziiHU7V.cjs.map → server-B9YgCQdS.cjs.map} +1 -1
- package/dist/{server-D8kyzuiq.js → server-DVmTC-SF.js} +3 -2
- package/dist/{server-D8kyzuiq.js.map → server-DVmTC-SF.js.map} +1 -1
- package/dist/style.css +62 -0
- package/dist/types/simulation.d.ts +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +78 -15
- package/template/.sunpeak/vite-env.d.ts +1 -0
- package/template/README.md +35 -20
- package/template/dist/albums.js +1 -1
- package/template/dist/albums.json +3 -2
- package/template/dist/carousel.js +1 -1
- package/template/dist/carousel.json +3 -2
- package/template/dist/confirmation.js +49 -0
- package/template/dist/confirmation.json +16 -0
- package/template/dist/counter.js +1 -1
- package/template/dist/counter.json +7 -2
- package/template/dist/map.js +1 -1
- package/template/dist/map.json +6 -3
- package/template/node_modules/.vite/deps/_metadata.json +19 -19
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/src/components/map/map-view.test.tsx +1 -1
- package/template/src/components/map/map-view.tsx +1 -1
- package/template/src/components/map/map.tsx +1 -1
- package/template/src/components/map/place-card.test.tsx +1 -1
- package/template/src/components/map/place-card.tsx +1 -1
- package/template/src/components/map/place-carousel.test.tsx +1 -1
- package/template/src/components/map/place-carousel.tsx +1 -1
- package/template/src/components/map/place-inspector.test.tsx +1 -1
- package/template/src/components/map/place-inspector.tsx +1 -1
- package/template/src/components/map/place-list.test.tsx +1 -1
- package/template/src/components/map/place-list.tsx +1 -1
- package/template/src/components/map/types.ts +18 -0
- package/template/src/resources/albums-resource.json +1 -1
- package/template/src/resources/carousel-resource.json +1 -1
- package/template/src/resources/confirmation-resource.json +12 -0
- package/template/src/resources/confirmation-resource.tsx +479 -0
- package/template/src/resources/counter-resource.json +4 -1
- package/template/src/resources/index.ts +39 -4
- package/template/src/resources/map-resource.json +7 -2
- package/template/src/simulations/albums-show-simulation.json +131 -0
- package/template/src/simulations/carousel-show-simulation.json +68 -0
- package/template/src/simulations/confirmation-diff-simulation.json +80 -0
- package/template/src/simulations/confirmation-post-simulation.json +56 -0
- package/template/src/simulations/confirmation-purchase-simulation.json +88 -0
- package/template/src/simulations/counter-show-simulation.json +20 -0
- package/template/src/simulations/index.ts +17 -12
- package/template/src/simulations/map-show-simulation.json +123 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.json +1 -1
- package/template/src/simulations/albums-simulation.ts +0 -147
- package/template/src/simulations/carousel-simulation.ts +0 -84
- package/template/src/simulations/counter-simulation.ts +0 -34
- package/template/src/simulations/map-simulation.ts +0 -154
package/dist/style.css
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
--tw-gradient-to-position: 100%;
|
|
28
28
|
--tw-leading: initial;
|
|
29
29
|
--tw-font-weight: initial;
|
|
30
|
+
--tw-tracking: initial;
|
|
30
31
|
--tw-shadow: 0 0 #0000;
|
|
31
32
|
--tw-shadow-color: initial;
|
|
32
33
|
--tw-shadow-alpha: 100%;
|
|
@@ -118,6 +119,10 @@
|
|
|
118
119
|
--text-xs--line-height: var(--font-text-xs-line-height);
|
|
119
120
|
--text-xs--font-weight: var(--font-text-xs-weight);
|
|
120
121
|
--text-xs--letter-spacing: var(--font-text-xs-tracking);
|
|
122
|
+
--text-xl: var(--font-heading-md-size);
|
|
123
|
+
--text-xl--line-height: var(--font-heading-md-line-height);
|
|
124
|
+
--text-xl--font-weight: var(--font-text-lg-weight);
|
|
125
|
+
--text-xl--letter-spacing: var(--font-heading-md-tracking);
|
|
121
126
|
--text-2xl: var(--font-heading-lg-size);
|
|
122
127
|
--text-2xl--line-height: var(--font-heading-lg-line-height);
|
|
123
128
|
--text-2xl--font-weight: var(--font-text-lg-weight);
|
|
@@ -3318,6 +3323,10 @@
|
|
|
3318
3323
|
margin-inline-end: calc(var(--spacing) * 2);
|
|
3319
3324
|
}
|
|
3320
3325
|
|
|
3326
|
+
.mt-0\.5 {
|
|
3327
|
+
margin-top: calc(var(--spacing) * .5);
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3321
3330
|
.mt-1 {
|
|
3322
3331
|
margin-top: calc(var(--spacing) * 1);
|
|
3323
3332
|
}
|
|
@@ -3381,6 +3390,10 @@
|
|
|
3381
3390
|
display: inline;
|
|
3382
3391
|
}
|
|
3383
3392
|
|
|
3393
|
+
.table {
|
|
3394
|
+
display: table;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3384
3397
|
.aspect-\[4\/3\] {
|
|
3385
3398
|
aspect-ratio: 4 / 3;
|
|
3386
3399
|
}
|
|
@@ -3414,6 +3427,10 @@
|
|
|
3414
3427
|
height: calc(var(--spacing) * 5);
|
|
3415
3428
|
}
|
|
3416
3429
|
|
|
3430
|
+
.h-6 {
|
|
3431
|
+
height: calc(var(--spacing) * 6);
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3417
3434
|
.h-7 {
|
|
3418
3435
|
height: calc(var(--spacing) * 7);
|
|
3419
3436
|
}
|
|
@@ -3490,6 +3507,10 @@
|
|
|
3490
3507
|
width: calc(var(--spacing) * 5);
|
|
3491
3508
|
}
|
|
3492
3509
|
|
|
3510
|
+
.w-6 {
|
|
3511
|
+
width: calc(var(--spacing) * 6);
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3493
3514
|
.w-7 {
|
|
3494
3515
|
width: calc(var(--spacing) * 7);
|
|
3495
3516
|
}
|
|
@@ -3664,6 +3685,10 @@
|
|
|
3664
3685
|
gap: calc(var(--spacing) * 3);
|
|
3665
3686
|
}
|
|
3666
3687
|
|
|
3688
|
+
.gap-4 {
|
|
3689
|
+
gap: calc(var(--spacing) * 4);
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3667
3692
|
:where(.space-y-0\.5 > :not(:last-child)) {
|
|
3668
3693
|
--tw-space-y-reverse: 0;
|
|
3669
3694
|
margin-block-start: calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));
|
|
@@ -3959,6 +3984,10 @@
|
|
|
3959
3984
|
padding: calc(var(--spacing) * 2);
|
|
3960
3985
|
}
|
|
3961
3986
|
|
|
3987
|
+
.p-3 {
|
|
3988
|
+
padding: calc(var(--spacing) * 3);
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3962
3991
|
.p-4 {
|
|
3963
3992
|
padding: calc(var(--spacing) * 4);
|
|
3964
3993
|
}
|
|
@@ -4015,6 +4044,10 @@
|
|
|
4015
4044
|
padding-block: calc(var(--spacing) * 4);
|
|
4016
4045
|
}
|
|
4017
4046
|
|
|
4047
|
+
.py-8 {
|
|
4048
|
+
padding-block: calc(var(--spacing) * 8);
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4018
4051
|
.pt-0 {
|
|
4019
4052
|
padding-top: calc(var(--spacing) * 0);
|
|
4020
4053
|
}
|
|
@@ -4023,6 +4056,10 @@
|
|
|
4023
4056
|
padding-top: calc(var(--spacing) * 2);
|
|
4024
4057
|
}
|
|
4025
4058
|
|
|
4059
|
+
.pt-4 {
|
|
4060
|
+
padding-top: calc(var(--spacing) * 4);
|
|
4061
|
+
}
|
|
4062
|
+
|
|
4026
4063
|
.pr-12 {
|
|
4027
4064
|
padding-right: calc(var(--spacing) * 12);
|
|
4028
4065
|
}
|
|
@@ -4101,6 +4138,13 @@
|
|
|
4101
4138
|
font-weight: var(--tw-font-weight, var(--text-sm--font-weight));
|
|
4102
4139
|
}
|
|
4103
4140
|
|
|
4141
|
+
.text-xl {
|
|
4142
|
+
font-size: var(--text-xl);
|
|
4143
|
+
line-height: var(--tw-leading, var(--text-xl--line-height));
|
|
4144
|
+
letter-spacing: var(--tw-tracking, var(--text-xl--letter-spacing));
|
|
4145
|
+
font-weight: var(--tw-font-weight, var(--text-xl--font-weight));
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4104
4148
|
.text-xs {
|
|
4105
4149
|
font-size: var(--text-xs);
|
|
4106
4150
|
line-height: var(--tw-leading, var(--text-xs--line-height));
|
|
@@ -4150,6 +4194,11 @@
|
|
|
4150
4194
|
font-weight: var(--font-weight-semibold);
|
|
4151
4195
|
}
|
|
4152
4196
|
|
|
4197
|
+
.tracking-wide {
|
|
4198
|
+
--tw-tracking: var(--tracking-wide);
|
|
4199
|
+
letter-spacing: var(--tracking-wide);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4153
4202
|
.break-words {
|
|
4154
4203
|
overflow-wrap: break-word;
|
|
4155
4204
|
}
|
|
@@ -4170,6 +4219,10 @@
|
|
|
4170
4219
|
white-space: pre-wrap;
|
|
4171
4220
|
}
|
|
4172
4221
|
|
|
4222
|
+
.text-\[\#000000\] {
|
|
4223
|
+
color: #000;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4173
4226
|
.text-\[var\(--color-error\)\] {
|
|
4174
4227
|
color: var(--color-error);
|
|
4175
4228
|
}
|
|
@@ -4186,6 +4239,10 @@
|
|
|
4186
4239
|
color: var(--color-white);
|
|
4187
4240
|
}
|
|
4188
4241
|
|
|
4242
|
+
.uppercase {
|
|
4243
|
+
text-transform: uppercase;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4189
4246
|
.antialiased {
|
|
4190
4247
|
-webkit-font-smoothing: antialiased;
|
|
4191
4248
|
-moz-osx-font-smoothing: grayscale;
|
|
@@ -4754,6 +4811,11 @@
|
|
|
4754
4811
|
inherits: false
|
|
4755
4812
|
}
|
|
4756
4813
|
|
|
4814
|
+
@property --tw-tracking {
|
|
4815
|
+
syntax: "*";
|
|
4816
|
+
inherits: false
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4757
4819
|
@property --tw-shadow {
|
|
4758
4820
|
syntax: "*";
|
|
4759
4821
|
inherits: false;
|
package/package.json
CHANGED
|
@@ -1,44 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bootstrap file for Sunpeak dev server
|
|
3
3
|
* This file bootstraps the ChatGPT simulator for development
|
|
4
|
+
*
|
|
5
|
+
* Auto-discovers simulations and resources by file naming convention:
|
|
6
|
+
* - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)
|
|
7
|
+
* - resources/{resource}-resource.json
|
|
8
|
+
* - resources/{Resource}Resource component (PascalCase)
|
|
4
9
|
*/
|
|
5
10
|
import { StrictMode } from 'react';
|
|
6
11
|
import { createRoot } from 'react-dom/client';
|
|
7
12
|
import { ChatGPTSimulator, type Simulation } from 'sunpeak';
|
|
8
|
-
import
|
|
9
|
-
import * as Resources from '../src/resources';
|
|
13
|
+
import resourceComponents from '../src/resources';
|
|
10
14
|
import '../src/styles/globals.css';
|
|
11
15
|
|
|
16
|
+
// Auto-discover all simulation and resource JSON files
|
|
17
|
+
const simulationModules = import.meta.glob('../src/simulations/*-simulation.json', {
|
|
18
|
+
eager: true,
|
|
19
|
+
});
|
|
20
|
+
const resourceModules = import.meta.glob('../src/resources/*-resource.json', { eager: true });
|
|
21
|
+
|
|
22
|
+
// Build resource map from discovered files
|
|
23
|
+
type ResourceData = { name: string; [key: string]: unknown };
|
|
24
|
+
const resourcesMap = new Map<string, ResourceData>();
|
|
25
|
+
for (const [path, module] of Object.entries(resourceModules)) {
|
|
26
|
+
// Extract key from path: '../src/resources/counter-resource.json' -> 'counter'
|
|
27
|
+
const match = path.match(/\/([^/]+)-resource\.json$/);
|
|
28
|
+
const key = match?.[1];
|
|
29
|
+
if (key) {
|
|
30
|
+
resourcesMap.set(key, (module as { default: ResourceData }).default);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get sorted resource keys for best-match lookup (longest first)
|
|
35
|
+
const resourceKeys = Array.from(resourcesMap.keys()).sort((a, b) => b.length - a.length);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the best matching resource for a simulation key.
|
|
39
|
+
* Matches the longest resource name that is a prefix of the simulation key.
|
|
40
|
+
* e.g., 'albums-show' matches 'albums' (not 'album' if both exist)
|
|
41
|
+
*/
|
|
42
|
+
function findResourceKey(simulationKey: string): string | undefined {
|
|
43
|
+
// Simulation key format: {resource}-{tool} (e.g., 'albums-show')
|
|
44
|
+
// Find the longest resource name that matches as a prefix followed by '-'
|
|
45
|
+
for (const resourceKey of resourceKeys) {
|
|
46
|
+
if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {
|
|
47
|
+
return resourceKey;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
12
53
|
/**
|
|
13
54
|
* Convert resource name to component name
|
|
14
55
|
* Example: 'carousel' -> 'CarouselResource', 'counter' -> 'CounterResource'
|
|
15
56
|
*/
|
|
16
|
-
function
|
|
17
|
-
// Convert name to PascalCase and append 'Resource'
|
|
57
|
+
function getResourceComponent(name: string): React.ComponentType {
|
|
18
58
|
const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
19
59
|
const componentName = `${pascalName}Resource`;
|
|
20
60
|
|
|
21
|
-
const component =
|
|
61
|
+
const component = resourceComponents[componentName];
|
|
22
62
|
|
|
23
63
|
if (!component) {
|
|
24
64
|
throw new Error(
|
|
25
65
|
`Resource component "${componentName}" not found for resource "${name}". ` +
|
|
26
|
-
`Make sure
|
|
66
|
+
`Make sure src/resources/${name}-resource.tsx exists with a default export.`
|
|
27
67
|
);
|
|
28
68
|
}
|
|
29
69
|
|
|
30
|
-
return component
|
|
70
|
+
return component;
|
|
31
71
|
}
|
|
32
72
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
73
|
+
// Build simulations array from discovered files
|
|
74
|
+
type SimulationData = Omit<Simulation, 'resourceComponent' | 'resource'>;
|
|
75
|
+
const simulations: Simulation[] = [];
|
|
76
|
+
|
|
77
|
+
for (const [path, module] of Object.entries(simulationModules)) {
|
|
78
|
+
// Extract simulation key from path: '../src/simulations/albums-show-simulation.json' -> 'albums-show'
|
|
79
|
+
const match = path.match(/\/([^/]+)-simulation\.json$/);
|
|
80
|
+
const simulationKey = match?.[1];
|
|
81
|
+
if (!simulationKey) continue;
|
|
82
|
+
|
|
83
|
+
const simulation = (module as { default: SimulationData }).default;
|
|
84
|
+
|
|
85
|
+
// Find matching resource by best prefix match
|
|
86
|
+
const resourceKey = findResourceKey(simulationKey);
|
|
87
|
+
if (!resourceKey) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`No matching resource found for simulation "${simulationKey}". ` +
|
|
90
|
+
`Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`
|
|
91
|
+
);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const resource = resourcesMap.get(resourceKey)!;
|
|
96
|
+
|
|
97
|
+
simulations.push({
|
|
98
|
+
...simulation,
|
|
99
|
+
resource,
|
|
100
|
+
resourceComponent: getResourceComponent(resource.name),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
40
103
|
|
|
41
|
-
// Read app config from
|
|
104
|
+
// Read app config from environment or use defaults
|
|
42
105
|
const appName = import.meta.env?.VITE_APP_NAME || 'Sunpeak';
|
|
43
106
|
const appIcon = import.meta.env?.VITE_APP_ICON || '🌄';
|
|
44
107
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/template/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A ChatGPT App UI built with [sunpeak](https://github.com/Sunpeak-AI/sunpeak).
|
|
4
4
|
|
|
5
|
-
For an initial overview of your new app and
|
|
5
|
+
For an initial overview of your new app and a detailed API reference, refer to the [documentation](https://docs.sunpeak.ai/template/project-structure).
|
|
6
6
|
|
|
7
7
|
## Quickstart
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
pnpm install
|
|
11
|
-
|
|
11
|
+
sunpeak dev
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
That's it! Edit the resource files in [./src/resources/](./src/resources/) to build your resource UI.
|
|
@@ -16,29 +16,30 @@ That's it! Edit the resource files in [./src/resources/](./src/resources/) to bu
|
|
|
16
16
|
## Commands
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
pnpm test
|
|
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
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The template includes a minimal test setup with Vitest. You can add additional tooling (linting, formatting, type-checking) as needed for your project.
|
|
26
26
|
|
|
27
27
|
### Customization
|
|
28
28
|
|
|
29
|
+
**Do not customize (required by `sunpeak` commands):**
|
|
30
|
+
|
|
31
|
+
- `src/resources/` - Resource files must be here
|
|
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`)
|
|
35
|
+
- `src/index-resource.tsx` - Build template (must have `// RESOURCE_IMPORT` and `// RESOURCE_MOUNT` comments)
|
|
36
|
+
|
|
29
37
|
**You can customize:**
|
|
30
38
|
|
|
31
39
|
- Package.json scripts - Add your own tooling (lint, format, typecheck, etc.)
|
|
32
40
|
- Component structure within `src/components/`
|
|
33
41
|
- Package manager (pnpm, npm, or yarn auto-detected)
|
|
34
|
-
|
|
35
|
-
**Do not customize (required by `sunpeak build`):**
|
|
36
|
-
|
|
37
|
-
- `src/resources/` - Resource files must be here
|
|
38
|
-
- `src/index-resource.tsx` - Build template (must have `// RESOURCE_IMPORT` and `// RESOURCE_MOUNT` comments)
|
|
39
|
-
- Resource file naming: `*-resource.tsx` (e.g., `counter-resource.tsx`)
|
|
40
|
-
|
|
41
|
-
If you need to customize these paths, create a custom build script instead of using `sunpeak build`.
|
|
42
|
+
- Pretty much everything else!
|
|
42
43
|
|
|
43
44
|
## Testing
|
|
44
45
|
|
|
@@ -62,7 +63,7 @@ Test your app directly in ChatGPT using the built-in MCP server:
|
|
|
62
63
|
|
|
63
64
|
```bash
|
|
64
65
|
# Start the MCP server (rebuilds and restarts on file changes).
|
|
65
|
-
|
|
66
|
+
sunpeak mcp
|
|
66
67
|
|
|
67
68
|
# In another terminal, run a tunnel. For example:
|
|
68
69
|
ngrok http 6766
|
|
@@ -70,7 +71,7 @@ ngrok http 6766
|
|
|
70
71
|
|
|
71
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`
|
|
72
73
|
|
|
73
|
-
Once your app is connected, send the name of a tool, like
|
|
74
|
+
Once your app is connected, send the name of the app and a tool, like `/sunpeak show counter`, to ChatGPT.
|
|
74
75
|
|
|
75
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`
|
|
76
77
|
|
|
@@ -79,17 +80,31 @@ When you make changes to the UI, refresh your app in ChatGPT after the MCP serve
|
|
|
79
80
|
Build your app for production:
|
|
80
81
|
|
|
81
82
|
```bash
|
|
82
|
-
|
|
83
|
+
sunpeak build
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
This creates optimized builds in `dist/`:
|
|
86
87
|
|
|
87
|
-
- `dist/counter.js`
|
|
88
88
|
- `dist/albums.js`
|
|
89
|
-
- `dist/
|
|
89
|
+
- `dist/albums.json`
|
|
90
|
+
- `dist/counter.js`
|
|
91
|
+
- `dist/counter.json`
|
|
90
92
|
- _(One .js file per resource in src/resources/)_
|
|
93
|
+
- _(One .json file per resource in src/resources/)_
|
|
94
|
+
|
|
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.
|
|
96
|
+
|
|
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.
|
|
98
|
+
|
|
99
|
+
## Add a new UI (Resource)
|
|
100
|
+
|
|
101
|
+
To add a new UI (MCP Resource), simply create the following files:
|
|
102
|
+
|
|
103
|
+
- `src/resource/NAME-resource.tsx`
|
|
104
|
+
- `src/resource/NAME-resource.json`
|
|
105
|
+
- `src/simulations/NAME-TOOLNAME-simulation.json`
|
|
91
106
|
|
|
92
|
-
|
|
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`.
|
|
93
108
|
|
|
94
109
|
## Resources
|
|
95
110
|
|