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.
Files changed (76) hide show
  1. package/README.md +2 -1
  2. package/bin/commands/deploy.mjs +18 -8
  3. package/bin/commands/dev.mjs +60 -4
  4. package/bin/commands/login.mjs +73 -55
  5. package/bin/commands/logout.mjs +26 -12
  6. package/bin/commands/mcp.mjs +1 -1
  7. package/bin/commands/pull.mjs +60 -39
  8. package/bin/commands/push.mjs +73 -49
  9. package/bin/commands/upgrade.mjs +203 -0
  10. package/bin/sunpeak.js +68 -35
  11. package/dist/chatgpt/chatgpt-simulator.d.ts +2 -1
  12. package/dist/index.cjs +13 -14
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.js +13 -14
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp/entry.cjs +41 -9
  17. package/dist/mcp/entry.cjs.map +1 -1
  18. package/dist/mcp/entry.js +42 -10
  19. package/dist/mcp/entry.js.map +1 -1
  20. package/dist/mcp/index.cjs +1 -1
  21. package/dist/mcp/index.js +1 -1
  22. package/dist/{server-CziiHU7V.cjs → server-B9YgCQdS.cjs} +3 -2
  23. package/dist/{server-CziiHU7V.cjs.map → server-B9YgCQdS.cjs.map} +1 -1
  24. package/dist/{server-D8kyzuiq.js → server-DVmTC-SF.js} +3 -2
  25. package/dist/{server-D8kyzuiq.js.map → server-DVmTC-SF.js.map} +1 -1
  26. package/dist/style.css +62 -0
  27. package/dist/types/simulation.d.ts +1 -1
  28. package/package.json +1 -1
  29. package/template/.sunpeak/dev.tsx +78 -15
  30. package/template/.sunpeak/vite-env.d.ts +1 -0
  31. package/template/README.md +35 -20
  32. package/template/dist/albums.js +1 -1
  33. package/template/dist/albums.json +3 -2
  34. package/template/dist/carousel.js +1 -1
  35. package/template/dist/carousel.json +3 -2
  36. package/template/dist/confirmation.js +49 -0
  37. package/template/dist/confirmation.json +16 -0
  38. package/template/dist/counter.js +1 -1
  39. package/template/dist/counter.json +7 -2
  40. package/template/dist/map.js +1 -1
  41. package/template/dist/map.json +6 -3
  42. package/template/node_modules/.vite/deps/_metadata.json +19 -19
  43. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  44. package/template/src/components/map/map-view.test.tsx +1 -1
  45. package/template/src/components/map/map-view.tsx +1 -1
  46. package/template/src/components/map/map.tsx +1 -1
  47. package/template/src/components/map/place-card.test.tsx +1 -1
  48. package/template/src/components/map/place-card.tsx +1 -1
  49. package/template/src/components/map/place-carousel.test.tsx +1 -1
  50. package/template/src/components/map/place-carousel.tsx +1 -1
  51. package/template/src/components/map/place-inspector.test.tsx +1 -1
  52. package/template/src/components/map/place-inspector.tsx +1 -1
  53. package/template/src/components/map/place-list.test.tsx +1 -1
  54. package/template/src/components/map/place-list.tsx +1 -1
  55. package/template/src/components/map/types.ts +18 -0
  56. package/template/src/resources/albums-resource.json +1 -1
  57. package/template/src/resources/carousel-resource.json +1 -1
  58. package/template/src/resources/confirmation-resource.json +12 -0
  59. package/template/src/resources/confirmation-resource.tsx +479 -0
  60. package/template/src/resources/counter-resource.json +4 -1
  61. package/template/src/resources/index.ts +39 -4
  62. package/template/src/resources/map-resource.json +7 -2
  63. package/template/src/simulations/albums-show-simulation.json +131 -0
  64. package/template/src/simulations/carousel-show-simulation.json +68 -0
  65. package/template/src/simulations/confirmation-diff-simulation.json +80 -0
  66. package/template/src/simulations/confirmation-post-simulation.json +56 -0
  67. package/template/src/simulations/confirmation-purchase-simulation.json +88 -0
  68. package/template/src/simulations/counter-show-simulation.json +20 -0
  69. package/template/src/simulations/index.ts +17 -12
  70. package/template/src/simulations/map-show-simulation.json +123 -0
  71. package/template/src/vite-env.d.ts +1 -0
  72. package/template/tsconfig.json +1 -1
  73. package/template/src/simulations/albums-simulation.ts +0 -147
  74. package/template/src/simulations/carousel-simulation.ts +0 -84
  75. package/template/src/simulations/counter-simulation.ts +0 -34
  76. 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;
@@ -42,6 +42,6 @@ export interface Simulation {
42
42
  userMessage?: string;
43
43
  simulationGlobals?: SimulationGlobals;
44
44
  tool: Tool;
45
- resource: Resource;
45
+ resource?: Resource;
46
46
  toolCall?: SimulationCallToolResult;
47
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.7.11",
3
+ "version": "0.8.4",
4
4
  "description": "The ChatGPT App framework. Quickstart, build, & test your ChatGPT App locally!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -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 { SIMULATIONS } from '../src/simulations';
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 getResourceComponentFromName(name: string): React.ComponentType {
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 = Resources[componentName as keyof typeof Resources];
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 it's exported from src/resources/index.ts`
66
+ `Make sure src/resources/${name}-resource.tsx exists with a default export.`
27
67
  );
28
68
  }
29
69
 
30
- return component as React.ComponentType;
70
+ return component;
31
71
  }
32
72
 
33
- // Package the resource component with the simulation
34
- const simulations: Simulation[] = Object.values(
35
- SIMULATIONS as Record<string, Omit<Simulation, 'resourceComponent'>>
36
- ).map((simulation) => ({
37
- ...simulation,
38
- resourceComponent: getResourceComponentFromName(simulation.resource.name),
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 package.json or use defaults
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" />
@@ -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 the sunpeak API, refer to the [documentation](https://docs.sunpeak.ai/template/project-structure).
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
- pnpm dev
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
- pnpm dev # Start development server
20
- pnpm build # Build all resources for production
21
- pnpm mcp # Start MCP server with auto-reload
22
- pnpm test # Run tests with Vitest
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
- pnpm mcp
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 `show counter`, to ChatGPT.
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
- pnpm build
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/carousel.js`
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
- Each file is a self-contained bundle with CSS inlined. Host these files and reference them as resources in your production MCP server.
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