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.
Files changed (98) hide show
  1. package/README.md +51 -48
  2. package/bin/commands/build.mjs +13 -4
  3. package/bin/commands/dev.mjs +64 -19
  4. package/bin/commands/new.mjs +13 -3
  5. package/bin/lib/extract-resource.mjs +1 -1
  6. package/bin/lib/extract-tool.mjs +78 -0
  7. package/bin/lib/patterns.mjs +2 -26
  8. package/dist/chatgpt/index.cjs +3 -6
  9. package/dist/chatgpt/index.cjs.map +1 -1
  10. package/dist/chatgpt/index.d.ts +1 -1
  11. package/dist/chatgpt/index.js +6 -9
  12. package/dist/claude/index.cjs +1 -1
  13. package/dist/claude/index.js +1 -1
  14. package/dist/discovery-CH80W5l9.js +217 -0
  15. package/dist/discovery-CH80W5l9.js.map +1 -0
  16. package/dist/discovery-DmB8_4QL.cjs +216 -0
  17. package/dist/discovery-DmB8_4QL.cjs.map +1 -0
  18. package/dist/{index-CutQgPzR.js → index-BjnAsaqp.js} +3 -6
  19. package/dist/index-BjnAsaqp.js.map +1 -0
  20. package/dist/{index-Cngntkp2.cjs → index-BvQ_ZuOO.cjs} +3 -6
  21. package/dist/{index-Cngntkp2.cjs.map → index-BvQ_ZuOO.cjs.map} +1 -1
  22. package/dist/{index-B0dxRJvS.cjs → index-C9CVbGFt.cjs} +3 -6
  23. package/dist/index-C9CVbGFt.cjs.map +1 -0
  24. package/dist/{index-Ce_5ZIdJ.js → index-CTGEqlgk.js} +3 -6
  25. package/dist/{index-Ce_5ZIdJ.js.map → index-CTGEqlgk.js.map} +1 -1
  26. package/dist/index.cjs +48 -5
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.js +3404 -3361
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/discovery-cli.cjs +58 -5
  32. package/dist/lib/discovery-cli.cjs.map +1 -1
  33. package/dist/lib/discovery-cli.d.ts +3 -2
  34. package/dist/lib/discovery-cli.js +61 -8
  35. package/dist/lib/discovery-cli.js.map +1 -1
  36. package/dist/lib/discovery.d.ts +42 -43
  37. package/dist/lib/extract-tool.d.ts +12 -0
  38. package/dist/mcp/favicon.d.ts +1 -1
  39. package/dist/mcp/index.cjs +3 -2
  40. package/dist/mcp/index.cjs.map +1 -1
  41. package/dist/mcp/index.d.ts +1 -1
  42. package/dist/mcp/index.js +3 -2
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/types.d.ts +24 -1
  45. package/dist/platform/chatgpt/index.cjs +1 -1
  46. package/dist/platform/chatgpt/index.js +1 -1
  47. package/dist/simulator/index.cjs +2 -5
  48. package/dist/simulator/index.cjs.map +1 -1
  49. package/dist/simulator/index.d.ts +1 -1
  50. package/dist/simulator/index.js +5 -8
  51. package/dist/simulator/simulator-url.d.ts +9 -9
  52. package/dist/{simulator-DcfQBRXE.cjs → simulator-B56j5P8W.cjs} +8 -2
  53. package/dist/{simulator-DcfQBRXE.cjs.map → simulator-B56j5P8W.cjs.map} +1 -1
  54. package/dist/{simulator-CxrtnguM.js → simulator-C0H_k092.js} +8 -2
  55. package/dist/{simulator-CxrtnguM.js.map → simulator-C0H_k092.js.map} +1 -1
  56. package/dist/simulator-url-CuLqtnSS.js.map +1 -1
  57. package/dist/simulator-url-rgg_KYOg.cjs.map +1 -1
  58. package/dist/types/resource-config.d.ts +7 -5
  59. package/dist/{use-app-D_TeaMFG.js → use-app-BThbgFFT.js} +51 -22
  60. package/dist/{use-app-D_TeaMFG.js.map → use-app-BThbgFFT.js.map} +1 -1
  61. package/dist/{use-app-BnoSPiUT.cjs → use-app-BuufpXTQ.cjs} +49 -20
  62. package/dist/{use-app-BnoSPiUT.cjs.map → use-app-BuufpXTQ.cjs.map} +1 -1
  63. package/package.json +1 -1
  64. package/template/.sunpeak/dev.tsx +8 -4
  65. package/template/.sunpeak/resource-loader.tsx +2 -1
  66. package/template/README.md +14 -10
  67. package/template/package.json +2 -1
  68. package/template/src/resources/albums/{albums-resource.test.tsx → albums.test.tsx} +1 -1
  69. package/template/src/resources/albums/{albums-resource.tsx → albums.tsx} +0 -1
  70. package/template/src/resources/carousel/{carousel-resource.test.tsx → carousel.test.tsx} +1 -1
  71. package/template/src/resources/carousel/{carousel-resource.tsx → carousel.tsx} +0 -1
  72. package/template/src/resources/index.ts +4 -4
  73. package/template/src/resources/map/{map-resource.test.tsx → map.test.tsx} +1 -1
  74. package/template/src/resources/map/{map-resource.tsx → map.tsx} +0 -1
  75. package/template/src/resources/review/{review-resource.test.tsx → review.test.tsx} +1 -1
  76. package/template/src/resources/review/{review-resource.tsx → review.tsx} +1 -2
  77. package/template/src/server.ts +15 -0
  78. package/template/src/tools/review-diff.ts +24 -0
  79. package/template/src/tools/review-post.ts +26 -0
  80. package/template/src/tools/review-purchase.ts +31 -0
  81. package/template/src/tools/show-albums.ts +22 -0
  82. package/template/src/tools/show-carousel.ts +25 -0
  83. package/template/src/tools/show-map.ts +29 -0
  84. package/template/tests/e2e/albums.spec.ts +6 -6
  85. package/template/tests/e2e/carousel.spec.ts +6 -6
  86. package/template/tests/e2e/map.spec.ts +11 -11
  87. package/template/tests/simulations/{review/review-diff-simulation.json → review-diff.json} +1 -31
  88. package/template/tests/simulations/{review/review-post-simulation.json → review-post.json} +1 -37
  89. package/template/tests/simulations/{review/review-purchase-simulation.json → review-purchase.json} +1 -38
  90. package/template/tests/simulations/{albums/albums-show-simulation.json → show-albums.json} +1 -24
  91. package/template/tests/simulations/{carousel/carousel-show-simulation.json → show-carousel.json} +1 -24
  92. package/template/tests/simulations/{map/map-show-simulation.json → show-map.json} +1 -35
  93. package/dist/discovery-CRR3SlyI.cjs +0 -156
  94. package/dist/discovery-CRR3SlyI.cjs.map +0 -1
  95. package/dist/discovery-DzV3HLXs.js +0 -157
  96. package/dist/discovery-DzV3HLXs.js.map +0 -1
  97. package/dist/index-B0dxRJvS.cjs.map +0 -1
  98. package/dist/index-CutQgPzR.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.15.4",
3
+ "version": "0.16.1",
4
4
  "description": "Local-first MCP Apps framework. Quickstart, build, test, and ship your Claude or ChatGPT App!",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -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/{resource}/{resource}-{scenario}-simulation.json
8
- * - src/resources/{resource}/{resource}-resource.tsx (component + resource metadata)
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/*/*-simulation.json', { eager: true }),
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>
@@ -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-resource.tsx # Review UI component + resource metadata.
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
- └── review/
38
- ├── review-{scenario1}-simulation.json # Mock state for testing.
39
- └── review-{scenario2}-simulation.json # Mock state for testing.
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-resource.tsx # React component + resource metadata (required)
94
- ├── NAME-resource.test.tsx # Unit tests (optional)
95
- └── components/ # UI components (optional)
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
- Create the simulation file(s) in `tests/simulations/` if you want to preview your resource in `sunpeak dev`.
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)
@@ -15,7 +15,8 @@
15
15
  "embla-carousel-wheel-gestures": "^8.1.0",
16
16
  "mapbox-gl": "^3.17.0",
17
17
  "sunpeak": "workspace:*",
18
- "tailwind-merge": "^3.4.0"
18
+ "tailwind-merge": "^3.4.0",
19
+ "zod": "^3.25.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@playwright/test": "^1.57.0",
@@ -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-resource';
3
+ import { AlbumsResource } from './albums';
4
4
 
5
5
  // Mock sunpeak — SafeArea renders as a plain div
6
6
  vi.mock('sunpeak', () => ({
@@ -3,7 +3,6 @@ import type { ResourceConfig } from 'sunpeak';
3
3
  import { Albums } from './components/albums';
4
4
 
5
5
  export const resource: ResourceConfig = {
6
- name: 'albums',
7
6
  title: 'Albums',
8
7
  description: 'Show photo albums widget',
9
8
  mimeType: 'text/html;profile=mcp-app',
@@ -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-resource';
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}-resource.tsx files and exports their component
5
- * with a PascalCase name (e.g., albums/albums-resource.tsx -> AlbumsResource).
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('./*/*-resource.tsx', { eager: true });
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-resource';
3
+ import { MapResource } from './map';
4
4
 
5
5
  // Mock sunpeak — SafeArea renders as a plain div
6
6
  vi.mock('sunpeak', () => ({
@@ -3,7 +3,6 @@ import type { ResourceConfig } from 'sunpeak';
3
3
  import { Map } from './components/map';
4
4
 
5
5
  export const resource: ResourceConfig = {
6
- name: 'map',
7
6
  title: 'Map',
8
7
  description: 'Pizza restaurant finder widget',
9
8
  mimeType: 'text/html;profile=mcp-app',
@@ -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-resource';
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)] bg-[var(--color-background-primary)]">
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-show', theme: 'light', host }));
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-show', theme: 'light', host }));
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-show', theme: 'dark', host }));
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-show', theme: 'dark', host }));
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-show',
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-show',
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-show', theme: 'light', host }));
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-show', theme: 'light', host }));
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-show', theme: 'light', host }));
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-show', theme: 'dark', host }));
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-show', theme: 'dark', host }));
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-show', theme: 'dark', host }));
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-show', theme: 'light', host }));
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-show',
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-show',
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-show', theme: 'light', host }));
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-show', theme: 'dark', host }));
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-show',
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-show', theme: 'dark', host }));
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-show',
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-show',
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-show',
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-show',
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",