sunpeak 0.6.1 → 0.6.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 (40) hide show
  1. package/bin/sunpeak.js +129 -6
  2. package/dist/chatgpt/conversation.d.ts +2 -1
  3. package/dist/index.cjs +24 -4
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +24 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/mcp/entry.cjs +2 -2
  8. package/dist/mcp/entry.cjs.map +1 -1
  9. package/dist/mcp/entry.js +2 -2
  10. package/dist/mcp/entry.js.map +1 -1
  11. package/dist/mcp/index.cjs +1 -1
  12. package/dist/mcp/index.js +1 -1
  13. package/dist/{server-DpriZ4jT.cjs → server-CQGbJWbk.cjs} +17 -8
  14. package/dist/{server-DpriZ4jT.cjs.map → server-CQGbJWbk.cjs.map} +1 -1
  15. package/dist/{server-SBlanUcf.js → server-DGCvp1RA.js} +17 -8
  16. package/dist/{server-SBlanUcf.js.map → server-DGCvp1RA.js.map} +1 -1
  17. package/dist/style.css +4 -0
  18. package/package.json +1 -1
  19. package/template/.sunpeak/dev.tsx +1 -1
  20. package/template/dist/chatgpt/albums.js +2 -2
  21. package/template/dist/chatgpt/carousel.js +1 -1
  22. package/template/dist/chatgpt/counter.js +1 -1
  23. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +2 -2
  24. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +9 -9
  25. package/template/node_modules/.vite/deps/_metadata.json +22 -22
  26. package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js → chunk-EVJ3DVH5.js} +5 -5
  27. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  28. package/template/src/components/album/album-carousel.test.tsx +84 -0
  29. package/template/src/components/album/album-carousel.tsx +168 -0
  30. package/template/src/components/album/albums.test.tsx +2 -2
  31. package/template/src/components/album/albums.tsx +3 -3
  32. package/template/src/components/album/index.ts +1 -0
  33. package/template/src/components/carousel/index.ts +1 -0
  34. package/template/src/components/index.ts +0 -1
  35. package/template/src/resources/carousel-resource.test.tsx +1 -4
  36. package/template/src/resources/carousel-resource.tsx +1 -2
  37. package/template/src/components/card/index.ts +0 -1
  38. /package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js.map → chunk-EVJ3DVH5.js.map} +0 -0
  39. /package/template/src/components/{card → carousel}/card.test.tsx +0 -0
  40. /package/template/src/components/{card → carousel}/card.tsx +0 -0
package/bin/sunpeak.js CHANGED
@@ -27,7 +27,81 @@ function checkPackageJson() {
27
27
  }
28
28
  }
29
29
 
30
- async function init(projectName) {
30
+ function parseResourcesInput(input) {
31
+ const VALID_RESOURCES = ['albums', 'carousel', 'counter'];
32
+
33
+ // If no input, return all resources
34
+ if (!input || input.trim() === '') {
35
+ return VALID_RESOURCES;
36
+ }
37
+
38
+ // Split by comma or space and trim
39
+ const tokens = input
40
+ .toLowerCase()
41
+ .split(/[,\s]+/)
42
+ .map((s) => s.trim())
43
+ .filter((s) => s.length > 0);
44
+
45
+ // Validate tokens
46
+ const invalid = tokens.filter((t) => !VALID_RESOURCES.includes(t));
47
+ if (invalid.length > 0) {
48
+ console.error(`Error: Invalid resource(s): ${invalid.join(', ')}`);
49
+ console.error(`Valid resources are: ${VALID_RESOURCES.join(', ')}`);
50
+ process.exit(1);
51
+ }
52
+
53
+ // Remove duplicates
54
+ return [...new Set(tokens)];
55
+ }
56
+
57
+ function updateIndexFiles(targetDir, selectedResources) {
58
+ // Map resource names to their component/export names
59
+ const resourceMap = {
60
+ albums: { component: 'album', resourceClass: 'AlbumsResource', simulation: 'albums' },
61
+ carousel: { component: 'carousel', resourceClass: 'CarouselResource', simulation: 'carousel' },
62
+ counter: { component: null, resourceClass: 'CounterResource', simulation: 'counter' },
63
+ };
64
+
65
+ // Update components/index.ts
66
+ const componentsIndexPath = join(targetDir, 'src', 'components', 'index.ts');
67
+ const componentExports = selectedResources
68
+ .map((r) => resourceMap[r].component)
69
+ .filter((comp) => comp !== null) // Filter out null components
70
+ .filter((v, i, a) => a.indexOf(v) === i) // Remove duplicates
71
+ .map((comp) => `export * from './${comp}';`)
72
+ .join('\n');
73
+ writeFileSync(componentsIndexPath, componentExports + '\n');
74
+
75
+ // Update resources/index.ts
76
+ const resourcesIndexPath = join(targetDir, 'src', 'resources', 'index.ts');
77
+ const resourceExports = selectedResources
78
+ .map((r) => `export { ${resourceMap[r].resourceClass} } from './${r}-resource';`)
79
+ .join('\n');
80
+ writeFileSync(resourcesIndexPath, resourceExports + '\n');
81
+
82
+ // Update simulations/index.ts
83
+ const simulationsIndexPath = join(targetDir, 'src', 'simulations', 'index.ts');
84
+ const simulationImports = selectedResources
85
+ .map((r) => `import { ${r}Simulation } from './${r}-simulation.js';`)
86
+ .join('\n');
87
+ const simulationExports = selectedResources.map((r) => ` ${r}: ${r}Simulation,`).join('\n');
88
+ const simulationsContent = `/**
89
+ * Server-safe simulation configurations
90
+ *
91
+ * This file contains only metadata and can be safely imported in Node.js contexts
92
+ * (like MCP servers) without causing issues with CSS imports or React components.
93
+ */
94
+
95
+ ${simulationImports}
96
+
97
+ export const SIMULATIONS = {
98
+ ${simulationExports}
99
+ } as const;
100
+ `;
101
+ writeFileSync(simulationsIndexPath, simulationsContent);
102
+ }
103
+
104
+ async function init(projectName, resourcesArg) {
31
105
  if (!projectName) {
32
106
  projectName = await prompt('☀️ 🏔️ Project name [my-app]: ');
33
107
  if (!projectName) {
@@ -40,6 +114,16 @@ async function init(projectName) {
40
114
  process.exit(1);
41
115
  }
42
116
 
117
+ // Use resources from args or ask for them
118
+ let resourcesInput;
119
+ if (resourcesArg) {
120
+ resourcesInput = resourcesArg;
121
+ console.log(`☀️ 🏔️ Resources: ${resourcesArg}`);
122
+ } else {
123
+ resourcesInput = await prompt('☀️ 🏔️ Resources (UIs) to include [albums, carousel, counter]: ');
124
+ }
125
+ const selectedResources = parseResourcesInput(resourcesInput);
126
+
43
127
  const targetDir = join(process.cwd(), projectName);
44
128
 
45
129
  if (existsSync(targetDir)) {
@@ -53,11 +137,44 @@ async function init(projectName) {
53
137
 
54
138
  mkdirSync(targetDir, { recursive: true });
55
139
 
140
+ // Map resource names to their component directory names
141
+ const resourceComponentMap = {
142
+ albums: 'album',
143
+ carousel: 'carousel',
144
+ counter: null, // Counter doesn't have a component directory
145
+ };
146
+
56
147
  cpSync(templateDir, targetDir, {
57
148
  recursive: true,
58
149
  filter: (src) => {
59
150
  const name = basename(src);
60
- return name !== 'node_modules' && name !== 'pnpm-lock.yaml';
151
+
152
+ // Skip node_modules and lock file
153
+ if (name === 'node_modules' || name === 'pnpm-lock.yaml') {
154
+ return false;
155
+ }
156
+
157
+ // Filter resource files based on selection
158
+ const VALID_RESOURCES = ['albums', 'carousel', 'counter'];
159
+ const excludedResources = VALID_RESOURCES.filter((r) => !selectedResources.includes(r));
160
+
161
+ for (const resource of excludedResources) {
162
+ // Skip resource files
163
+ if (name === `${resource}-resource.tsx` || name === `${resource}-resource.test.tsx`) {
164
+ return false;
165
+ }
166
+ // Skip simulation files
167
+ if (name === `${resource}-simulation.ts`) {
168
+ return false;
169
+ }
170
+ // Skip component directories (map resource name to component dir name)
171
+ const componentDirName = resourceComponentMap[resource];
172
+ if (componentDirName && src.includes('/components/') && name === componentDirName) {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ return true;
61
178
  },
62
179
  });
63
180
 
@@ -88,6 +205,9 @@ async function init(projectName) {
88
205
 
89
206
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
90
207
 
208
+ // Update index.ts files based on selected resources
209
+ updateIndexFiles(targetDir, selectedResources);
210
+
91
211
  console.log(`
92
212
  Done! To get started:
93
213
 
@@ -119,7 +239,7 @@ const [, , command, ...args] = process.argv;
119
239
 
120
240
  switch (command) {
121
241
  case 'new':
122
- await init(args[0]);
242
+ await init(args[0], args[1]);
123
243
  break;
124
244
 
125
245
  case 'dev':
@@ -149,8 +269,11 @@ const [, , command, ...args] = process.argv;
149
269
  ☀️ 🏔️ sunpeak - The MCP App framework
150
270
 
151
271
  Usage:
152
- npx sunpeak new [name] Create a new project (no install needed)
153
- pnpm dlx sunpeak new Alternative with pnpm
272
+ npx sunpeak new [name] [resources] Create a new project (no install needed)
273
+ pnpm dlx sunpeak new Alternative with pnpm
274
+
275
+ Resources: albums, carousel, counter (comma/space separated, or "all")
276
+ Example: npx sunpeak new my-app "albums,carousel"
154
277
 
155
278
  Inside your project, use npm scripts:
156
279
  pnpm dev Start development server
@@ -159,7 +282,7 @@ Inside your project, use npm scripts:
159
282
  pnpm test Run tests
160
283
 
161
284
  Direct CLI commands (when sunpeak is installed):
162
- sunpeak new [name] Create a new project
285
+ sunpeak new [name] [resources] Create a new project
163
286
  sunpeak dev Start dev server
164
287
  sunpeak build Build resources
165
288
  sunpeak mcp Start MCP server
@@ -6,6 +6,7 @@ interface ConversationProps {
6
6
  appName?: string;
7
7
  appIcon?: string;
8
8
  userMessage?: string;
9
+ resourceMeta?: Record<string, unknown>;
9
10
  }
10
- export declare function Conversation({ children, screenWidth, appName, appIcon, userMessage, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function Conversation({ children, screenWidth, appName, appIcon, userMessage, resourceMeta, }: ConversationProps): import("react/jsx-runtime").JSX.Element;
11
12
  export {};
package/dist/index.cjs CHANGED
@@ -7572,9 +7572,10 @@ const SCREEN_WIDTHS = {
7572
7572
  function Conversation({
7573
7573
  children,
7574
7574
  screenWidth,
7575
- appName = "Sunpeak App",
7575
+ appName = "Sunpeak",
7576
7576
  appIcon,
7577
- userMessage = "What have you got for me today?"
7577
+ userMessage = "What have you got for me today?",
7578
+ resourceMeta
7578
7579
  }) {
7579
7580
  const displayMode = useDisplayMode() ?? "inline";
7580
7581
  const api = useWidgetAPI();
@@ -7608,7 +7609,25 @@ function Conversation({
7608
7609
  }
7609
7610
  ) }),
7610
7611
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-primary flex items-center justify-center text-base", children: appName }),
7611
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end" })
7612
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(
7613
+ Button,
7614
+ {
7615
+ variant: "outline",
7616
+ color: "primary",
7617
+ className: "bg-token-bg-primary",
7618
+ onClick: () => {
7619
+ const widgetDomain = resourceMeta == null ? void 0 : resourceMeta["openai/widgetDomain"];
7620
+ if ((api == null ? void 0 : api.openExternal) && widgetDomain) {
7621
+ api.openExternal({ href: widgetDomain });
7622
+ }
7623
+ },
7624
+ children: [
7625
+ appIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "me-2 h-4 w-4 flex items-center justify-center", children: appIcon }),
7626
+ "Open in ",
7627
+ appName
7628
+ ]
7629
+ }
7630
+ ) })
7612
7631
  ] }),
7613
7632
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative overflow-hidden flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full max-w-full overflow-auto", children }) }),
7614
7633
  /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "bg-surface", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[48rem] mx-auto px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -7917,7 +7936,7 @@ const DEFAULT_DISPLAY_MODE = "inline";
7917
7936
  function ChatGPTSimulator({
7918
7937
  children,
7919
7938
  simulations = [],
7920
- appName = "Sunpeak App",
7939
+ appName = "Sunpeak",
7921
7940
  appIcon
7922
7941
  }) {
7923
7942
  const [screenWidth, setScreenWidth] = React__namespace.useState("full");
@@ -8411,6 +8430,7 @@ function ChatGPTSimulator({
8411
8430
  appName,
8412
8431
  appIcon,
8413
8432
  userMessage,
8433
+ resourceMeta: selectedSim == null ? void 0 : selectedSim.resource._meta,
8414
8434
  children: content
8415
8435
  },
8416
8436
  selectedKey