sunpeak 0.15.4 → 0.16.1
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 +51 -48
- package/bin/commands/build.mjs +13 -4
- package/bin/commands/dev.mjs +64 -19
- package/bin/commands/new.mjs +13 -3
- package/bin/lib/extract-resource.mjs +1 -1
- package/bin/lib/extract-tool.mjs +78 -0
- package/bin/lib/patterns.mjs +2 -26
- package/dist/chatgpt/index.cjs +3 -6
- package/dist/chatgpt/index.cjs.map +1 -1
- package/dist/chatgpt/index.d.ts +1 -1
- package/dist/chatgpt/index.js +6 -9
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/discovery-CH80W5l9.js +217 -0
- package/dist/discovery-CH80W5l9.js.map +1 -0
- package/dist/discovery-DmB8_4QL.cjs +216 -0
- package/dist/discovery-DmB8_4QL.cjs.map +1 -0
- package/dist/{index-CutQgPzR.js → index-BjnAsaqp.js} +3 -6
- package/dist/index-BjnAsaqp.js.map +1 -0
- package/dist/{index-Cngntkp2.cjs → index-BvQ_ZuOO.cjs} +3 -6
- package/dist/{index-Cngntkp2.cjs.map → index-BvQ_ZuOO.cjs.map} +1 -1
- package/dist/{index-B0dxRJvS.cjs → index-C9CVbGFt.cjs} +3 -6
- package/dist/index-C9CVbGFt.cjs.map +1 -0
- package/dist/{index-Ce_5ZIdJ.js → index-CTGEqlgk.js} +3 -6
- package/dist/{index-Ce_5ZIdJ.js.map → index-CTGEqlgk.js.map} +1 -1
- package/dist/index.cjs +48 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3404 -3361
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery-cli.cjs +58 -5
- package/dist/lib/discovery-cli.cjs.map +1 -1
- package/dist/lib/discovery-cli.d.ts +3 -2
- package/dist/lib/discovery-cli.js +61 -8
- package/dist/lib/discovery-cli.js.map +1 -1
- package/dist/lib/discovery.d.ts +42 -43
- package/dist/lib/extract-tool.d.ts +12 -0
- package/dist/mcp/favicon.d.ts +1 -1
- package/dist/mcp/index.cjs +3 -2
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +3 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/types.d.ts +24 -1
- package/dist/platform/chatgpt/index.cjs +1 -1
- package/dist/platform/chatgpt/index.js +1 -1
- package/dist/simulator/index.cjs +2 -5
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.ts +1 -1
- package/dist/simulator/index.js +5 -8
- package/dist/simulator/simulator-url.d.ts +9 -9
- package/dist/{simulator-DcfQBRXE.cjs → simulator-B56j5P8W.cjs} +8 -2
- package/dist/{simulator-DcfQBRXE.cjs.map → simulator-B56j5P8W.cjs.map} +1 -1
- package/dist/{simulator-CxrtnguM.js → simulator-C0H_k092.js} +8 -2
- package/dist/{simulator-CxrtnguM.js.map → simulator-C0H_k092.js.map} +1 -1
- package/dist/simulator-url-CuLqtnSS.js.map +1 -1
- package/dist/simulator-url-rgg_KYOg.cjs.map +1 -1
- package/dist/types/resource-config.d.ts +7 -5
- package/dist/{use-app-D_TeaMFG.js → use-app-BThbgFFT.js} +51 -22
- package/dist/{use-app-D_TeaMFG.js.map → use-app-BThbgFFT.js.map} +1 -1
- package/dist/{use-app-BnoSPiUT.cjs → use-app-BuufpXTQ.cjs} +49 -20
- package/dist/{use-app-BnoSPiUT.cjs.map → use-app-BuufpXTQ.cjs.map} +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +8 -4
- package/template/.sunpeak/resource-loader.tsx +2 -1
- package/template/README.md +14 -10
- package/template/package.json +2 -1
- package/template/src/resources/albums/{albums-resource.test.tsx → albums.test.tsx} +1 -1
- package/template/src/resources/albums/{albums-resource.tsx → albums.tsx} +0 -1
- package/template/src/resources/carousel/{carousel-resource.test.tsx → carousel.test.tsx} +1 -1
- package/template/src/resources/carousel/{carousel-resource.tsx → carousel.tsx} +0 -1
- package/template/src/resources/index.ts +4 -4
- package/template/src/resources/map/{map-resource.test.tsx → map.test.tsx} +1 -1
- package/template/src/resources/map/{map-resource.tsx → map.tsx} +0 -1
- package/template/src/resources/review/{review-resource.test.tsx → review.test.tsx} +1 -1
- package/template/src/resources/review/{review-resource.tsx → review.tsx} +1 -2
- package/template/src/server.ts +15 -0
- package/template/src/tools/review-diff.ts +24 -0
- package/template/src/tools/review-post.ts +26 -0
- package/template/src/tools/review-purchase.ts +31 -0
- package/template/src/tools/show-albums.ts +22 -0
- package/template/src/tools/show-carousel.ts +25 -0
- package/template/src/tools/show-map.ts +29 -0
- package/template/tests/e2e/albums.spec.ts +6 -6
- package/template/tests/e2e/carousel.spec.ts +6 -6
- package/template/tests/e2e/map.spec.ts +11 -11
- package/template/tests/simulations/{review/review-diff-simulation.json → review-diff.json} +1 -31
- package/template/tests/simulations/{review/review-post-simulation.json → review-post.json} +1 -37
- package/template/tests/simulations/{review/review-purchase-simulation.json → review-purchase.json} +1 -38
- package/template/tests/simulations/{albums/albums-show-simulation.json → show-albums.json} +1 -24
- package/template/tests/simulations/{carousel/carousel-show-simulation.json → show-carousel.json} +1 -24
- package/template/tests/simulations/{map/map-show-simulation.json → show-map.json} +1 -35
- package/dist/discovery-CRR3SlyI.cjs +0 -156
- package/dist/discovery-CRR3SlyI.cjs.map +0 -1
- package/dist/discovery-DzV3HLXs.js +0 -157
- package/dist/discovery-DzV3HLXs.js.map +0 -1
- package/dist/index-B0dxRJvS.cjs.map +0 -1
- package/dist/index-CutQgPzR.js.map +0 -1
package/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* This file bootstraps the multi-host simulator for development.
|
|
5
5
|
*
|
|
6
6
|
* Auto-discovers simulations and resources by file naming convention:
|
|
7
|
-
* - tests/simulations
|
|
8
|
-
* - src/resources/{resource}/{resource}
|
|
7
|
+
* - tests/simulations/*.json
|
|
8
|
+
* - src/resources/{resource}/{resource}.tsx (component + resource metadata)
|
|
9
9
|
* - src/resources/{resource}/{Resource}Resource component (PascalCase)
|
|
10
10
|
*/
|
|
11
11
|
import { StrictMode } from 'react';
|
|
@@ -18,9 +18,13 @@ const { Simulator, buildDevSimulations } = simulator;
|
|
|
18
18
|
|
|
19
19
|
// Build simulations from discovered files
|
|
20
20
|
const simulations = buildDevSimulations({
|
|
21
|
-
simulationModules: import.meta.glob('../tests/simulations
|
|
22
|
-
resourceModules: import.meta.glob('../src/resources/*/*-resource.tsx', { eager: true }),
|
|
21
|
+
simulationModules: import.meta.glob('../tests/simulations/*.json', { eager: true }),
|
|
23
22
|
resourceComponents: resourceComponents as Record<string, React.ComponentType>,
|
|
23
|
+
toolModules: import.meta.glob('../src/tools/*.ts', { eager: true }),
|
|
24
|
+
resourceModules: import.meta.glob(
|
|
25
|
+
['../src/resources/*/*.tsx', '!../src/resources/*/*.test.tsx'],
|
|
26
|
+
{ eager: true }
|
|
27
|
+
),
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
// Read app config from environment or use defaults
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { StrictMode } from 'react';
|
|
18
18
|
import { createRoot } from 'react-dom/client';
|
|
19
19
|
import { AppProvider } from 'sunpeak';
|
|
20
|
+
import pkg from '../package.json';
|
|
20
21
|
import '../src/styles/globals.css';
|
|
21
22
|
import resourceComponents from '../src/resources';
|
|
22
23
|
|
|
@@ -39,7 +40,7 @@ if (!componentName) {
|
|
|
39
40
|
|
|
40
41
|
root.render(
|
|
41
42
|
<StrictMode>
|
|
42
|
-
<AppProvider appInfo={{ name: componentName, version: '1.0.0' }}>
|
|
43
|
+
<AppProvider appInfo={{ name: pkg.name || componentName, version: pkg.version || '1.0.0' }}>
|
|
43
44
|
<Component />
|
|
44
45
|
</AppProvider>
|
|
45
46
|
</StrictMode>
|
package/template/README.md
CHANGED
|
@@ -32,11 +32,15 @@ Using a Review page as an example, sunpeak projects look like:
|
|
|
32
32
|
my-app/
|
|
33
33
|
├── src/resources/
|
|
34
34
|
│ └── review/
|
|
35
|
-
│ └── review
|
|
35
|
+
│ └── review.tsx # Review UI component + resource metadata.
|
|
36
|
+
├── src/tools/
|
|
37
|
+
│ ├── review-diff.ts # Tool: metadata, schema, handler.
|
|
38
|
+
│ ├── review-post.ts
|
|
39
|
+
│ └── review-purchase.ts
|
|
36
40
|
├── tests/simulations/
|
|
37
|
-
│
|
|
38
|
-
│
|
|
39
|
-
│
|
|
41
|
+
│ ├── review-diff.json # Mock state for testing.
|
|
42
|
+
│ ├── review-post.json
|
|
43
|
+
│ └── review-purchase.json
|
|
40
44
|
└── package.json
|
|
41
45
|
```
|
|
42
46
|
|
|
@@ -90,14 +94,14 @@ To add a new UI (MCP Resource), create a new directory under `src/resources/` wi
|
|
|
90
94
|
|
|
91
95
|
```
|
|
92
96
|
src/resources/NAME/
|
|
93
|
-
├── NAME
|
|
94
|
-
├── NAME
|
|
95
|
-
└── components/
|
|
97
|
+
├── NAME.tsx # React component + resource metadata (required)
|
|
98
|
+
├── NAME.test.tsx # Unit tests (optional)
|
|
99
|
+
└── components/ # UI components (optional)
|
|
96
100
|
```
|
|
97
101
|
|
|
98
|
-
Only the resource file (`.tsx`) is required to generate a production build and ship a UI. It must export a `resource` object (`ResourceConfig`) describing the resource metadata, and a React component that renders the UI.
|
|
102
|
+
Only the resource file (`.tsx`) is required to generate a production build and ship a UI. It must export a `resource` object (`ResourceConfig`) describing the resource metadata, and a React component that renders the UI. The resource name is auto-derived from the directory name.
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
Then create a tool file in `src/tools/` and simulation file(s) in `tests/simulations/` to preview your resource in `sunpeak dev`.
|
|
101
105
|
|
|
102
106
|
## Coding Agent Skill
|
|
103
107
|
|
|
@@ -110,6 +114,6 @@ npx skills add Sunpeak-AI/sunpeak@create-sunpeak-app
|
|
|
110
114
|
## Resources
|
|
111
115
|
|
|
112
116
|
- [sunpeak](https://github.com/Sunpeak-AI/sunpeak)
|
|
117
|
+
- [MCP Apps Documentation](https://sunpeak.ai/docs/mcp-apps/introduction)
|
|
113
118
|
- [MCP Apps SDK](https://github.com/modelcontextprotocol/ext-apps)
|
|
114
119
|
- [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
|
|
115
|
-
- [ChatGPT Apps SDK UI Documentation](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
|
package/template/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { AlbumsResource } from './albums
|
|
3
|
+
import { AlbumsResource } from './albums';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak — SafeArea renders as a plain div
|
|
6
6
|
vi.mock('sunpeak', () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { CarouselResource } from './carousel
|
|
3
|
+
import { CarouselResource } from './carousel';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak hooks
|
|
6
6
|
interface Place {
|
|
@@ -3,7 +3,6 @@ import type { ResourceConfig } from 'sunpeak';
|
|
|
3
3
|
import { Carousel, Card } from './components';
|
|
4
4
|
|
|
5
5
|
export const resource: ResourceConfig = {
|
|
6
|
-
name: 'carousel',
|
|
7
6
|
title: 'Carousel',
|
|
8
7
|
description: 'Show popular places to visit widget',
|
|
9
8
|
mimeType: 'text/html;profile=mcp-app',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-discovers and re-exports all resource components.
|
|
3
3
|
*
|
|
4
|
-
* Discovers all {resource}/{resource}
|
|
5
|
-
* with a PascalCase name (e.g., albums/albums
|
|
4
|
+
* Discovers all {resource}/{resource}.tsx files and exports their component
|
|
5
|
+
* with a PascalCase name (e.g., albums/albums.tsx -> AlbumsResource).
|
|
6
6
|
*
|
|
7
7
|
* Supports both export styles:
|
|
8
8
|
* - Default export: export default MyComponent
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createResourceExports } from 'sunpeak';
|
|
12
12
|
|
|
13
|
-
// Auto-discover all resource component files in subdirectories
|
|
14
|
-
const resourceModules = import.meta.glob('
|
|
13
|
+
// Auto-discover all resource component files in subdirectories (exclude test files)
|
|
14
|
+
const resourceModules = import.meta.glob(['./*/*.tsx', '!./*/*.test.tsx'], { eager: true });
|
|
15
15
|
|
|
16
16
|
// Build exports object from discovered files using library helper
|
|
17
17
|
export default createResourceExports(resourceModules);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { MapResource } from './map
|
|
3
|
+
import { MapResource } from './map';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak — SafeArea renders as a plain div
|
|
6
6
|
vi.mock('sunpeak', () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { ReviewResource } from './review
|
|
3
|
+
import { ReviewResource } from './review';
|
|
4
4
|
|
|
5
5
|
// Mock sunpeak hooks
|
|
6
6
|
const mockSetState = vi.fn();
|
|
@@ -11,7 +11,6 @@ import { Button } from '../../components/button';
|
|
|
11
11
|
import { ExpandLg } from '../../components/icon';
|
|
12
12
|
|
|
13
13
|
export const resource: ResourceConfig = {
|
|
14
|
-
name: 'review',
|
|
15
14
|
title: 'Review',
|
|
16
15
|
description: 'Visualize and review a proposed set of changes or actions',
|
|
17
16
|
mimeType: 'text/html;profile=mcp-app',
|
|
@@ -475,7 +474,7 @@ export function ReviewResource() {
|
|
|
475
474
|
</div>
|
|
476
475
|
|
|
477
476
|
{/* Footer with Actions */}
|
|
478
|
-
<div className="px-4 py-3 border-t border-[var(--color-border-tertiary)]
|
|
477
|
+
<div className="px-4 py-3 border-t border-[var(--color-border-tertiary)]">
|
|
479
478
|
{decision === null ? (
|
|
480
479
|
<div className="flex gap-3">
|
|
481
480
|
<Button
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { AuthInfo } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Optional server entry point.
|
|
6
|
+
*
|
|
7
|
+
* The `auth` function extracts authentication info from incoming HTTP requests.
|
|
8
|
+
* The returned `AuthInfo` is available as `extra.authInfo` in tool handlers.
|
|
9
|
+
*/
|
|
10
|
+
export function auth(req: IncomingMessage): AuthInfo {
|
|
11
|
+
const token = req.headers.authorization?.replace('Bearer ', '') ?? '';
|
|
12
|
+
return { token, clientId: 'my-app', scopes: [] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const server = { name: 'Sunpeak', version: '1.0.0' };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Diff Review',
|
|
7
|
+
description: 'Show a review dialog for a proposed code diff',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
changesetId: z.string().describe('Unique identifier for the changeset'),
|
|
16
|
+
title: z.string().describe('Title describing the changes'),
|
|
17
|
+
description: z.string().describe('Detailed description of what the changes accomplish'),
|
|
18
|
+
files: z.array(z.string()).describe('List of file paths affected by this change'),
|
|
19
|
+
runMigrations: z.boolean().describe('Whether to run database migrations as part of the change'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
23
|
+
return { structuredContent: { title: 'Review', sections: [] } };
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Review Post',
|
|
7
|
+
description: 'Review a social media post before publishing',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
content: z.string().describe('The text content of the post'),
|
|
16
|
+
platforms: z
|
|
17
|
+
.array(z.enum(['x', 'linkedin', 'facebook', 'instagram']))
|
|
18
|
+
.describe('Social media platforms to post to'),
|
|
19
|
+
schedule: z.enum(['now', 'scheduled']).describe('When to publish the post'),
|
|
20
|
+
scheduledTime: z.string().describe('ISO 8601 timestamp for scheduled posts'),
|
|
21
|
+
visibility: z.enum(['public', 'connections', 'private']).describe('Post visibility setting'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
25
|
+
return { structuredContent: { title: 'Review Your Post', sections: [] } };
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'review',
|
|
6
|
+
title: 'Review Purchase',
|
|
7
|
+
description: 'Review a purchase before completing the transaction',
|
|
8
|
+
annotations: { readOnlyHint: false },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
cartId: z.string().describe('Shopping cart identifier'),
|
|
16
|
+
items: z
|
|
17
|
+
.array(
|
|
18
|
+
z.object({
|
|
19
|
+
productId: z.string(),
|
|
20
|
+
quantity: z.number(),
|
|
21
|
+
})
|
|
22
|
+
)
|
|
23
|
+
.describe('List of items to purchase'),
|
|
24
|
+
shippingAddressId: z.string().describe('ID of the saved shipping address'),
|
|
25
|
+
shippingMethod: z.enum(['standard', 'express', 'overnight']).describe('Shipping speed'),
|
|
26
|
+
paymentMethodId: z.string().describe('ID of the saved payment method'),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
30
|
+
return { structuredContent: { title: 'Confirm Your Order', sections: [] } };
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'albums',
|
|
6
|
+
title: 'Show Albums',
|
|
7
|
+
description: 'Show photo albums',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
category: z.string().describe('Filter albums by category (e.g., travel, food, family)'),
|
|
16
|
+
search: z.string().describe('Search term to filter albums by title or description'),
|
|
17
|
+
limit: z.number().describe('Maximum number of albums to return'),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
21
|
+
return { structuredContent: { albums: [] } };
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'carousel',
|
|
6
|
+
title: 'Show Carousel',
|
|
7
|
+
description: 'Show popular places to visit',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
city: z.string().describe('City name to search for places'),
|
|
16
|
+
state: z.string().describe('State or region'),
|
|
17
|
+
categories: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.describe('Filter by categories (e.g., parks, restaurants, landmarks)'),
|
|
20
|
+
limit: z.number().describe('Maximum number of places to return'),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
24
|
+
return { structuredContent: { places: [] } };
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
|
|
3
|
+
|
|
4
|
+
export const tool: AppToolConfig = {
|
|
5
|
+
resource: 'map',
|
|
6
|
+
title: 'Show Map',
|
|
7
|
+
description: 'Show the map',
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
_meta: {
|
|
10
|
+
ui: { visibility: ['model', 'app'] },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const schema = {
|
|
15
|
+
query: z.string().describe('Search query for places (e.g., pizza, coffee, restaurants)'),
|
|
16
|
+
location: z
|
|
17
|
+
.object({
|
|
18
|
+
lat: z.number(),
|
|
19
|
+
lng: z.number(),
|
|
20
|
+
})
|
|
21
|
+
.describe('Center location for the search'),
|
|
22
|
+
radius: z.number().describe('Search radius in miles'),
|
|
23
|
+
minRating: z.number().describe('Minimum rating filter (1-5)'),
|
|
24
|
+
priceRange: z.array(z.enum(['$', '$$', '$$$', '$$$$'])).describe('Price range filter'),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default async function (_args: Record<string, unknown>, _extra: ToolHandlerExtra) {
|
|
28
|
+
return { structuredContent: { places: [] } };
|
|
29
|
+
}
|
|
@@ -7,7 +7,7 @@ for (const host of hosts) {
|
|
|
7
7
|
test.describe(`Albums Resource [${host}]`, () => {
|
|
8
8
|
test.describe('Light Mode', () => {
|
|
9
9
|
test('should render album cards with correct styles', async ({ page }) => {
|
|
10
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
10
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'light', host }));
|
|
11
11
|
|
|
12
12
|
const iframe = page.frameLocator('iframe');
|
|
13
13
|
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
@@ -27,7 +27,7 @@ for (const host of hosts) {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test('should have album image with correct aspect ratio', async ({ page }) => {
|
|
30
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
30
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'light', host }));
|
|
31
31
|
|
|
32
32
|
const iframe = page.frameLocator('iframe');
|
|
33
33
|
const albumImage = iframe.locator('button:has-text("Summer Slice") img').first();
|
|
@@ -54,7 +54,7 @@ for (const host of hosts) {
|
|
|
54
54
|
|
|
55
55
|
test.describe('Dark Mode', () => {
|
|
56
56
|
test('should render album cards with correct styles', async ({ page }) => {
|
|
57
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
57
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
58
58
|
|
|
59
59
|
const iframe = page.frameLocator('iframe');
|
|
60
60
|
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
@@ -73,7 +73,7 @@ for (const host of hosts) {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('should have text with appropriate contrast', async ({ page }) => {
|
|
76
|
-
await page.goto(createSimulatorUrl({ simulation: 'albums
|
|
76
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
77
77
|
|
|
78
78
|
const iframe = page.frameLocator('iframe');
|
|
79
79
|
const albumTitle = iframe.locator('button:has-text("Summer Slice") div').first();
|
|
@@ -96,7 +96,7 @@ for (const host of hosts) {
|
|
|
96
96
|
test('should render correctly in fullscreen displayMode', async ({ page }) => {
|
|
97
97
|
await page.goto(
|
|
98
98
|
createSimulatorUrl({
|
|
99
|
-
simulation: 'albums
|
|
99
|
+
simulation: 'show-albums',
|
|
100
100
|
theme: 'light',
|
|
101
101
|
displayMode: 'fullscreen',
|
|
102
102
|
host,
|
|
@@ -114,7 +114,7 @@ for (const host of hosts) {
|
|
|
114
114
|
test('should maintain album card styles in fullscreen', async ({ page }) => {
|
|
115
115
|
await page.goto(
|
|
116
116
|
createSimulatorUrl({
|
|
117
|
-
simulation: 'albums
|
|
117
|
+
simulation: 'show-albums',
|
|
118
118
|
theme: 'dark',
|
|
119
119
|
displayMode: 'fullscreen',
|
|
120
120
|
host,
|
|
@@ -7,7 +7,7 @@ for (const host of hosts) {
|
|
|
7
7
|
test.describe(`Carousel Resource [${host}]`, () => {
|
|
8
8
|
test.describe('Light Mode', () => {
|
|
9
9
|
test('should render carousel cards with correct styles', async ({ page }) => {
|
|
10
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
10
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
11
11
|
|
|
12
12
|
const iframe = page.frameLocator('iframe');
|
|
13
13
|
const card = iframe.locator('.rounded-2xl').first();
|
|
@@ -26,7 +26,7 @@ for (const host of hosts) {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('should have card with border styling', async ({ page }) => {
|
|
29
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
29
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
30
30
|
|
|
31
31
|
const iframe = page.frameLocator('iframe');
|
|
32
32
|
const card = iframe.locator('.rounded-2xl.border').first();
|
|
@@ -45,7 +45,7 @@ for (const host of hosts) {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('should have interactive buttons', async ({ page }) => {
|
|
48
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
48
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'light', host }));
|
|
49
49
|
|
|
50
50
|
const iframe = page.frameLocator('iframe');
|
|
51
51
|
const visitButton = iframe.locator('button:has-text("Visit")').first();
|
|
@@ -64,7 +64,7 @@ for (const host of hosts) {
|
|
|
64
64
|
|
|
65
65
|
test.describe('Dark Mode', () => {
|
|
66
66
|
test('should render carousel cards with correct styles', async ({ page }) => {
|
|
67
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
67
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
68
68
|
|
|
69
69
|
const iframe = page.frameLocator('iframe');
|
|
70
70
|
const card = iframe.locator('.rounded-2xl').first();
|
|
@@ -83,7 +83,7 @@ for (const host of hosts) {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
test('should have appropriate styling for dark mode', async ({ page }) => {
|
|
86
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
86
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
87
87
|
|
|
88
88
|
const iframe = page.frameLocator('iframe');
|
|
89
89
|
// Select card by its border + rounded combo
|
|
@@ -110,7 +110,7 @@ for (const host of hosts) {
|
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
await page.goto(createSimulatorUrl({ simulation: 'carousel
|
|
113
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
|
|
114
114
|
|
|
115
115
|
// Wait for iframe content to render
|
|
116
116
|
const iframe = page.frameLocator('iframe');
|
|
@@ -7,7 +7,7 @@ for (const host of hosts) {
|
|
|
7
7
|
test.describe(`Map Resource [${host}]`, () => {
|
|
8
8
|
test.describe('Light Mode', () => {
|
|
9
9
|
test('should render map container with correct styles', async ({ page }) => {
|
|
10
|
-
await page.goto(createSimulatorUrl({ simulation: 'map
|
|
10
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-map', theme: 'light', host }));
|
|
11
11
|
|
|
12
12
|
const iframe = page.frameLocator('iframe');
|
|
13
13
|
const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
|
|
@@ -26,7 +26,7 @@ for (const host of hosts) {
|
|
|
26
26
|
test('should have rounded border in inline mode', async ({ page }) => {
|
|
27
27
|
await page.goto(
|
|
28
28
|
createSimulatorUrl({
|
|
29
|
-
simulation: 'map
|
|
29
|
+
simulation: 'show-map',
|
|
30
30
|
theme: 'light',
|
|
31
31
|
displayMode: 'inline',
|
|
32
32
|
host,
|
|
@@ -52,7 +52,7 @@ for (const host of hosts) {
|
|
|
52
52
|
test('should have fullscreen expand button in inline mode', async ({ page }) => {
|
|
53
53
|
await page.goto(
|
|
54
54
|
createSimulatorUrl({
|
|
55
|
-
simulation: 'map
|
|
55
|
+
simulation: 'show-map',
|
|
56
56
|
theme: 'light',
|
|
57
57
|
displayMode: 'inline',
|
|
58
58
|
host,
|
|
@@ -83,7 +83,7 @@ for (const host of hosts) {
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
await page.goto(createSimulatorUrl({ simulation: 'map
|
|
86
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-map', theme: 'light', host }));
|
|
87
87
|
|
|
88
88
|
const iframe = page.frameLocator('iframe');
|
|
89
89
|
const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
|
|
@@ -103,7 +103,7 @@ for (const host of hosts) {
|
|
|
103
103
|
|
|
104
104
|
test.describe('Dark Mode', () => {
|
|
105
105
|
test('should render map container with correct styles', async ({ page }) => {
|
|
106
|
-
await page.goto(createSimulatorUrl({ simulation: 'map
|
|
106
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-map', theme: 'dark', host }));
|
|
107
107
|
|
|
108
108
|
const iframe = page.frameLocator('iframe');
|
|
109
109
|
const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
|
|
@@ -113,7 +113,7 @@ for (const host of hosts) {
|
|
|
113
113
|
test('should have appropriate border color for dark mode', async ({ page }) => {
|
|
114
114
|
await page.goto(
|
|
115
115
|
createSimulatorUrl({
|
|
116
|
-
simulation: 'map
|
|
116
|
+
simulation: 'show-map',
|
|
117
117
|
theme: 'dark',
|
|
118
118
|
displayMode: 'inline',
|
|
119
119
|
host,
|
|
@@ -143,7 +143,7 @@ for (const host of hosts) {
|
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
await page.goto(createSimulatorUrl({ simulation: 'map
|
|
146
|
+
await page.goto(createSimulatorUrl({ simulation: 'show-map', theme: 'dark', host }));
|
|
147
147
|
|
|
148
148
|
const iframe = page.frameLocator('iframe');
|
|
149
149
|
const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
|
|
@@ -165,7 +165,7 @@ for (const host of hosts) {
|
|
|
165
165
|
test('should not have rounded border in fullscreen mode', async ({ page }) => {
|
|
166
166
|
await page.goto(
|
|
167
167
|
createSimulatorUrl({
|
|
168
|
-
simulation: 'map
|
|
168
|
+
simulation: 'show-map',
|
|
169
169
|
theme: 'light',
|
|
170
170
|
displayMode: 'fullscreen',
|
|
171
171
|
host,
|
|
@@ -189,7 +189,7 @@ for (const host of hosts) {
|
|
|
189
189
|
test('should not show fullscreen button when already in fullscreen', async ({ page }) => {
|
|
190
190
|
await page.goto(
|
|
191
191
|
createSimulatorUrl({
|
|
192
|
-
simulation: 'map
|
|
192
|
+
simulation: 'show-map',
|
|
193
193
|
theme: 'light',
|
|
194
194
|
displayMode: 'fullscreen',
|
|
195
195
|
host,
|
|
@@ -208,7 +208,7 @@ for (const host of hosts) {
|
|
|
208
208
|
test('should show place list sidebar in fullscreen', async ({ page }) => {
|
|
209
209
|
await page.goto(
|
|
210
210
|
createSimulatorUrl({
|
|
211
|
-
simulation: 'map
|
|
211
|
+
simulation: 'show-map',
|
|
212
212
|
theme: 'dark',
|
|
213
213
|
displayMode: 'fullscreen',
|
|
214
214
|
host,
|
|
@@ -225,7 +225,7 @@ for (const host of hosts) {
|
|
|
225
225
|
await page.setViewportSize({ width: 1024, height: 768 });
|
|
226
226
|
await page.goto(
|
|
227
227
|
createSimulatorUrl({
|
|
228
|
-
simulation: 'map
|
|
228
|
+
simulation: 'show-map',
|
|
229
229
|
theme: 'light',
|
|
230
230
|
displayMode: 'fullscreen',
|
|
231
231
|
host,
|
|
@@ -1,36 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
+
"tool": "review-diff",
|
|
2
3
|
"userMessage": "Refactor the authentication module to use JWT tokens instead of sessions.",
|
|
3
|
-
"tool": {
|
|
4
|
-
"name": "review-diff",
|
|
5
|
-
"description": "Show a review dialog for a proposed code diff",
|
|
6
|
-
"inputSchema": {
|
|
7
|
-
"type": "object",
|
|
8
|
-
"properties": {
|
|
9
|
-
"changesetId": { "type": "string", "description": "Unique identifier for the changeset" },
|
|
10
|
-
"title": { "type": "string", "description": "Title describing the changes" },
|
|
11
|
-
"description": {
|
|
12
|
-
"type": "string",
|
|
13
|
-
"description": "Detailed description of what the changes accomplish"
|
|
14
|
-
},
|
|
15
|
-
"files": {
|
|
16
|
-
"type": "array",
|
|
17
|
-
"items": { "type": "string" },
|
|
18
|
-
"description": "List of file paths affected by this change"
|
|
19
|
-
},
|
|
20
|
-
"runMigrations": {
|
|
21
|
-
"type": "boolean",
|
|
22
|
-
"description": "Whether to run database migrations as part of the change"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"required": ["changesetId", "title"],
|
|
26
|
-
"additionalProperties": false
|
|
27
|
-
},
|
|
28
|
-
"title": "Diff Review",
|
|
29
|
-
"annotations": { "readOnlyHint": false },
|
|
30
|
-
"_meta": {
|
|
31
|
-
"ui": { "visibility": ["model", "app"] }
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
4
|
"toolInput": {
|
|
35
5
|
"changesetId": "cs_789",
|
|
36
6
|
"title": "Refactor Authentication Module",
|