sunpeak 0.12.7 → 0.12.11

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 (94) hide show
  1. package/README.md +123 -43
  2. package/bin/commands/build.mjs +66 -3
  3. package/dist/chatgpt/index.cjs +1 -1
  4. package/dist/chatgpt/index.js +1 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/{simulator-url-CYMOGoB1.cjs → simulator-url-Bnxt0LlN.cjs} +3 -2
  8. package/dist/{simulator-url-CYMOGoB1.cjs.map → simulator-url-Bnxt0LlN.cjs.map} +1 -1
  9. package/dist/{simulator-url-DG79-dU3.js → simulator-url-CQhLDEdd.js} +2 -1
  10. package/dist/{simulator-url-DG79-dU3.js.map → simulator-url-CQhLDEdd.js.map} +1 -1
  11. package/package.json +1 -1
  12. package/template/dist/albums/albums.js +47 -7
  13. package/template/dist/albums/albums.json +1 -1
  14. package/template/dist/carousel/carousel.js +47 -7
  15. package/template/dist/carousel/carousel.json +1 -1
  16. package/template/dist/map/map.js +187 -140
  17. package/template/dist/map/map.json +1 -1
  18. package/template/dist/review/review.js +47 -7
  19. package/template/dist/review/review.json +1 -1
  20. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
  21. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
  22. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +15 -15
  23. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
  24. package/template/node_modules/.vite/deps/_metadata.json +32 -32
  25. package/template/node_modules/.vite/deps/{chunk-24D227QE.js → chunk-OBBBT4IN.js} +7 -7
  26. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  27. package/template/tests/e2e/review.spec.ts +1 -1
  28. package/template/tests/simulations/review/review-diff-simulation.json +1 -1
  29. /package/template/node_modules/.vite/deps/{chunk-24D227QE.js.map → chunk-OBBBT4IN.js.map} +0 -0
  30. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Avatar.js +0 -0
  31. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Avatar.js.map +0 -0
  32. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Button.js +0 -0
  33. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Button.js.map +0 -0
  34. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Checkbox.js +0 -0
  35. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Checkbox.js.map +0 -0
  36. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Icon.js +0 -0
  37. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Icon.js.map +0 -0
  38. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Input.js +0 -0
  39. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Input.js.map +0 -0
  40. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_SegmentedControl.js +0 -0
  41. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_SegmentedControl.js.map +0 -0
  42. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Select.js +0 -0
  43. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Select.js.map +0 -0
  44. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Textarea.js +0 -0
  45. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_components_Textarea.js.map +0 -0
  46. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_theme.js +0 -0
  47. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/@openai_apps-sdk-ui_theme.js.map +0 -0
  48. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-6CR5OFLH.js +0 -0
  49. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-6CR5OFLH.js.map +0 -0
  50. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-EGRHWZRV.js +0 -0
  51. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-EGRHWZRV.js.map +0 -0
  52. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-EXGLAROW.js +0 -0
  53. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-EXGLAROW.js.map +0 -0
  54. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-IFPDCKUT.js +0 -0
  55. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-IFPDCKUT.js.map +0 -0
  56. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-ILHRZGIS.js +0 -0
  57. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-ILHRZGIS.js.map +0 -0
  58. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-JSPMUVNT.js +0 -0
  59. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-JSPMUVNT.js.map +0 -0
  60. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-L2PWLFDO.js +0 -0
  61. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-L2PWLFDO.js.map +0 -0
  62. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-OMOWBQDU.js +0 -0
  63. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-OMOWBQDU.js.map +0 -0
  64. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-R3BTL4CL.js +0 -0
  65. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-R3BTL4CL.js.map +0 -0
  66. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-SPDZ46BB.js +0 -0
  67. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-SPDZ46BB.js.map +0 -0
  68. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-WSHFT23M.js +0 -0
  69. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-WSHFT23M.js.map +0 -0
  70. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-Y7UNCRGH.js +0 -0
  71. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-Y7UNCRGH.js.map +0 -0
  72. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-ZNFXUT4Q.js +0 -0
  73. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/chunk-ZNFXUT4Q.js.map +0 -0
  74. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/clsx.js +0 -0
  75. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/clsx.js.map +0 -0
  76. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/embla-carousel-react.js +0 -0
  77. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/embla-carousel-react.js.map +0 -0
  78. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/embla-carousel-wheel-gestures.js +0 -0
  79. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/embla-carousel-wheel-gestures.js.map +0 -0
  80. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/mapbox-gl.js +0 -0
  81. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/mapbox-gl.js.map +0 -0
  82. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/package.json +0 -0
  83. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react-dom.js +0 -0
  84. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react-dom.js.map +0 -0
  85. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react-dom_client.js +0 -0
  86. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react-dom_client.js.map +0 -0
  87. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react.js +0 -0
  88. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react.js.map +0 -0
  89. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react_jsx-dev-runtime.js +0 -0
  90. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react_jsx-dev-runtime.js.map +0 -0
  91. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react_jsx-runtime.js +0 -0
  92. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/react_jsx-runtime.js.map +0 -0
  93. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/tailwind-merge.js +0 -0
  94. /package/template/node_modules/.vite-mcp/{deps_temp_01980537 → deps_temp_e6e57b12}/tailwind-merge.js.map +0 -0
package/README.md CHANGED
@@ -8,16 +8,17 @@
8
8
 
9
9
  # sunpeak
10
10
 
11
- [![npm version](https://img.shields.io/npm/v/sunpeak.svg?style=flat-square)](https://www.npmjs.com/package/sunpeak)
12
- [![npm downloads](https://img.shields.io/npm/dm/sunpeak.svg?style=flat-square)](https://www.npmjs.com/package/sunpeak)
13
- [![CI](https://img.shields.io/github/actions/workflow/status/Sunpeak-AI/sunpeak/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/Sunpeak-AI/sunpeak/actions)
14
- [![License](https://img.shields.io/npm/l/sunpeak.svg?style=flat-square)](https://github.com/Sunpeak-AI/sunpeak/blob/main/LICENSE)
15
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
16
- [![React](https://img.shields.io/badge/React-19-blue?style=flat-square&logo=react)](https://reactjs.org/)
11
+ [![npm version](https://img.shields.io/npm/v/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://www.npmjs.com/package/sunpeak)
12
+ [![npm downloads](https://img.shields.io/npm/dm/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://www.npmjs.com/package/sunpeak)
13
+ [![stars](https://img.shields.io/github/stars/Sunpeak-AI/sunpeak?style=flat&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak)
14
+ [![CI](https://img.shields.io/github/actions/workflow/status/Sunpeak-AI/sunpeak/ci.yml?branch=main&style=flat&label=ci&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak/actions)
15
+ [![License](https://img.shields.io/npm/l/sunpeak.svg?style=flat&color=FFB800&labelColor=1A1F36)](https://github.com/Sunpeak-AI/sunpeak/blob/main/LICENSE)
16
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?style=flat&logo=typescript&label=ts&color=FFB800&logoColor=white&labelColor=1A1F36)](https://www.typescriptlang.org/)
17
+ [![React](https://img.shields.io/badge/React-19-blue?style=flat&logo=react&label=react&color=FFB800&logoColor=white&labelColor=1A1F36)](https://reactjs.org/)
17
18
 
18
- The ChatGPT App framework.
19
+ Local-first ChatGPT App framework.
19
20
 
20
- Quickstart, build, test, and ship your ChatGPT App—locally!
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 Resources) while keeping your MCP server client-agnostic. sunpeak consists of:
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 page as an example, sunpeak projects look like:
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 UIs by creating a `-resource.tsx` component file ([example](#resource-component)) and `-resource.json` MCP metadata file ([example](#resource-mcp-metadata)).
79
- 2. Create test state (simulations) for local dev, ChatGPT dev, automated testing, and demos by creating a `-simulation.json` file. ([example](#simulation))
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
- ## Examples
101
+ ## Example App
101
102
 
102
- Example sunpeak resource & simulation files for an MCP Resource called "Review".
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
- React component defining a UI (MCP Resource) in your ChatGPT App.
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
- import { Card } from './components';
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
- <Card
115
- image="https://images.unsplash.com/photo-1520950237264-dfe336995c34"
116
- imageAlt="Lady Bird Lake"
117
- header="Lady Bird Lake"
118
- metadata="⭐ 4.5 • Austin, TX"
119
- button1={{ children: 'Visit', isPrimary: true, onClick: () => {} }}
120
- button2={{ children: 'Learn More', onClick: () => {} }}
121
- >
122
- Scenic lake perfect for kayaking, paddleboarding, and trails.
123
- </Card>
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 Resource object](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource).
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. Testing a ChatGPT App require setting a lot of state: state in your backend (accessed via MCP tool), the stored widget runtime, and ChatGPT itself.
190
+ `sunpeak` testing object (`.json`) defining key App-owned states.
153
191
 
154
- Simulation files let you define key App states for development, automated testing, and demo purposes.
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 Tool object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) and an [official MCP CallToolResult object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#structured-content). ChatGPT state (like light/dark mode) is not set on the simulation, but rather on the sunpeak `ChatGPTSimulator` itself via UI, props, or URL params.
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
- "userMessage": "Refactor the authentication module", // Simulator styling.
162
- // Official MCP Tool object.
200
+ // Official MCP tool object.
163
201
  "tool": {
164
- "name": "diff-review",
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)
@@ -1,11 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import { build as viteBuild } from 'vite';
3
- import react from '@vitejs/plugin-react';
4
- import tailwindcss from '@tailwindcss/vite';
5
2
  import { existsSync, rmSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from 'fs';
6
3
  import path from 'path';
4
+ import { createRequire } from 'module';
5
+ import { pathToFileURL } from 'url';
7
6
  import { toPascalCase } from '../lib/patterns.mjs';
8
7
 
8
+ /**
9
+ * Resolve the ESM entry point for a package from a specific project directory.
10
+ * This avoids the CJS deprecation warning from packages like Vite.
11
+ */
12
+ function resolveEsmEntry(require, packageName) {
13
+ // First resolve to find where the package is located
14
+ const resolvedPath = require.resolve(packageName);
15
+
16
+ // Walk up to find the package's package.json
17
+ let dir = path.dirname(resolvedPath);
18
+ while (dir !== path.dirname(dir)) {
19
+ const pkgJsonPath = path.join(dir, 'package.json');
20
+ if (existsSync(pkgJsonPath)) {
21
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
22
+ if (pkg.name === packageName) {
23
+ // Found the package.json, look for ESM entry in exports
24
+ const exports = pkg.exports;
25
+ if (exports?.['.']?.import) {
26
+ const importEntry = exports['.'].import;
27
+ // Handle nested conditions like { types, default }
28
+ const esmPath = typeof importEntry === 'string' ? importEntry : importEntry.default;
29
+ if (esmPath) {
30
+ return pathToFileURL(path.join(dir, esmPath)).href;
31
+ }
32
+ }
33
+ // Fallback to module field
34
+ if (pkg.module) {
35
+ return pathToFileURL(path.join(dir, pkg.module)).href;
36
+ }
37
+ break;
38
+ }
39
+ }
40
+ dir = path.dirname(dir);
41
+ }
42
+
43
+ // Fallback to resolved path (may be CJS)
44
+ return pathToFileURL(resolvedPath).href;
45
+ }
46
+
9
47
  /**
10
48
  * Build all resources for a Sunpeak project
11
49
  * Runs in the context of a user's project directory
@@ -48,6 +86,31 @@ export async function build(projectRoot = process.cwd()) {
48
86
  process.exit(1);
49
87
  }
50
88
 
89
+ // Import vite and plugins from the user's project (not from sunpeak's node_modules)
90
+ // This allows sunpeak to work when installed globally
91
+ // We resolve to ESM entry points to avoid the CJS deprecation warning from Vite
92
+ const require = createRequire(path.join(projectRoot, 'package.json'));
93
+ let viteBuild, react, tailwindcss;
94
+ try {
95
+ const [viteModule, reactModule, tailwindModule] = await Promise.all([
96
+ import(resolveEsmEntry(require, 'vite')),
97
+ import(resolveEsmEntry(require, '@vitejs/plugin-react')),
98
+ import(resolveEsmEntry(require, '@tailwindcss/vite')),
99
+ ]);
100
+ viteBuild = viteModule.build;
101
+ react = reactModule.default;
102
+ tailwindcss = tailwindModule.default;
103
+ } catch (error) {
104
+ console.error('Error: Could not load build dependencies from your project.');
105
+ console.error('\nMake sure you have these packages installed in your project:');
106
+ console.error(' - vite');
107
+ console.error(' - @vitejs/plugin-react');
108
+ console.error(' - @tailwindcss/vite');
109
+ console.error('\nRun: npm install -D vite @vitejs/plugin-react @tailwindcss/vite');
110
+ console.error('\nOriginal error:', error.message);
111
+ process.exit(1);
112
+ }
113
+
51
114
  // Plugin factory to inline CSS into the JS bundle for all output files
52
115
  const inlineCssPlugin = (buildOutDir) => ({
53
116
  name: 'inline-css',
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const simulatorUrl = require("../simulator-url-CYMOGoB1.cjs");
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;
@@ -1,4 +1,4 @@
1
- import { C, I, T, c, i, u } from "../simulator-url-DG79-dU3.js";
1
+ import { C, I, T, c, i, u } from "../simulator-url-CQhLDEdd.js";
2
2
  export {
3
3
  C as ChatGPTSimulator,
4
4
  I as IframeResource,
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-CYMOGoB1.cjs");
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-DG79-dU3.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-DG79-dU3.js";
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-CYMOGoB1.cjs", document.baseURI).href } !== "undefined" ? __vite_import_meta_env__ : void 0;
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-CYMOGoB1.cjs.map
9284
+ //# sourceMappingURL=simulator-url-Bnxt0LlN.cjs.map