sunpeak 0.20.1 → 0.20.5
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 +59 -89
- package/bin/commands/inspect.mjs +142 -13
- package/bin/commands/new.mjs +33 -9
- package/bin/commands/test-init.mjs +113 -100
- package/bin/commands/test.mjs +7 -2
- package/bin/lib/eval/eval-runner.mjs +7 -1
- package/bin/lib/inspect/inspect-config.mjs +1 -1
- package/bin/lib/live/live-config.d.mts +10 -0
- package/bin/lib/live/live-config.mjs +34 -2
- package/bin/lib/test/base-config.mjs +3 -1
- package/bin/lib/test/test-config.mjs +1 -1
- package/bin/sunpeak.js +16 -15
- package/dist/chatgpt/index.cjs +1 -1
- package/dist/chatgpt/index.js +1 -1
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/host/chatgpt/index.cjs +1 -1
- package/dist/host/chatgpt/index.js +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/inspector/index.cjs +1 -1
- package/dist/inspector/index.js +1 -1
- package/dist/{inspector-BBDa5yCm.js → inspector-60Na_Zc4.js} +2 -2
- package/dist/inspector-60Na_Zc4.js.map +1 -0
- package/dist/{inspector-DAA1Wiyh.cjs → inspector-D0qOqYX2.cjs} +2 -2
- package/dist/{inspector-BBDa5yCm.js.map → inspector-D0qOqYX2.cjs.map} +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/{use-app-DPkj5Jp_.cjs → use-app-B33mckz4.cjs} +7 -3
- package/dist/use-app-B33mckz4.cjs.map +1 -0
- package/dist/{use-app-Cr0auUa1.js → use-app-kv5GQr0G.js} +7 -3
- package/dist/use-app-kv5GQr0G.js.map +1 -0
- package/package.json +3 -3
- package/template/README.md +21 -23
- package/template/dist/albums/albums.html +1 -1
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.html +1 -1
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.html +1 -1
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.html +1 -1
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.vite/deps/_metadata.json +3 -3
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +6 -2
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +6 -2
- package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
- package/template/package.json +2 -1
- package/template/tests/e2e/visual.spec.ts +2 -2
- package/dist/inspector-DAA1Wiyh.cjs.map +0 -1
- package/dist/use-app-Cr0auUa1.js.map +0 -1
- package/dist/use-app-DPkj5Jp_.cjs.map +0 -1
|
@@ -40,6 +40,7 @@ export const defaultDeps = {
|
|
|
40
40
|
mkdirSync,
|
|
41
41
|
execSync,
|
|
42
42
|
cwd: () => process.cwd(),
|
|
43
|
+
isTTY: () => !!process.stdin.isTTY,
|
|
43
44
|
intro: p.intro,
|
|
44
45
|
outro: p.outro,
|
|
45
46
|
confirm: p.confirm,
|
|
@@ -80,6 +81,7 @@ export async function testInit(args = [], deps = defaultDeps) {
|
|
|
80
81
|
: undefined;
|
|
81
82
|
|
|
82
83
|
const projectType = detectProjectType(d);
|
|
84
|
+
const interactive = d.isTTY();
|
|
83
85
|
|
|
84
86
|
if (projectType === 'sunpeak') {
|
|
85
87
|
await initSunpeakProject(d);
|
|
@@ -89,74 +91,78 @@ export async function testInit(args = [], deps = defaultDeps) {
|
|
|
89
91
|
await initExternalProject(cliServer, d);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
// Offer to configure eval providers
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
// Offer to configure eval providers (skip without a TTY — prompts can't work)
|
|
95
|
+
if (interactive) {
|
|
96
|
+
const providers = await d.selectProviders();
|
|
97
|
+
if (!d.isCancel(providers) && providers.length > 0) {
|
|
98
|
+
const pm = d.detectPackageManager();
|
|
99
|
+
const pkgsToInstall = ['ai', ...providers.map((p) => p.pkg)];
|
|
100
|
+
const installCmd = `${pm} add -D ${pkgsToInstall.join(' ')}`;
|
|
101
|
+
try {
|
|
102
|
+
d.execSync(installCmd, { cwd: d.cwd(), stdio: 'inherit' });
|
|
103
|
+
} catch {
|
|
104
|
+
d.log.info(`Provider install failed. Install manually: ${installCmd}`);
|
|
105
|
+
}
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
// Uncomment selected models in eval.config.ts
|
|
108
|
+
const evalDir = d.existsSync(join(d.cwd(), 'tests', 'evals'))
|
|
109
|
+
? join(d.cwd(), 'tests', 'evals')
|
|
110
|
+
: d.existsSync(join(d.cwd(), 'tests', 'sunpeak', 'evals'))
|
|
111
|
+
? join(d.cwd(), 'tests', 'sunpeak', 'evals')
|
|
112
|
+
: null;
|
|
113
|
+
if (evalDir) {
|
|
114
|
+
const configPath = join(evalDir, 'eval.config.ts');
|
|
115
|
+
if (d.existsSync(configPath)) {
|
|
116
|
+
let config = d.readFileSync(configPath, 'utf-8');
|
|
117
|
+
for (const prov of providers) {
|
|
118
|
+
for (const model of prov.models) {
|
|
119
|
+
config = config.replace(
|
|
120
|
+
new RegExp(`^(\\s*)// ('${model.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}',?.*)$`, 'm'),
|
|
121
|
+
'$1$2'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
120
124
|
}
|
|
125
|
+
d.writeFileSync(configPath, config);
|
|
121
126
|
}
|
|
122
|
-
d.writeFileSync(configPath, config);
|
|
123
|
-
}
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
// Prompt for API keys and write .env
|
|
129
|
+
const envLines = [];
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
for (const prov of providers) {
|
|
132
|
+
if (seen.has(prov.envVar)) continue;
|
|
133
|
+
seen.add(prov.envVar);
|
|
134
|
+
const key = await d.password({
|
|
135
|
+
message: `${prov.envVar} (enter to skip)`,
|
|
136
|
+
mask: '*',
|
|
137
|
+
});
|
|
138
|
+
if (!d.isCancel(key) && key) {
|
|
139
|
+
envLines.push(`${prov.envVar}=${key}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (envLines.length > 0 && evalDir) {
|
|
143
|
+
const relEnvPath = evalDir.startsWith(d.cwd()) ? evalDir.slice(d.cwd().length + 1) : evalDir;
|
|
144
|
+
d.writeFileSync(join(evalDir, '.env'), envLines.join('\n') + '\n');
|
|
145
|
+
d.log.info(`API keys saved to ${relEnvPath}/.env (gitignored)`);
|
|
137
146
|
}
|
|
138
|
-
}
|
|
139
|
-
if (envLines.length > 0 && evalDir) {
|
|
140
|
-
const relEnvPath = evalDir.startsWith(d.cwd()) ? evalDir.slice(d.cwd().length + 1) : evalDir;
|
|
141
|
-
d.writeFileSync(join(evalDir, '.env'), envLines.join('\n') + '\n');
|
|
142
|
-
d.log.info(`API keys saved to ${relEnvPath}/.env (gitignored)`);
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
|
-
}
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
// Offer to install the testing skill
|
|
151
|
+
const installSkill = await d.confirm({
|
|
152
|
+
message: 'Install the test-mcp-server skill? (helps your coding agent write tests)',
|
|
153
|
+
initialValue: true,
|
|
154
|
+
});
|
|
155
|
+
if (!d.isCancel(installSkill) && installSkill) {
|
|
156
|
+
const pm = d.detectPackageManager();
|
|
157
|
+
const dlx = pm === 'yarn' ? 'yarn dlx' : pm === 'npm' ? 'npx' : 'pnpm dlx';
|
|
158
|
+
try {
|
|
159
|
+
d.execSync(`${dlx} skills add Sunpeak-AI/sunpeak@test-mcp-server`, {
|
|
160
|
+
cwd: d.cwd(),
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
});
|
|
163
|
+
} catch {
|
|
164
|
+
d.log.info(`Skill install skipped. Install later: ${dlx} skills add Sunpeak-AI/sunpeak@test-mcp-server`);
|
|
165
|
+
}
|
|
160
166
|
}
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -191,6 +197,11 @@ async function getServerConfig(cliServer, d) {
|
|
|
191
197
|
return { type: 'command', value: cliServer };
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
// Without a TTY, interactive prompts can't work — default to "configure later".
|
|
201
|
+
if (!d.isTTY()) {
|
|
202
|
+
return { type: 'later' };
|
|
203
|
+
}
|
|
204
|
+
|
|
194
205
|
const serverType = await d.select({
|
|
195
206
|
message: 'How does your MCP server start?',
|
|
196
207
|
options: [
|
|
@@ -348,7 +359,7 @@ function scaffoldEvals(evalsDir, { server, isSunpeak, d: deps } = {}) {
|
|
|
348
359
|
* 2. Install the AI SDK and provider packages (e.g. pnpm add ai @ai-sdk/openai)
|
|
349
360
|
* 3. Copy .env.example to .env and add your API keys
|
|
350
361
|
* 4. Replace this file with evals for your own tools
|
|
351
|
-
* 5. Run: sunpeak test --eval
|
|
362
|
+
* 5. Run: npx sunpeak test --eval
|
|
352
363
|
*
|
|
353
364
|
* Each case sends a prompt to every configured model and checks
|
|
354
365
|
* that the model calls the expected tool with the expected arguments.
|
|
@@ -394,10 +405,10 @@ function scaffoldVisualTest(filePath, d) {
|
|
|
394
405
|
/**
|
|
395
406
|
* Visual regression tests — compare screenshots against saved baselines.
|
|
396
407
|
*
|
|
397
|
-
* Screenshots only run with: sunpeak test --visual
|
|
398
|
-
* Update baselines with: sunpeak test --visual --update
|
|
408
|
+
* Screenshots only run with: npx sunpeak test --visual
|
|
409
|
+
* Update baselines with: npx sunpeak test --visual --update
|
|
399
410
|
*
|
|
400
|
-
* During normal \`sunpeak test\` runs, screenshot() calls are silently
|
|
411
|
+
* During normal \`npx sunpeak test\` runs, screenshot() calls are silently
|
|
401
412
|
* skipped so these tests still pass without baselines.
|
|
402
413
|
*
|
|
403
414
|
* Uncomment the tests below and replace 'your-tool' with your tool name.
|
|
@@ -437,9 +448,9 @@ function scaffoldVisualTest(filePath, d) {
|
|
|
437
448
|
/**
|
|
438
449
|
* Scaffold live test boilerplate (test against real ChatGPT/Claude).
|
|
439
450
|
* @param {string} liveDir - Directory to create live test files in
|
|
440
|
-
* @param {{ isSunpeak?: boolean, d: object }} options
|
|
451
|
+
* @param {{ isSunpeak?: boolean, server?: object, d: object }} options
|
|
441
452
|
*/
|
|
442
|
-
function scaffoldLiveTests(liveDir, { isSunpeak, d } = {}) {
|
|
453
|
+
function scaffoldLiveTests(liveDir, { isSunpeak, server, d } = {}) {
|
|
443
454
|
if (d.existsSync(join(liveDir, 'playwright.config.ts'))) {
|
|
444
455
|
d.log.info('Live test config already exists. Skipping live test scaffold.');
|
|
445
456
|
return;
|
|
@@ -456,12 +467,27 @@ function scaffoldLiveTests(liveDir, { isSunpeak, d } = {}) {
|
|
|
456
467
|
* Prerequisites:
|
|
457
468
|
* 1. Your MCP server must be accessible via a public URL (e.g., ngrok tunnel)
|
|
458
469
|
* 2. The server must be registered as an MCP action in the host
|
|
459
|
-
* 3. Run: sunpeak test --live
|
|
470
|
+
* 3. Run: npx sunpeak test --live
|
|
460
471
|
*
|
|
461
472
|
* On first run, a browser window opens for you to log in to the host.
|
|
462
|
-
* The session is saved for subsequent runs (typically lasts a few hours)
|
|
473
|
+
* The session is saved for subsequent runs (typically lasts a few hours).
|
|
474
|
+
*/`;
|
|
475
|
+
|
|
476
|
+
// Build the server option for non-sunpeak projects
|
|
477
|
+
let serverOption = '';
|
|
478
|
+
if (!isSunpeak && server?.type === 'url') {
|
|
479
|
+
serverOption = `\n server: { url: '${server.value}' },`;
|
|
480
|
+
} else if (!isSunpeak && server?.type === 'command') {
|
|
481
|
+
const parts = server.value.split(/\s+/);
|
|
482
|
+
const cmd = parts[0];
|
|
483
|
+
const args = parts.slice(1);
|
|
484
|
+
serverOption = args.length > 0
|
|
485
|
+
? `\n server: { command: '${cmd}', args: [${args.map(a => `'${a}'`).join(', ')}] },`
|
|
486
|
+
: `\n server: { command: '${cmd}' },`;
|
|
487
|
+
}
|
|
463
488
|
|
|
464
|
-
const
|
|
489
|
+
const configContent = `${liveConfigPreamble}
|
|
490
|
+
export default defineLiveConfig({${serverOption}
|
|
465
491
|
// hosts: ['chatgpt'], // Which hosts to test against
|
|
466
492
|
// colorScheme: 'light', // Default color scheme
|
|
467
493
|
// viewport: { width: 1280, height: 720 },
|
|
@@ -469,19 +495,6 @@ function scaffoldLiveTests(liveDir, { isSunpeak, d } = {}) {
|
|
|
469
495
|
});
|
|
470
496
|
`;
|
|
471
497
|
|
|
472
|
-
const configContent = isSunpeak
|
|
473
|
-
? `${liveConfigPreamble}
|
|
474
|
-
*/
|
|
475
|
-
${liveConfigExport}`
|
|
476
|
-
: `${liveConfigPreamble}
|
|
477
|
-
*
|
|
478
|
-
* NOTE: defineLiveConfig() starts a local sunpeak dev server as its backend.
|
|
479
|
-
* If your MCP server is not a sunpeak project, you may need to customize the
|
|
480
|
-
* webServer option in the Playwright config below to start your own server,
|
|
481
|
-
* or remove webServer entirely if your server is already running.
|
|
482
|
-
*/
|
|
483
|
-
${liveConfigExport}`;
|
|
484
|
-
|
|
485
498
|
d.writeFileSync(join(liveDir, 'playwright.config.ts'), configContent);
|
|
486
499
|
|
|
487
500
|
// Live test example
|
|
@@ -497,9 +510,9 @@ ${liveConfigExport}`;
|
|
|
497
510
|
* - live.setColorScheme('dark', app) — switch theme while app is visible
|
|
498
511
|
* - live.page — the underlying Playwright page
|
|
499
512
|
*
|
|
500
|
-
* Run with: sunpeak test --live
|
|
513
|
+
* Run with: npx sunpeak test --live
|
|
501
514
|
*
|
|
502
|
-
* These tests are excluded from normal \`sunpeak test\` runs because
|
|
515
|
+
* These tests are excluded from normal \`npx sunpeak test\` runs because
|
|
503
516
|
* they require host accounts and cost API credits.
|
|
504
517
|
*/
|
|
505
518
|
|
|
@@ -542,7 +555,7 @@ function scaffoldUnitTest(filePath, d) {
|
|
|
542
555
|
* Import your tool handler directly and test its input/output
|
|
543
556
|
* without starting the MCP server or inspector.
|
|
544
557
|
*
|
|
545
|
-
* Run with: sunpeak test --unit
|
|
558
|
+
* Run with: npx sunpeak test --unit
|
|
546
559
|
*
|
|
547
560
|
* To set up vitest, add it to your devDependencies:
|
|
548
561
|
* npm install -D vitest
|
|
@@ -663,7 +676,7 @@ test('server exposes tools', async ({ mcp }) => {
|
|
|
663
676
|
scaffoldVisualTest(join(testDir, 'visual.test.ts'), d);
|
|
664
677
|
|
|
665
678
|
// 3. Live tests
|
|
666
|
-
scaffoldLiveTests(join(testDir, 'live'), { isSunpeak: false, d });
|
|
679
|
+
scaffoldLiveTests(join(testDir, 'live'), { isSunpeak: false, server, d });
|
|
667
680
|
|
|
668
681
|
// 4. Eval boilerplate
|
|
669
682
|
scaffoldEvals(join(testDir, 'evals'), { server, d });
|
|
@@ -690,10 +703,10 @@ test('server exposes tools', async ({ mcp }) => {
|
|
|
690
703
|
}
|
|
691
704
|
|
|
692
705
|
d.log.step('Ready! Run tests with:');
|
|
693
|
-
d.log.message(' sunpeak test # E2E tests');
|
|
694
|
-
d.log.message(' sunpeak test --visual # Visual regression (generates baselines on first run)');
|
|
695
|
-
d.log.message(' sunpeak test --live # Live tests against real hosts (requires login)');
|
|
696
|
-
d.log.message(' sunpeak test --eval # Multi-model evals (configure models in evals/eval.config.ts)');
|
|
706
|
+
d.log.message(' npx sunpeak test # E2E tests');
|
|
707
|
+
d.log.message(' npx sunpeak test --visual # Visual regression (generates baselines on first run)');
|
|
708
|
+
d.log.message(' npx sunpeak test --live # Live tests against real hosts (requires login)');
|
|
709
|
+
d.log.message(' npx sunpeak test --eval # Multi-model evals (configure models in evals/eval.config.ts)');
|
|
697
710
|
}
|
|
698
711
|
|
|
699
712
|
async function initJsProject(cliServer, d) {
|
|
@@ -757,7 +770,7 @@ test('server exposes tools', async ({ mcp }) => {
|
|
|
757
770
|
scaffoldVisualTest(join(e2eDir, 'visual.test.ts'), d);
|
|
758
771
|
|
|
759
772
|
// 3. Live tests
|
|
760
|
-
scaffoldLiveTests(join(cwd, 'tests', 'live'), { isSunpeak: false, d });
|
|
773
|
+
scaffoldLiveTests(join(cwd, 'tests', 'live'), { isSunpeak: false, server, d });
|
|
761
774
|
|
|
762
775
|
// 4. Eval boilerplate
|
|
763
776
|
scaffoldEvals(join(cwd, 'tests', 'evals'), { server, d });
|
|
@@ -773,11 +786,11 @@ test('server exposes tools', async ({ mcp }) => {
|
|
|
773
786
|
d.log.message(` ${pkgMgr} add -D sunpeak @playwright/test vitest`);
|
|
774
787
|
d.log.message(` ${pkgMgr} exec playwright install chromium`);
|
|
775
788
|
d.log.message('');
|
|
776
|
-
d.log.message(' sunpeak test # E2E tests');
|
|
777
|
-
d.log.message(' sunpeak test --unit # Unit tests (vitest)');
|
|
778
|
-
d.log.message(' sunpeak test --visual # Visual regression');
|
|
779
|
-
d.log.message(' sunpeak test --live # Live tests against real hosts');
|
|
780
|
-
d.log.message(' sunpeak test --eval # Multi-model evals');
|
|
789
|
+
d.log.message(' npx sunpeak test # E2E tests');
|
|
790
|
+
d.log.message(' npx sunpeak test --unit # Unit tests (vitest)');
|
|
791
|
+
d.log.message(' npx sunpeak test --visual # Visual regression');
|
|
792
|
+
d.log.message(' npx sunpeak test --live # Live tests against real hosts');
|
|
793
|
+
d.log.message(' npx sunpeak test --eval # Multi-model evals');
|
|
781
794
|
}
|
|
782
795
|
|
|
783
796
|
async function initSunpeakProject(d) {
|
|
@@ -824,10 +837,10 @@ export default defineConfig();
|
|
|
824
837
|
scaffoldUnitTest(join(cwd, 'tests', 'unit', 'example.test.ts'), d);
|
|
825
838
|
|
|
826
839
|
d.log.step('Scaffolded test types:');
|
|
827
|
-
d.log.message(' tests/e2e/visual.test.ts — Visual regression (sunpeak test --visual)');
|
|
828
|
-
d.log.message(' tests/live/ — Live host tests (sunpeak test --live)');
|
|
829
|
-
d.log.message(' tests/evals/ — Multi-model evals (sunpeak test --eval)');
|
|
830
|
-
d.log.message(' tests/unit/example.test.ts — Unit tests (sunpeak test --unit)');
|
|
840
|
+
d.log.message(' tests/e2e/visual.test.ts — Visual regression (npx sunpeak test --visual)');
|
|
841
|
+
d.log.message(' tests/live/ — Live host tests (npx sunpeak test --live)');
|
|
842
|
+
d.log.message(' tests/evals/ — Multi-model evals (npx sunpeak test --eval)');
|
|
843
|
+
d.log.message(' tests/unit/example.test.ts — Unit tests (npx sunpeak test --unit)');
|
|
831
844
|
d.log.message('');
|
|
832
845
|
d.log.message(' Migrate existing e2e tests:');
|
|
833
846
|
d.log.message(' Replace: import { test, expect } from "@playwright/test"');
|
package/bin/commands/test.mjs
CHANGED
|
@@ -85,12 +85,15 @@ export async function runTest(args) {
|
|
|
85
85
|
configCandidates: [
|
|
86
86
|
'tests/live/playwright.config.ts',
|
|
87
87
|
'tests/live/playwright.config.js',
|
|
88
|
-
//
|
|
88
|
+
// Non-JS projects: tests/sunpeak/ self-contained directory (run from project root)
|
|
89
89
|
'tests/sunpeak/live/playwright.config.ts',
|
|
90
90
|
'tests/sunpeak/live/playwright.config.js',
|
|
91
|
+
// Non-JS projects: run from within tests/sunpeak/ directly
|
|
92
|
+
'live/playwright.config.ts',
|
|
93
|
+
'live/playwright.config.js',
|
|
91
94
|
],
|
|
92
95
|
configRequired: true,
|
|
93
|
-
configErrorMessage: 'No live test config found at tests/live/playwright.config.ts',
|
|
96
|
+
configErrorMessage: 'No live test config found. Expected at tests/live/playwright.config.ts or live/playwright.config.ts',
|
|
94
97
|
});
|
|
95
98
|
results.push({ suite: 'live', code });
|
|
96
99
|
}
|
|
@@ -537,6 +540,8 @@ function findEvalDir() {
|
|
|
537
540
|
const candidates = [
|
|
538
541
|
'tests/evals',
|
|
539
542
|
'tests/sunpeak/evals',
|
|
543
|
+
// When running from within tests/sunpeak/ directly (non-JS projects)
|
|
544
|
+
'evals',
|
|
540
545
|
];
|
|
541
546
|
|
|
542
547
|
for (const candidate of candidates) {
|
|
@@ -77,7 +77,13 @@ export async function createMcpConnection(serverArg) {
|
|
|
77
77
|
const { StreamableHTTPClientTransport } = await import(
|
|
78
78
|
'@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
79
79
|
);
|
|
80
|
-
|
|
80
|
+
// Follow redirects (e.g. /mcp → /mcp/) before creating the transport.
|
|
81
|
+
let finalUrl = serverArg;
|
|
82
|
+
try {
|
|
83
|
+
const resp = await fetch(serverArg, { method: 'HEAD', redirect: 'follow' });
|
|
84
|
+
if (resp.url && resp.url !== serverArg) finalUrl = resp.url;
|
|
85
|
+
} catch { /* use original URL */ }
|
|
86
|
+
const transport = new StreamableHTTPClientTransport(new URL(finalUrl));
|
|
81
87
|
await client.connect(transport);
|
|
82
88
|
return { client, transport };
|
|
83
89
|
} else {
|
|
@@ -28,6 +28,16 @@ export interface LiveConfigOptions {
|
|
|
28
28
|
|
|
29
29
|
/** Additional Playwright `use` options, merged with defaults. */
|
|
30
30
|
use?: Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
/** External MCP server config. Omit for sunpeak framework projects. */
|
|
33
|
+
server?: {
|
|
34
|
+
/** Server URL (e.g., 'http://localhost:8000/mcp') */
|
|
35
|
+
url?: string;
|
|
36
|
+
/** Server start command */
|
|
37
|
+
command?: string;
|
|
38
|
+
/** Command arguments */
|
|
39
|
+
args?: string[];
|
|
40
|
+
};
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
export interface HostConfigOptions {
|
|
@@ -11,6 +11,7 @@ import { join, dirname } from 'path';
|
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { ANTI_BOT_ARGS, CHROME_USER_AGENT } from './utils.mjs';
|
|
13
13
|
import { getPortSync } from '../get-port.mjs';
|
|
14
|
+
import { resolveSunpeakBin } from '../resolve-bin.mjs';
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const GLOBAL_SETUP_PATH = join(__dirname, 'global-setup.mjs');
|
|
@@ -34,6 +35,10 @@ const GLOBAL_SETUP_PATH = join(__dirname, 'global-setup.mjs');
|
|
|
34
35
|
* @param {string[]} [options.permissions] - Browser permissions to grant (e.g., ['geolocation'])
|
|
35
36
|
* @param {boolean} [options.devOverlay=true] - Show the dev overlay (resource timestamp + tool timing) in resources
|
|
36
37
|
* @param {Object} [options.use] - Additional Playwright `use` options (merged with defaults)
|
|
38
|
+
* @param {Object} [options.server] - External MCP server config (omit for sunpeak projects)
|
|
39
|
+
* @param {string} [options.server.url] - Server URL (e.g., 'http://localhost:8000/mcp')
|
|
40
|
+
* @param {string} [options.server.command] - Server start command
|
|
41
|
+
* @param {string[]} [options.server.args] - Command arguments
|
|
37
42
|
*/
|
|
38
43
|
export function createLiveConfig(hostOptions, options = {}) {
|
|
39
44
|
const { hostId, authFileName } = hostOptions;
|
|
@@ -49,6 +54,7 @@ export function createLiveConfig(hostOptions, options = {}) {
|
|
|
49
54
|
geolocation,
|
|
50
55
|
permissions,
|
|
51
56
|
use: userUse,
|
|
57
|
+
server,
|
|
52
58
|
} = options;
|
|
53
59
|
|
|
54
60
|
const resolvedAuthDir = authDir || join(testDir, '.auth');
|
|
@@ -91,10 +97,36 @@ export function createLiveConfig(hostOptions, options = {}) {
|
|
|
91
97
|
},
|
|
92
98
|
],
|
|
93
99
|
webServer: {
|
|
94
|
-
command:
|
|
95
|
-
url: `http://
|
|
100
|
+
command: buildLiveWebServerCommand({ server, vitePort, devOverlay }),
|
|
101
|
+
url: `http://127.0.0.1:${vitePort}/health`,
|
|
96
102
|
reuseExistingServer: !process.env.CI,
|
|
97
103
|
timeout: 60_000,
|
|
98
104
|
},
|
|
99
105
|
};
|
|
100
106
|
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build the webServer command for live tests.
|
|
110
|
+
* Uses `sunpeak inspect` for external servers, `pnpm dev` for sunpeak projects.
|
|
111
|
+
*/
|
|
112
|
+
function buildLiveWebServerCommand({ server, vitePort, devOverlay }) {
|
|
113
|
+
const sandboxPort = getPortSync(24680);
|
|
114
|
+
const envPrefix = `SUNPEAK_LIVE_TEST=1 SUNPEAK_SANDBOX_PORT=${sandboxPort}${devOverlay ? '' : ' SUNPEAK_DEV_OVERLAY=false'}`;
|
|
115
|
+
|
|
116
|
+
if (server) {
|
|
117
|
+
// External MCP server — launch sunpeak inspect
|
|
118
|
+
const bin = resolveSunpeakBin();
|
|
119
|
+
if (server.url) {
|
|
120
|
+
return `${envPrefix} ${bin} inspect --server ${server.url} --port ${vitePort}`;
|
|
121
|
+
}
|
|
122
|
+
if (server.command) {
|
|
123
|
+
const cmd = server.args
|
|
124
|
+
? `${server.command} ${server.args.join(' ')}`
|
|
125
|
+
: server.command;
|
|
126
|
+
return `${envPrefix} ${bin} inspect --server "${cmd}" --port ${vitePort}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// sunpeak framework project — use pnpm dev
|
|
131
|
+
return `${envPrefix} pnpm dev -- --prod-resources --port ${vitePort}`;
|
|
132
|
+
}
|
|
@@ -44,7 +44,9 @@ export function createBaseConfig({ hosts, testDir, webServer, port, use, globalS
|
|
|
44
44
|
? { expect: { toHaveScreenshot: toHaveScreenshotDefaults } }
|
|
45
45
|
: {}),
|
|
46
46
|
use: {
|
|
47
|
-
|
|
47
|
+
// Use 127.0.0.1 instead of localhost to avoid IPv4/IPv6 resolution
|
|
48
|
+
// ambiguity that causes ECONNREFUSED flakes on macOS.
|
|
49
|
+
baseURL: `http://127.0.0.1:${port}`,
|
|
48
50
|
trace: 'on-first-retry',
|
|
49
51
|
...use,
|
|
50
52
|
},
|
package/bin/sunpeak.js
CHANGED
|
@@ -102,22 +102,11 @@ function getVersion() {
|
|
|
102
102
|
{
|
|
103
103
|
const resources = discoverResources();
|
|
104
104
|
console.log(`
|
|
105
|
-
☀️ 🏔️ sunpeak -
|
|
105
|
+
☀️ 🏔️ sunpeak - App framework, testing framework, and inspector for MCP Apps
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
pnpm add -g sunpeak
|
|
107
|
+
Usage: npx sunpeak <command>
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
sunpeak inspect Inspect any MCP server in the inspector
|
|
112
|
-
--server, -s <url|cmd> MCP server URL or stdio command (required)
|
|
113
|
-
--simulations <dir> Simulation JSON directory
|
|
114
|
-
sunpeak test Run e2e tests against the inspector
|
|
115
|
-
init Scaffold test infrastructure into a project
|
|
116
|
-
--unit Run unit tests (vitest)
|
|
117
|
-
--live Run live tests against real hosts
|
|
118
|
-
--eval Run evals against LLM models
|
|
119
|
-
|
|
120
|
-
App framework (for sunpeak projects):
|
|
109
|
+
App framework:
|
|
121
110
|
sunpeak new [name] [resources] Create a new project
|
|
122
111
|
sunpeak dev Start dev server + inspector + MCP endpoint
|
|
123
112
|
--no-begging Suppress GitHub star message
|
|
@@ -125,8 +114,20 @@ App framework (for sunpeak projects):
|
|
|
125
114
|
sunpeak start Start production MCP server
|
|
126
115
|
--port, -p Server port (default: 8000, or PORT env)
|
|
127
116
|
sunpeak upgrade Upgrade sunpeak to latest version
|
|
128
|
-
sunpeak --version Show version number
|
|
129
117
|
|
|
118
|
+
Testing (works with any MCP server):
|
|
119
|
+
sunpeak test Run e2e tests against the inspector
|
|
120
|
+
init Scaffold test infrastructure into a project
|
|
121
|
+
--unit Run unit tests (vitest)
|
|
122
|
+
--live Run live tests against real hosts
|
|
123
|
+
--eval Run evals against LLM models
|
|
124
|
+
|
|
125
|
+
Inspector (works with any MCP server):
|
|
126
|
+
sunpeak inspect Inspect any MCP server in the inspector
|
|
127
|
+
--server, -s <url|cmd> MCP server URL or stdio command (required)
|
|
128
|
+
--simulations <dir> Simulation JSON directory
|
|
129
|
+
|
|
130
|
+
sunpeak --version Show version number
|
|
130
131
|
Resources: ${resources.join(', ')} (comma/space separated)
|
|
131
132
|
Example: sunpeak new sunpeak-app "${resources.slice(0, 2).join(',')}"
|
|
132
133
|
`);
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../chunk-9hOWP6kD.cjs");
|
|
3
|
-
const require_inspector = require("../inspector-
|
|
3
|
+
const require_inspector = require("../inspector-D0qOqYX2.cjs");
|
|
4
4
|
const require_inspector_url = require("../inspector-url-C3LTKgXt.cjs");
|
|
5
5
|
const require_discovery = require("../discovery-Clu4uHp1.cjs");
|
|
6
6
|
//#region src/chatgpt/index.ts
|
package/dist/chatgpt/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __exportAll } from "../chunk-D6g4UhsZ.js";
|
|
2
|
-
import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-
|
|
2
|
+
import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Inspector, v as SCREEN_WIDTHS } from "../inspector-60Na_Zc4.js";
|
|
3
3
|
import { t as createInspectorUrl } from "../inspector-url-CyQcuBI9.js";
|
|
4
4
|
import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-Cgoegt62.js";
|
|
5
5
|
//#region src/chatgpt/index.ts
|
package/dist/claude/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../chunk-9hOWP6kD.cjs");
|
|
3
|
-
const require_inspector = require("../inspector-
|
|
3
|
+
const require_inspector = require("../inspector-D0qOqYX2.cjs");
|
|
4
4
|
exports.Inspector = require_inspector.Inspector;
|
package/dist/claude/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as Inspector } from "../inspector-
|
|
1
|
+
import { t as Inspector } from "../inspector-60Na_Zc4.js";
|
|
2
2
|
export { Inspector };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-9hOWP6kD.cjs");
|
|
3
|
-
const require_use_app = require("../../use-app-
|
|
3
|
+
const require_use_app = require("../../use-app-B33mckz4.cjs");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
//#region src/host/chatgpt/openai-types.ts
|
|
6
6
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-9hOWP6kD.cjs");
|
|
3
3
|
const require_protocol = require("./protocol-C8pFDmcy.cjs");
|
|
4
|
-
const require_use_app = require("./use-app-
|
|
5
|
-
const require_inspector = require("./inspector-
|
|
4
|
+
const require_use_app = require("./use-app-B33mckz4.cjs");
|
|
5
|
+
const require_inspector = require("./inspector-D0qOqYX2.cjs");
|
|
6
6
|
const require_host_index = require("./host/index.cjs");
|
|
7
7
|
const require_inspector_index = require("./inspector/index.cjs");
|
|
8
8
|
const require_chatgpt_index = require("./chatgpt/index.cjs");
|
|
9
9
|
let react = require("react");
|
|
10
10
|
react = require_chunk.__toESM(react, 1);
|
|
11
11
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
12
|
-
//#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.
|
|
12
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.6.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__react-_1fd7d0151a0915598274278e5ddb69e9/node_modules/@modelcontextprotocol/ext-apps/dist/src/react/index.js
|
|
13
13
|
var m = require_protocol.union([require_protocol.literal("light"), require_protocol.literal("dark")]).describe("Color theme preference for the host environment."), N$1 = require_protocol.union([
|
|
14
14
|
require_protocol.literal("inline"),
|
|
15
15
|
require_protocol.literal("fullscreen"),
|