Skip to content

Commit 06e786f

Browse files
committed
Add confirmation resource, fix sunpeak dev
1 parent e643907 commit 06e786f

8 files changed

Lines changed: 782 additions & 9 deletions

File tree

bin/commands/dev.mjs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,56 @@
11
#!/usr/bin/env node
2-
import { createServer } from 'vite';
3-
import react from '@vitejs/plugin-react';
4-
import tailwindcss from '@tailwindcss/vite';
52
import { existsSync } from 'fs';
6-
import { join, resolve, basename } from 'path';
3+
import { join, resolve, basename, dirname } from 'path';
4+
import { createRequire } from 'module';
5+
import { pathToFileURL } from 'url';
6+
7+
/**
8+
* Import a module from the project's node_modules using ESM resolution
9+
*/
10+
async function importFromProject(require, moduleName) {
11+
// Resolve the module's main entry to find its location
12+
const resolvedPath = require.resolve(moduleName);
13+
14+
// Walk up to find package.json
15+
const { readFileSync } = await import('fs');
16+
let pkgDir = dirname(resolvedPath);
17+
let pkg;
18+
while (pkgDir !== dirname(pkgDir)) {
19+
try {
20+
const pkgJsonPath = join(pkgDir, 'package.json');
21+
pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
22+
if (pkg.name === moduleName || moduleName.startsWith(pkg.name + '/')) {
23+
break;
24+
}
25+
} catch {
26+
// No package.json at this level, keep looking
27+
}
28+
pkgDir = dirname(pkgDir);
29+
}
30+
31+
if (!pkg) {
32+
// Fallback to CJS resolution if we can't find package.json
33+
return import(resolvedPath);
34+
}
35+
36+
// Determine ESM entry: exports.import > exports.default > module > main
37+
let entry = pkg.main || 'index.js';
38+
if (pkg.exports) {
39+
const exp = pkg.exports['.'] || pkg.exports;
40+
if (typeof exp === 'string') {
41+
entry = exp;
42+
} else if (exp.import) {
43+
entry = typeof exp.import === 'string' ? exp.import : exp.import.default;
44+
} else if (exp.default) {
45+
entry = exp.default;
46+
}
47+
} else if (pkg.module) {
48+
entry = pkg.module;
49+
}
50+
51+
const entryPath = join(pkgDir, entry);
52+
return import(pathToFileURL(entryPath).href);
53+
}
754

855
/**
956
* Start the Vite development server
@@ -18,6 +65,15 @@ export async function dev(projectRoot = process.cwd(), args = []) {
1865
process.exit(1);
1966
}
2067

68+
// Import vite and plugins from the project's node_modules (ESM)
69+
const require = createRequire(join(projectRoot, 'package.json'));
70+
const vite = await importFromProject(require, 'vite');
71+
const createServer = vite.createServer;
72+
const reactPlugin = await importFromProject(require, '@vitejs/plugin-react');
73+
const react = reactPlugin.default;
74+
const tailwindPlugin = await importFromProject(require, '@tailwindcss/vite');
75+
const tailwindcss = tailwindPlugin.default;
76+
2177
// Parse port from args or use default
2278
let port = parseInt(process.env.PORT || '6767');
2379
const portArgIndex = args.findIndex(arg => arg === '--port' || arg === '-p');

bin/sunpeak.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function checkPackageJson() {
2828
}
2929

3030
function parseResourcesInput(input) {
31-
const VALID_RESOURCES = ['albums', 'carousel', 'counter', 'map'];
31+
const VALID_RESOURCES = ['albums', 'carousel', 'confirmation', 'counter', 'map'];
3232

3333
// If no input, return all resources
3434
if (!input || input.trim() === '') {
@@ -59,6 +59,7 @@ function updateIndexFiles(targetDir, selectedResources) {
5959
const resourceMap = {
6060
albums: { component: 'album', resourceClass: 'AlbumsResource' },
6161
carousel: { component: 'carousel', resourceClass: 'CarouselResource' },
62+
confirmation: { component: null, resourceClass: 'ConfirmationResource' },
6263
counter: { component: null, resourceClass: 'CounterResource' },
6364
map: { component: 'map', resourceClass: 'MapResource' },
6465
};
@@ -138,7 +139,7 @@ async function init(projectName, resourcesArg) {
138139
console.log(`☀️ 🏔️ Resources: ${resourcesArg}`);
139140
} else {
140141
resourcesInput = await prompt(
141-
'☀️ 🏔️ Resources (UIs) to include [albums, carousel, counter, map]: '
142+
'☀️ 🏔️ Resources (UIs) to include [albums, carousel, confirmation, counter, map]: '
142143
);
143144
}
144145
const selectedResources = parseResourcesInput(resourcesInput);
@@ -160,6 +161,7 @@ async function init(projectName, resourcesArg) {
160161
const resourceComponentMap = {
161162
albums: 'album',
162163
carousel: 'carousel',
164+
confirmation: null, // Confirmation doesn't have a component directory
163165
counter: null, // Counter doesn't have a component directory
164166
map: 'map',
165167
};
@@ -175,7 +177,7 @@ async function init(projectName, resourcesArg) {
175177
}
176178

177179
// Filter resource files based on selection
178-
const VALID_RESOURCES = ['albums', 'carousel', 'counter', 'map'];
180+
const VALID_RESOURCES = ['albums', 'carousel', 'confirmation', 'counter', 'map'];
179181
const excludedResources = VALID_RESOURCES.filter((r) => !selectedResources.includes(r));
180182

181183
for (const resource of excludedResources) {
@@ -402,7 +404,7 @@ Usage:
402404
npx sunpeak new [name] [resources] Create a new project (no install needed)
403405
pnpm dlx sunpeak new Alternative with pnpm
404406
405-
Resources: albums, carousel, counter, map (comma/space separated)
407+
Resources: albums, carousel, confirmation, counter, map (comma/space separated)
406408
Example: npx sunpeak new my-app "albums,carousel"
407409
408410
Inside your project, use npm scripts:

template/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ To add a new UI (MCP Resource), simply create the following files:
102102

103103
- `src/resource/NAME-resource.tsx`
104104
- `src/resource/NAME-resource.json`
105-
- `src/simulations/NAME-TOOLNAME-resource.json`
105+
- `src/simulations/NAME-TOOLNAME-simulation.json`
106106

107107
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`.
108108

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "confirmation",
3+
"title": "Confirmation",
4+
"description": "Visualize and confirm a proposed set of changes or actions",
5+
"mimeType": "text/html+skybridge",
6+
"_meta": {
7+
"openai/widgetDomain": "https://sunpeak.ai",
8+
"openai/widgetCSP": {
9+
"resource_domains": ["https://images.unsplash.com", "https://cdn.openai.com"]
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)