sunpeak 0.12.8 → 0.12.12
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 +123 -43
- package/bin/commands/dev.mjs +72 -64
- package/dist/chatgpt/index.cjs +1 -1
- package/dist/chatgpt/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/{simulator-url-CYMOGoB1.cjs → simulator-url-Bnxt0LlN.cjs} +3 -2
- package/dist/{simulator-url-CYMOGoB1.cjs.map → simulator-url-Bnxt0LlN.cjs.map} +1 -1
- package/dist/{simulator-url-DG79-dU3.js → simulator-url-CQhLDEdd.js} +2 -1
- package/dist/{simulator-url-DG79-dU3.js.map → simulator-url-CQhLDEdd.js.map} +1 -1
- package/package.json +1 -1
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.vite/deps/_metadata.json +19 -19
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Button.js +2 -2
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Select.js +15 -15
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e/chunk-IFPDCKUT.js → deps_temp_ed5fce0f/chunk-24D227QE.js} +5 -5
- package/template/tests/e2e/review.spec.ts +1 -1
- package/template/tests/simulations/review/review-diff-simulation.json +1 -1
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Avatar.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Avatar.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Button.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Checkbox.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Checkbox.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Icon.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Icon.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Input.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Input.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_SegmentedControl.js +4 -4
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_SegmentedControl.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Select.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Textarea.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_components_Textarea.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_theme.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/@openai_apps-sdk-ui_theme.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e/chunk-IFPDCKUT.js.map → deps_temp_ed5fce0f/chunk-24D227QE.js.map} +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-6CR5OFLH.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-6CR5OFLH.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-EGRHWZRV.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-EGRHWZRV.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-EXGLAROW.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-EXGLAROW.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-ILHRZGIS.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-ILHRZGIS.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-JSPMUVNT.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-JSPMUVNT.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-L2PWLFDO.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-L2PWLFDO.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-OMOWBQDU.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-OMOWBQDU.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-R3BTL4CL.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-R3BTL4CL.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-SPDZ46BB.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-SPDZ46BB.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-WSHFT23M.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-WSHFT23M.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-Y7UNCRGH.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-Y7UNCRGH.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-ZNFXUT4Q.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/chunk-ZNFXUT4Q.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/clsx.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/clsx.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/embla-carousel-react.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/embla-carousel-react.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/embla-carousel-wheel-gestures.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/embla-carousel-wheel-gestures.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/mapbox-gl.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/mapbox-gl.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/package.json +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react-dom.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react-dom.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react-dom_client.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react-dom_client.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react_jsx-dev-runtime.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react_jsx-dev-runtime.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react_jsx-runtime.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/react_jsx-runtime.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/tailwind-merge.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_c22d194e → deps_temp_ed5fce0f}/tailwind-merge.js.map +0 -0
package/README.md
CHANGED
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
# sunpeak
|
|
10
10
|
|
|
11
|
-
[](https://www.npmjs.com/package/sunpeak)
|
|
12
|
+
[](https://www.npmjs.com/package/sunpeak)
|
|
13
|
+
[](https://github.com/Sunpeak-AI/sunpeak)
|
|
14
|
+
[](https://github.com/Sunpeak-AI/sunpeak/actions)
|
|
15
|
+
[](https://github.com/Sunpeak-AI/sunpeak/blob/main/LICENSE)
|
|
16
|
+
[](https://www.typescriptlang.org/)
|
|
17
|
+
[](https://reactjs.org/)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
Local-first ChatGPT App framework.
|
|
19
20
|
|
|
20
|
-
Quickstart, build, test, and ship your ChatGPT App
|
|
21
|
+
Quickstart, build, test, and ship your ChatGPT App!
|
|
21
22
|
|
|
22
23
|
[Demo (Hosted)](https://sunpeak.ai/#simulator) ~
|
|
23
24
|
[Demo (Video)](https://d10djik02wlf6x.cloudfront.net/sunpeak-demo-prod.mp4) ~
|
|
@@ -43,21 +44,21 @@ pnpm add -g sunpeak
|
|
|
43
44
|
sunpeak new
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
To add sunpeak to an existing project, refer to the [documentation](https://docs.sunpeak.ai/add-to-existing-project).
|
|
47
|
+
To add `sunpeak` to an existing project, refer to the [documentation](https://docs.sunpeak.ai/add-to-existing-project).
|
|
47
48
|
|
|
48
49
|
## Overview
|
|
49
50
|
|
|
50
|
-
sunpeak is an npm package that helps you build ChatGPT Apps (MCP
|
|
51
|
+
`sunpeak` is an npm package that helps you build ChatGPT Apps (MCP resources) while keeping your MCP server client-agnostic. `sunpeak` consists of:
|
|
51
52
|
|
|
52
53
|
### The `sunpeak` library
|
|
53
54
|
|
|
54
55
|
1. Runtime APIs: Strongly typed APIs for interacting with the ChatGPT runtime, **architected to support future platforms** (Gemini, Claude, etc.).
|
|
55
|
-
2. ChatGPT simulator: React component replicating ChatGPT's runtime to **test Apps locally and automatically
|
|
56
|
+
2. ChatGPT simulator: React component replicating ChatGPT's runtime to **test Apps locally and automatically** via UI, props, or URL parameters.
|
|
56
57
|
3. MCP server: Serve Resources with mock data to the real ChatGPT with HMR (**no more cache issues or 5-click manual refreshes**).
|
|
57
58
|
|
|
58
59
|
### The `sunpeak` framework
|
|
59
60
|
|
|
60
|
-
Next.js for ChatGPT Apps. Using a Review
|
|
61
|
+
Next.js for ChatGPT Apps. Using an example App `my-app` with a `Review` UI (MCP resource), `sunpeak` projects look like:
|
|
61
62
|
|
|
62
63
|
```bash
|
|
63
64
|
my-app/
|
|
@@ -72,11 +73,11 @@ my-app/
|
|
|
72
73
|
└── package.json
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
1. Project scaffold: Complete development setup with the sunpeak library.
|
|
76
|
-
2. UI components: Production-ready components following ChatGPT design guidelines and using OpenAI apps-sdk-ui React components.
|
|
76
|
+
1. Project scaffold: Complete development setup with the `sunpeak` library.
|
|
77
|
+
2. UI components: Production-ready components following ChatGPT design guidelines and using OpenAI `apps-sdk-ui` React components.
|
|
77
78
|
3. Convention over configuration:
|
|
78
|
-
1. Create
|
|
79
|
-
2. Create test state (
|
|
79
|
+
1. Create a UI by creating a `-resource.tsx` component file ([example](#resource-component)) and `-resource.json` MCP metadata file ([example](#resource-mcp-metadata)).
|
|
80
|
+
2. Create test state (`Simulation`s) for local dev, ChatGPT dev, automated testing, and demos by creating a `-simulation.json` file. ([example](#simulation))
|
|
80
81
|
|
|
81
82
|
### The `sunpeak` CLI
|
|
82
83
|
|
|
@@ -93,46 +94,76 @@ Think Docker Hub for ChatGPT Apps:
|
|
|
93
94
|
</div>
|
|
94
95
|
|
|
95
96
|
1. Tag your app builds with version numbers and environment names (like `v1.0.0` and `prod`)
|
|
96
|
-
2. `push` built Apps to a central location
|
|
97
|
-
3. `pull` built Apps to be run in different environments, like your production MCP server.
|
|
97
|
+
2. `sunpeak push` built Apps to a central location
|
|
98
|
+
3. `sunpeak pull` built Apps to be run in different environments, like your production MCP server.
|
|
98
99
|
4. Share your fully-functional demo Apps with teammates, prospects, and strangers!
|
|
99
100
|
|
|
100
|
-
##
|
|
101
|
+
## Example App
|
|
101
102
|
|
|
102
|
-
Example
|
|
103
|
+
Example `Resource`, `Simulation`, and testing file (using `ChatGPTSimulator`) for an MCP resource called "Review".
|
|
103
104
|
|
|
104
|
-
### Resource Component
|
|
105
|
+
### `Resource` Component
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
```bash
|
|
108
|
+
my-app/
|
|
109
|
+
├── src/resources/
|
|
110
|
+
│ └── review/
|
|
111
|
+
│ ├── review-resource.tsx # This one!
|
|
112
|
+
│ └── review-resource.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
React component (`.tsx`) defining a UI (MCP resource) in your ChatGPT App.
|
|
107
116
|
|
|
108
117
|
```tsx
|
|
109
118
|
// src/resources/review-resource.tsx
|
|
110
|
-
|
|
119
|
+
|
|
120
|
+
import { useWidgetProps, useWidgetState } from 'sunpeak';
|
|
121
|
+
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
111
122
|
|
|
112
123
|
export function ReviewResource() {
|
|
124
|
+
const data = useWidgetProps<{ title: string; changes: string[] }>();
|
|
125
|
+
const [state, setState] = useWidgetState<{ decision: string | null }>(() => ({
|
|
126
|
+
decision: null,
|
|
127
|
+
}));
|
|
128
|
+
|
|
113
129
|
return (
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
<div>
|
|
131
|
+
<h1>{data.title}</h1>
|
|
132
|
+
<ul>
|
|
133
|
+
{data.changes.map((change, i) => (
|
|
134
|
+
<li key={i}>{change}</li>
|
|
135
|
+
))}
|
|
136
|
+
</ul>
|
|
137
|
+
{state.decision ? (
|
|
138
|
+
<p>{state.decision === 'approved' ? 'Approved!' : 'Rejected'}</p>
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
<Button onClick={() => setState({ decision: 'approved' })}>Approve</Button>
|
|
142
|
+
<Button onClick={() => setState({ decision: 'rejected' })}>Reject</Button>
|
|
143
|
+
</>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
124
146
|
);
|
|
125
147
|
}
|
|
126
148
|
```
|
|
127
149
|
|
|
128
|
-
### Resource MCP Metadata
|
|
150
|
+
### `Resource` MCP Metadata
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
my-app/
|
|
154
|
+
├── src/resources/
|
|
155
|
+
│ └── review/
|
|
156
|
+
│ ├── review-resource.tsx
|
|
157
|
+
│ └── review-resource.json # This one!
|
|
158
|
+
```
|
|
129
159
|
|
|
130
|
-
MCP metadata for your UI. Version your resource metadata alongside the resource itself.
|
|
160
|
+
MCP metadata (`.json`) for your UI. Version your resource metadata alongside the resource itself.
|
|
131
161
|
|
|
132
|
-
This is just an official [MCP
|
|
162
|
+
This is just an official [MCP resource object](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource).
|
|
133
163
|
|
|
134
164
|
```jsonc
|
|
135
165
|
// src/resources/review-resource.json
|
|
166
|
+
|
|
136
167
|
{
|
|
137
168
|
"name": "review",
|
|
138
169
|
"title": "Review",
|
|
@@ -147,21 +178,28 @@ This is just an official [MCP Resource object](https://modelcontextprotocol.io/s
|
|
|
147
178
|
}
|
|
148
179
|
```
|
|
149
180
|
|
|
150
|
-
### Simulation
|
|
181
|
+
### `Simulation`
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
├── tests/simulations/
|
|
185
|
+
│ └── review/
|
|
186
|
+
│ ├── review-{scenario1}-simulation.json # These!
|
|
187
|
+
│ └── review-{scenario2}-simulation.json # These!
|
|
188
|
+
```
|
|
151
189
|
|
|
152
|
-
`sunpeak` object
|
|
190
|
+
`sunpeak` testing object (`.json`) defining key App-owned states.
|
|
153
191
|
|
|
154
|
-
|
|
192
|
+
Testing a ChatGPT App requires setting a lot of state: state in your **backend**, **MCP tools**, **stored widget runtime**, and ChatGPT itself.
|
|
155
193
|
|
|
156
|
-
Simulation files contain an [official MCP
|
|
194
|
+
`Simulation` files contain an [official MCP tool object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool), an [official MCP CallToolResult object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#structured-content), and a [sunpeak widgetState object](https://docs.sunpeak.ai/api-reference/hooks/use-widget-state) so you can define **backend**, **tool**, and **stored widget runtime** states for testing.
|
|
157
195
|
|
|
158
196
|
```jsonc
|
|
159
197
|
// tests/simulations/review-diff-simulation.json
|
|
198
|
+
|
|
160
199
|
{
|
|
161
|
-
|
|
162
|
-
// Official MCP Tool object.
|
|
200
|
+
// Official MCP tool object.
|
|
163
201
|
"tool": {
|
|
164
|
-
"name": "diff
|
|
202
|
+
"name": "review-diff",
|
|
165
203
|
"description": "Show a review dialog for a proposed code diff",
|
|
166
204
|
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
|
|
167
205
|
"title": "Diff Review",
|
|
@@ -176,13 +214,55 @@ Simulation files contain an [official MCP Tool object](https://modelcontextproto
|
|
|
176
214
|
// Official MCP CallToolResult object.
|
|
177
215
|
"callToolResult": {
|
|
178
216
|
"structuredContent": {
|
|
217
|
+
"title": "Refactor Authentication Module",
|
|
179
218
|
// ...
|
|
180
219
|
},
|
|
181
220
|
"_meta": {},
|
|
182
221
|
},
|
|
222
|
+
// Initial widget state object (persisted in ChatGPT).
|
|
223
|
+
"widgetState": {
|
|
224
|
+
// ...
|
|
225
|
+
},
|
|
183
226
|
}
|
|
184
227
|
```
|
|
185
228
|
|
|
229
|
+
### `ChatGPTSimulator`
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
├── tests/e2e/
|
|
233
|
+
│ └── review.spec.ts # This! (not pictured above for simplicity)
|
|
234
|
+
└── package.json
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The `ChatGPTSimulator` allows you to set **ChatGPT state** (like light/dark mode) via URL params, which can be rendered alongside your `Simulation`s and tested via pre-configured Playwright end-to-end tests (`.spec.ts`).
|
|
238
|
+
|
|
239
|
+
Using the `ChatGPTSimulator` and `Simulation`s, you can test all possible App states locally and automatically!
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// tests/e2e/review.spec.ts
|
|
243
|
+
|
|
244
|
+
import { test, expect } from '@playwright/test';
|
|
245
|
+
import { createSimulatorUrl } from 'sunpeak/chatgpt';
|
|
246
|
+
|
|
247
|
+
test.describe('Review Resource', () => {
|
|
248
|
+
test.describe('Light Mode', () => {
|
|
249
|
+
test('should render review title with correct styles', async ({ page }) => {
|
|
250
|
+
params = { simulation: 'review-diff', theme: 'light' }; // Set sim & ChatGPT state.
|
|
251
|
+
await page.goto(createSimulatorUrl(params));
|
|
252
|
+
await page.waitForLoadState('networkidle');
|
|
253
|
+
|
|
254
|
+
const title = page.locator('h1:has-text("Refactor Authentication Module")');
|
|
255
|
+
await expect(title).toBeVisible();
|
|
256
|
+
|
|
257
|
+
const color = await title.evaluate((el) => window.getComputedStyle(el).color);
|
|
258
|
+
|
|
259
|
+
// Light mode should render dark text.
|
|
260
|
+
expect(color).toBe('rgb(13, 13, 13)');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
186
266
|
## Resources
|
|
187
267
|
|
|
188
268
|
- [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
|
package/bin/commands/dev.mjs
CHANGED
|
@@ -86,6 +86,9 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
86
86
|
// Parse --no-begging flag
|
|
87
87
|
const noBegging = args.includes('--no-begging');
|
|
88
88
|
|
|
89
|
+
// Parse --prod-mcp flag (serve production build files over MCP instead of Vite HMR)
|
|
90
|
+
const prodMcp = args.includes('--prod-mcp');
|
|
91
|
+
|
|
89
92
|
console.log(`Starting Vite dev server on port ${port}...`);
|
|
90
93
|
|
|
91
94
|
// Check if we're in the sunpeak workspace (directory is named "template")
|
|
@@ -177,37 +180,41 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
// Start MCP server with its own Vite instance
|
|
183
|
+
// Start MCP server with its own Vite instance (unless --prod-mcp is set)
|
|
181
184
|
if (simulations.length > 0) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const url = new URL(id.replace('virtual:sunpeak-entry', 'http://x'));
|
|
203
|
-
const srcPath = url.searchParams.get('src');
|
|
204
|
-
const componentName = url.searchParams.get('component');
|
|
205
|
-
|
|
206
|
-
if (!srcPath || !componentName) {
|
|
207
|
-
return 'console.error("Missing src or component param");';
|
|
185
|
+
const mcpMode = prodMcp ? 'production build' : 'Vite HMR';
|
|
186
|
+
console.log(`\nStarting MCP server with ${simulations.length} simulation(s) (${mcpMode})...`);
|
|
187
|
+
|
|
188
|
+
let mcpViteServer = null;
|
|
189
|
+
|
|
190
|
+
if (!prodMcp) {
|
|
191
|
+
// Virtual entry module plugin for MCP
|
|
192
|
+
// For internal dev (template): Import sunpeak styles directly via JS to avoid CSS @import
|
|
193
|
+
// alias issues with Lightning CSS, then import app.css for user customizations.
|
|
194
|
+
// For external users: Import OpenAI SDK CSS directly (ensures Tailwind processes @theme static),
|
|
195
|
+
// then import globals.css for user's Tailwind config and custom styles.
|
|
196
|
+
const styleImports = isTemplate
|
|
197
|
+
? `import '${parentSrc}/style.css';\nimport '/src/styles/app.css';`
|
|
198
|
+
: `import '@openai/apps-sdk-ui/css';\nimport '/src/styles/globals.css';`;
|
|
199
|
+
|
|
200
|
+
const sunpeakEntryPlugin = () => ({
|
|
201
|
+
name: 'sunpeak-entry',
|
|
202
|
+
resolveId(id) {
|
|
203
|
+
if (id.startsWith('virtual:sunpeak-entry')) {
|
|
204
|
+
return id;
|
|
208
205
|
}
|
|
206
|
+
},
|
|
207
|
+
load(id) {
|
|
208
|
+
if (id.startsWith('virtual:sunpeak-entry')) {
|
|
209
|
+
const url = new URL(id.replace('virtual:sunpeak-entry', 'http://x'));
|
|
210
|
+
const srcPath = url.searchParams.get('src');
|
|
211
|
+
const componentName = url.searchParams.get('component');
|
|
212
|
+
|
|
213
|
+
if (!srcPath || !componentName) {
|
|
214
|
+
return 'console.error("Missing src or component param");';
|
|
215
|
+
}
|
|
209
216
|
|
|
210
|
-
|
|
217
|
+
return `
|
|
211
218
|
import { createElement } from 'react';
|
|
212
219
|
import { createRoot } from 'react-dom/client';
|
|
213
220
|
${styleImports}
|
|
@@ -220,43 +227,44 @@ if (!Component) {
|
|
|
220
227
|
createRoot(document.getElementById('root')).render(createElement(Component));
|
|
221
228
|
}
|
|
222
229
|
`;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
});
|
|
226
233
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
// Create Vite dev server in middleware mode for MCP
|
|
235
|
+
// Use separate cache directory to avoid conflicts with main dev server
|
|
236
|
+
mcpViteServer = await createServer({
|
|
237
|
+
root: projectRoot,
|
|
238
|
+
cacheDir: 'node_modules/.vite-mcp',
|
|
239
|
+
plugins: [react(), tailwindcss(), sunpeakEntryPlugin()],
|
|
240
|
+
resolve: {
|
|
241
|
+
alias: {
|
|
242
|
+
...(isTemplate && {
|
|
243
|
+
sunpeak: parentSrc,
|
|
244
|
+
}),
|
|
245
|
+
},
|
|
238
246
|
},
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
server: {
|
|
248
|
+
middlewareMode: true,
|
|
249
|
+
allowedHosts: true,
|
|
250
|
+
watch: {
|
|
251
|
+
// Only watch files that affect the UI bundle (not JSON, tests, etc.)
|
|
252
|
+
// MCP resources reload on next tool call, not on file change
|
|
253
|
+
ignored: (filePath) => {
|
|
254
|
+
if (!filePath.includes('.')) return false; // Watch directories
|
|
255
|
+
if (/\.(tsx?|css)$/.test(filePath)) {
|
|
256
|
+
return /\.(test|spec)\.tsx?$/.test(filePath); // Ignore tests
|
|
257
|
+
}
|
|
258
|
+
return true; // Ignore everything else
|
|
259
|
+
},
|
|
252
260
|
},
|
|
253
261
|
},
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
262
|
+
optimizeDeps: {
|
|
263
|
+
include: ['react', 'react-dom/client'],
|
|
264
|
+
},
|
|
265
|
+
appType: 'custom',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
260
268
|
|
|
261
269
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
262
270
|
runMCPServer({
|
|
@@ -264,18 +272,18 @@ if (!Component) {
|
|
|
264
272
|
version: pkg.version || '0.1.0',
|
|
265
273
|
simulations,
|
|
266
274
|
port: 6766,
|
|
267
|
-
viteServer: mcpViteServer,
|
|
275
|
+
...(mcpViteServer && { viteServer: mcpViteServer }),
|
|
268
276
|
});
|
|
269
277
|
|
|
270
278
|
// Handle signals - close both servers
|
|
271
279
|
process.on('SIGINT', async () => {
|
|
272
|
-
await mcpViteServer.close();
|
|
280
|
+
if (mcpViteServer) await mcpViteServer.close();
|
|
273
281
|
await server.close();
|
|
274
282
|
process.exit(0);
|
|
275
283
|
});
|
|
276
284
|
|
|
277
285
|
process.on('SIGTERM', async () => {
|
|
278
|
-
await mcpViteServer.close();
|
|
286
|
+
if (mcpViteServer) await mcpViteServer.close();
|
|
279
287
|
await server.close();
|
|
280
288
|
process.exit(0);
|
|
281
289
|
});
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const simulatorUrl = require("../simulator-url-
|
|
3
|
+
const simulatorUrl = require("../simulator-url-Bnxt0LlN.cjs");
|
|
4
4
|
exports.ChatGPTSimulator = simulatorUrl.ChatGPTSimulator;
|
|
5
5
|
exports.IframeResource = simulatorUrl.IframeResource;
|
|
6
6
|
exports.ThemeProvider = simulatorUrl.ThemeProvider;
|
package/dist/chatgpt/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const simulatorUrl = require("./simulator-url-
|
|
3
|
+
const simulatorUrl = require("./simulator-url-Bnxt0LlN.cjs");
|
|
4
4
|
const React = require("react");
|
|
5
5
|
function _interopNamespaceDefault(e) {
|
|
6
6
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as clsx } from "./simulator-url-
|
|
2
|
-
import { C, I, S, T, c, v, s, q, i, r, w, t, e, f, g, h, j, u, k, l, m, n, d, b, o, p } from "./simulator-url-
|
|
1
|
+
import { a as clsx } from "./simulator-url-CQhLDEdd.js";
|
|
2
|
+
import { C, I, S, T, c, v, s, q, i, r, w, t, e, f, g, h, j, u, k, l, m, n, d, b, o, p } from "./simulator-url-CQhLDEdd.js";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
const MOBILE_BREAKPOINT = 768;
|
|
5
5
|
function useIsMobile() {
|
|
@@ -5620,7 +5620,7 @@ const useEscCloseStack = (listening, cb) => {
|
|
|
5620
5620
|
}, [id, listening, latestCallback]);
|
|
5621
5621
|
};
|
|
5622
5622
|
const __vite_import_meta_env__ = { "DEV": false, "MODE": "production" };
|
|
5623
|
-
const META_ENV = typeof { url: typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("simulator-url-
|
|
5623
|
+
const META_ENV = typeof { url: typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("simulator-url-Bnxt0LlN.cjs", document.baseURI).href } !== "undefined" ? __vite_import_meta_env__ : void 0;
|
|
5624
5624
|
const NODE_ENV = typeof process !== "undefined" && process.env?.NODE_ENV ? process.env?.NODE_ENV : "production";
|
|
5625
5625
|
const isDev = NODE_ENV === "development" || !!META_ENV?.DEV;
|
|
5626
5626
|
const isJSDomLike = typeof navigator !== "undefined" && /(jsdom|happy-dom)/i.test(navigator.userAgent) || typeof globalThis.happyDOM === "object";
|
|
@@ -8744,6 +8744,7 @@ function ChatGPTSimulator({
|
|
|
8744
8744
|
mock.setWidgetStateExternal(selectedSim.widgetState);
|
|
8745
8745
|
}
|
|
8746
8746
|
mock.toolOutput = selectedSim.callToolResult?.structuredContent ?? null;
|
|
8747
|
+
mock.toolResponseMetadata = selectedSim.callToolResult?._meta ?? null;
|
|
8747
8748
|
}
|
|
8748
8749
|
}, [selectedSimulationName, selectedSim, mock]);
|
|
8749
8750
|
const theme = useTheme() ?? DEFAULT_THEME;
|
|
@@ -9280,4 +9281,4 @@ exports.useWidgetAPI = useWidgetAPI;
|
|
|
9280
9281
|
exports.useWidgetGlobal = useWidgetGlobal;
|
|
9281
9282
|
exports.useWidgetProps = useWidgetProps;
|
|
9282
9283
|
exports.useWidgetState = useWidgetState;
|
|
9283
|
-
//# sourceMappingURL=simulator-url-
|
|
9284
|
+
//# sourceMappingURL=simulator-url-Bnxt0LlN.cjs.map
|