sunpeak 0.18.6 → 0.18.9
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/bin/commands/dev.mjs +9 -5
- package/bin/commands/inspect.mjs +13 -1
- package/bin/commands/new.mjs +5 -0
- package/bin/lib/dev-overlay.mjs +50 -0
- package/bin/lib/live/live-config.d.mts +3 -0
- package/bin/lib/live/live-config.mjs +3 -1
- package/bin/lib/sandbox-server.mjs +19 -0
- package/dist/chatgpt/index.cjs +2 -3
- package/dist/chatgpt/index.js +2 -3
- package/dist/claude/index.cjs +1 -2
- package/dist/claude/index.js +1 -2
- package/dist/host/chatgpt/index.cjs +0 -1
- package/dist/host/chatgpt/index.cjs.map +1 -1
- package/dist/host/chatgpt/index.js +0 -1
- package/dist/host/chatgpt/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/inspector/index.cjs +2 -3
- package/dist/inspector/index.js +2 -3
- package/dist/inspector/inspector-url.d.ts +13 -0
- package/dist/inspector/use-inspector-state.d.ts +2 -0
- package/dist/{inspector-DRD_Q66E.cjs → inspector-CTMccsz9.cjs} +71 -22
- package/dist/inspector-CTMccsz9.cjs.map +1 -0
- package/dist/{inspector-CjSoXm6N.js → inspector-DkS75JCk.js} +71 -22
- package/dist/inspector-DkS75JCk.js.map +1 -0
- package/dist/{inspector-url-7qhtJwY6.cjs → inspector-url-C3LTKgXt.cjs} +3 -1
- package/dist/inspector-url-C3LTKgXt.cjs.map +1 -0
- package/dist/{inspector-url-DuEFmxLP.js → inspector-url-CyQcuBI9.js} +3 -1
- package/dist/inspector-url-CyQcuBI9.js.map +1 -0
- package/dist/mcp/index.cjs +98 -15
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +98 -15
- package/dist/mcp/index.js.map +1 -1
- package/dist/style.css +4 -0
- package/package.json +5 -5
- 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/.bin/vite +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/node_modules/.vite/deps/_metadata.json +4 -4
- package/template/node_modules/.vite-mcp/deps/@testing-library_react.js +9 -6
- package/template/node_modules/.vite-mcp/deps/@testing-library_react.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/_metadata.json +21 -21
- package/template/node_modules/.vite-mcp/deps/vitest.js +366 -128
- package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
- package/template/package.json +2 -2
- package/template/tests/e2e/albums.spec.ts +1 -1
- package/template/tests/e2e/carousel.spec.ts +1 -1
- package/template/tests/e2e/dev-overlay.spec.ts +118 -0
- package/template/tests/e2e/helpers.ts +13 -0
- package/template/tests/e2e/map.spec.ts +1 -1
- package/template/tests/e2e/review.spec.ts +1 -1
- package/template/tests/live/playwright.config.ts +1 -1
- package/dist/inspector-CjSoXm6N.js.map +0 -1
- package/dist/inspector-DRD_Q66E.cjs.map +0 -1
- package/dist/inspector-url-7qhtJwY6.cjs.map +0 -1
- package/dist/inspector-url-DuEFmxLP.js.map +0 -1
|
@@ -38,6 +38,8 @@ function createInspectorUrl(params, basePath = "/") {
|
|
|
38
38
|
if (params.safeAreaLeft !== void 0) searchParams.set("safeAreaLeft", String(params.safeAreaLeft));
|
|
39
39
|
if (params.safeAreaRight !== void 0) searchParams.set("safeAreaRight", String(params.safeAreaRight));
|
|
40
40
|
if (params.prodResources !== void 0) searchParams.set("prodResources", String(params.prodResources));
|
|
41
|
+
if (params.sidebar !== void 0) searchParams.set("sidebar", String(params.sidebar));
|
|
42
|
+
if (params.devOverlay !== void 0) searchParams.set("devOverlay", String(params.devOverlay));
|
|
41
43
|
const queryString = searchParams.toString();
|
|
42
44
|
return queryString ? `${basePath}?${queryString}` : basePath;
|
|
43
45
|
}
|
|
@@ -49,4 +51,4 @@ Object.defineProperty(exports, "createInspectorUrl", {
|
|
|
49
51
|
}
|
|
50
52
|
});
|
|
51
53
|
|
|
52
|
-
//# sourceMappingURL=inspector-url-
|
|
54
|
+
//# sourceMappingURL=inspector-url-C3LTKgXt.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-url-C3LTKgXt.cjs","names":[],"sources":["../src/inspector/inspector-url.ts"],"sourcesContent":["import type { Theme, DisplayMode, DeviceType } from '../types/runtime';\n\n/**\n * Strongly-typed URL parameters for the Inspector.\n *\n * Use with `createInspectorUrl()` to generate type-safe URL paths for e2e tests.\n *\n * The two primary selectors mirror the sidebar dropdowns:\n * - `tool` — which tool to inspect (Tool dropdown)\n * - `simulation` — which simulation fixture to load (Simulation dropdown)\n *\n * When only `tool` is specified, no mock data is loaded (\"Press Run\" state).\n * When `simulation` is specified, mock data from that fixture renders immediately.\n * When both are specified, the tool is selected and the simulation provides mock data.\n *\n * @example\n * ```ts\n * import { createInspectorUrl } from 'sunpeak/inspector';\n *\n * // Select a tool with no mock data (user must click Run):\n * await page.goto(createInspectorUrl({ tool: 'show-albums' }));\n *\n * // Select a simulation (mock data renders immediately):\n * await page.goto(createInspectorUrl({ simulation: 'show-albums' }));\n *\n * // Full options:\n * await page.goto(createInspectorUrl({\n * simulation: 'show-albums',\n * theme: 'dark',\n * host: 'claude',\n * }));\n * ```\n */\nexport interface InspectorUrlParams {\n /**\n * The simulation name to load (e.g., 'show-albums', 'review-diff').\n * Corresponds to the simulation JSON filename without the `.json` extension.\n * When specified, mock data from the simulation fixture renders immediately.\n */\n simulation?: string;\n\n /**\n * The tool name to select (e.g., 'show-albums', 'show-map').\n * When specified without `simulation`, no mock data is loaded — the user\n * must click Run to call the real handler.\n */\n tool?: string;\n\n /**\n * The host shell to use (e.g., 'chatgpt', 'claude').\n * Switches conversation chrome, theming, and reported host info/capabilities.\n * @default 'chatgpt'\n */\n host?: string;\n\n /**\n * The color theme for the inspector.\n * @default 'dark'\n */\n theme?: Theme;\n\n /**\n * The display mode for the widget.\n * - 'inline': Embedded in the conversation\n * - 'pip': Picture-in-picture mode with max height\n * - 'fullscreen': Full screen overlay\n * @default 'inline'\n */\n displayMode?: DisplayMode;\n\n /**\n * The locale for the inspector (e.g., 'en-US', 'ja-JP').\n * @default 'en-US'\n */\n locale?: string;\n\n /**\n * Maximum height in pixels for PiP mode.\n * Only applicable when displayMode is 'pip'.\n */\n maxHeight?: number;\n\n /**\n * The device type to simulate.\n * Affects default hover/touch capabilities.\n */\n deviceType?: DeviceType;\n\n /**\n * Whether the device supports hover interactions.\n * @default true for desktop, false for mobile/tablet\n */\n hover?: boolean;\n\n /**\n * Whether the device supports touch interactions.\n * @default false for desktop, true for mobile/tablet\n */\n touch?: boolean;\n\n /**\n * Safe area inset from the top of the screen (in pixels).\n * Used for devices with notches or status bars.\n */\n safeAreaTop?: number;\n\n /**\n * Safe area inset from the bottom of the screen (in pixels).\n * Used for devices with home indicators.\n */\n safeAreaBottom?: number;\n\n /**\n * Safe area inset from the left of the screen (in pixels).\n */\n safeAreaLeft?: number;\n\n /**\n * Safe area inset from the right of the screen (in pixels).\n */\n safeAreaRight?: number;\n\n /**\n * Enable Prod Resources mode (production dist/ bundles instead of HMR).\n */\n prodResources?: boolean;\n\n /**\n * Show the inspector sidebar. Useful for headless testing or embedding\n * the inspector as a pure resource viewer.\n * @default true\n */\n sidebar?: boolean;\n\n /**\n * Show the dev overlay (resource timestamp + tool timing) inside resources.\n * Set to false to hide it during e2e tests so it doesn't interfere with\n * element assertions.\n * @default true\n */\n devOverlay?: boolean;\n}\n\n/**\n * Creates a URL path with query parameters for the Inspector.\n *\n * @param params - The inspector parameters to encode\n * @param basePath - The base path for the URL (default: '/')\n * @returns A URL path string with encoded query parameters\n *\n * @example\n * ```ts\n * // Tool only (no mock data, \"Press Run\" state):\n * createInspectorUrl({ tool: 'show-albums' })\n * // Returns: '/?tool=show-albums'\n *\n * // Simulation (mock data renders immediately):\n * createInspectorUrl({ simulation: 'show-albums', theme: 'light' })\n * // Returns: '/?simulation=show-albums&theme=light'\n *\n * // Both tool and simulation:\n * createInspectorUrl({ tool: 'show-albums', simulation: 'show-albums' })\n * // Returns: '/?tool=show-albums&simulation=show-albums'\n * ```\n */\nexport function createInspectorUrl(params: InspectorUrlParams, basePath = '/'): string {\n const searchParams = new URLSearchParams();\n\n if (params.tool !== undefined) {\n searchParams.set('tool', params.tool);\n }\n if (params.simulation !== undefined) {\n searchParams.set('simulation', params.simulation);\n }\n if (params.host !== undefined) {\n searchParams.set('host', params.host);\n }\n if (params.theme !== undefined) {\n searchParams.set('theme', params.theme);\n }\n if (params.displayMode !== undefined) {\n searchParams.set('displayMode', params.displayMode);\n }\n if (params.locale !== undefined) {\n searchParams.set('locale', params.locale);\n }\n if (params.maxHeight !== undefined) {\n searchParams.set('maxHeight', String(params.maxHeight));\n }\n if (params.deviceType !== undefined) {\n searchParams.set('deviceType', params.deviceType);\n }\n if (params.hover !== undefined) {\n searchParams.set('hover', String(params.hover));\n }\n if (params.touch !== undefined) {\n searchParams.set('touch', String(params.touch));\n }\n if (params.safeAreaTop !== undefined) {\n searchParams.set('safeAreaTop', String(params.safeAreaTop));\n }\n if (params.safeAreaBottom !== undefined) {\n searchParams.set('safeAreaBottom', String(params.safeAreaBottom));\n }\n if (params.safeAreaLeft !== undefined) {\n searchParams.set('safeAreaLeft', String(params.safeAreaLeft));\n }\n if (params.safeAreaRight !== undefined) {\n searchParams.set('safeAreaRight', String(params.safeAreaRight));\n }\n if (params.prodResources !== undefined) {\n searchParams.set('prodResources', String(params.prodResources));\n }\n if (params.sidebar !== undefined) {\n searchParams.set('sidebar', String(params.sidebar));\n }\n if (params.devOverlay !== undefined) {\n searchParams.set('devOverlay', String(params.devOverlay));\n }\n\n const queryString = searchParams.toString();\n return queryString ? `${basePath}?${queryString}` : basePath;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqKA,SAAgB,mBAAmB,QAA4B,WAAW,KAAa;CACrF,MAAM,eAAe,IAAI,iBAAiB;AAE1C,KAAI,OAAO,SAAS,KAAA,EAClB,cAAa,IAAI,QAAQ,OAAO,KAAK;AAEvC,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,WAAW;AAEnD,KAAI,OAAO,SAAS,KAAA,EAClB,cAAa,IAAI,QAAQ,OAAO,KAAK;AAEvC,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,MAAM;AAEzC,KAAI,OAAO,gBAAgB,KAAA,EACzB,cAAa,IAAI,eAAe,OAAO,YAAY;AAErD,KAAI,OAAO,WAAW,KAAA,EACpB,cAAa,IAAI,UAAU,OAAO,OAAO;AAE3C,KAAI,OAAO,cAAc,KAAA,EACvB,cAAa,IAAI,aAAa,OAAO,OAAO,UAAU,CAAC;AAEzD,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,WAAW;AAEnD,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;AAEjD,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;AAEjD,KAAI,OAAO,gBAAgB,KAAA,EACzB,cAAa,IAAI,eAAe,OAAO,OAAO,YAAY,CAAC;AAE7D,KAAI,OAAO,mBAAmB,KAAA,EAC5B,cAAa,IAAI,kBAAkB,OAAO,OAAO,eAAe,CAAC;AAEnE,KAAI,OAAO,iBAAiB,KAAA,EAC1B,cAAa,IAAI,gBAAgB,OAAO,OAAO,aAAa,CAAC;AAE/D,KAAI,OAAO,kBAAkB,KAAA,EAC3B,cAAa,IAAI,iBAAiB,OAAO,OAAO,cAAc,CAAC;AAEjE,KAAI,OAAO,kBAAkB,KAAA,EAC3B,cAAa,IAAI,iBAAiB,OAAO,OAAO,cAAc,CAAC;AAEjE,KAAI,OAAO,YAAY,KAAA,EACrB,cAAa,IAAI,WAAW,OAAO,OAAO,QAAQ,CAAC;AAErD,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,OAAO,WAAW,CAAC;CAG3D,MAAM,cAAc,aAAa,UAAU;AAC3C,QAAO,cAAc,GAAG,SAAS,GAAG,gBAAgB"}
|
|
@@ -38,10 +38,12 @@ function createInspectorUrl(params, basePath = "/") {
|
|
|
38
38
|
if (params.safeAreaLeft !== void 0) searchParams.set("safeAreaLeft", String(params.safeAreaLeft));
|
|
39
39
|
if (params.safeAreaRight !== void 0) searchParams.set("safeAreaRight", String(params.safeAreaRight));
|
|
40
40
|
if (params.prodResources !== void 0) searchParams.set("prodResources", String(params.prodResources));
|
|
41
|
+
if (params.sidebar !== void 0) searchParams.set("sidebar", String(params.sidebar));
|
|
42
|
+
if (params.devOverlay !== void 0) searchParams.set("devOverlay", String(params.devOverlay));
|
|
41
43
|
const queryString = searchParams.toString();
|
|
42
44
|
return queryString ? `${basePath}?${queryString}` : basePath;
|
|
43
45
|
}
|
|
44
46
|
//#endregion
|
|
45
47
|
export { createInspectorUrl as t };
|
|
46
48
|
|
|
47
|
-
//# sourceMappingURL=inspector-url-
|
|
49
|
+
//# sourceMappingURL=inspector-url-CyQcuBI9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector-url-CyQcuBI9.js","names":[],"sources":["../src/inspector/inspector-url.ts"],"sourcesContent":["import type { Theme, DisplayMode, DeviceType } from '../types/runtime';\n\n/**\n * Strongly-typed URL parameters for the Inspector.\n *\n * Use with `createInspectorUrl()` to generate type-safe URL paths for e2e tests.\n *\n * The two primary selectors mirror the sidebar dropdowns:\n * - `tool` — which tool to inspect (Tool dropdown)\n * - `simulation` — which simulation fixture to load (Simulation dropdown)\n *\n * When only `tool` is specified, no mock data is loaded (\"Press Run\" state).\n * When `simulation` is specified, mock data from that fixture renders immediately.\n * When both are specified, the tool is selected and the simulation provides mock data.\n *\n * @example\n * ```ts\n * import { createInspectorUrl } from 'sunpeak/inspector';\n *\n * // Select a tool with no mock data (user must click Run):\n * await page.goto(createInspectorUrl({ tool: 'show-albums' }));\n *\n * // Select a simulation (mock data renders immediately):\n * await page.goto(createInspectorUrl({ simulation: 'show-albums' }));\n *\n * // Full options:\n * await page.goto(createInspectorUrl({\n * simulation: 'show-albums',\n * theme: 'dark',\n * host: 'claude',\n * }));\n * ```\n */\nexport interface InspectorUrlParams {\n /**\n * The simulation name to load (e.g., 'show-albums', 'review-diff').\n * Corresponds to the simulation JSON filename without the `.json` extension.\n * When specified, mock data from the simulation fixture renders immediately.\n */\n simulation?: string;\n\n /**\n * The tool name to select (e.g., 'show-albums', 'show-map').\n * When specified without `simulation`, no mock data is loaded — the user\n * must click Run to call the real handler.\n */\n tool?: string;\n\n /**\n * The host shell to use (e.g., 'chatgpt', 'claude').\n * Switches conversation chrome, theming, and reported host info/capabilities.\n * @default 'chatgpt'\n */\n host?: string;\n\n /**\n * The color theme for the inspector.\n * @default 'dark'\n */\n theme?: Theme;\n\n /**\n * The display mode for the widget.\n * - 'inline': Embedded in the conversation\n * - 'pip': Picture-in-picture mode with max height\n * - 'fullscreen': Full screen overlay\n * @default 'inline'\n */\n displayMode?: DisplayMode;\n\n /**\n * The locale for the inspector (e.g., 'en-US', 'ja-JP').\n * @default 'en-US'\n */\n locale?: string;\n\n /**\n * Maximum height in pixels for PiP mode.\n * Only applicable when displayMode is 'pip'.\n */\n maxHeight?: number;\n\n /**\n * The device type to simulate.\n * Affects default hover/touch capabilities.\n */\n deviceType?: DeviceType;\n\n /**\n * Whether the device supports hover interactions.\n * @default true for desktop, false for mobile/tablet\n */\n hover?: boolean;\n\n /**\n * Whether the device supports touch interactions.\n * @default false for desktop, true for mobile/tablet\n */\n touch?: boolean;\n\n /**\n * Safe area inset from the top of the screen (in pixels).\n * Used for devices with notches or status bars.\n */\n safeAreaTop?: number;\n\n /**\n * Safe area inset from the bottom of the screen (in pixels).\n * Used for devices with home indicators.\n */\n safeAreaBottom?: number;\n\n /**\n * Safe area inset from the left of the screen (in pixels).\n */\n safeAreaLeft?: number;\n\n /**\n * Safe area inset from the right of the screen (in pixels).\n */\n safeAreaRight?: number;\n\n /**\n * Enable Prod Resources mode (production dist/ bundles instead of HMR).\n */\n prodResources?: boolean;\n\n /**\n * Show the inspector sidebar. Useful for headless testing or embedding\n * the inspector as a pure resource viewer.\n * @default true\n */\n sidebar?: boolean;\n\n /**\n * Show the dev overlay (resource timestamp + tool timing) inside resources.\n * Set to false to hide it during e2e tests so it doesn't interfere with\n * element assertions.\n * @default true\n */\n devOverlay?: boolean;\n}\n\n/**\n * Creates a URL path with query parameters for the Inspector.\n *\n * @param params - The inspector parameters to encode\n * @param basePath - The base path for the URL (default: '/')\n * @returns A URL path string with encoded query parameters\n *\n * @example\n * ```ts\n * // Tool only (no mock data, \"Press Run\" state):\n * createInspectorUrl({ tool: 'show-albums' })\n * // Returns: '/?tool=show-albums'\n *\n * // Simulation (mock data renders immediately):\n * createInspectorUrl({ simulation: 'show-albums', theme: 'light' })\n * // Returns: '/?simulation=show-albums&theme=light'\n *\n * // Both tool and simulation:\n * createInspectorUrl({ tool: 'show-albums', simulation: 'show-albums' })\n * // Returns: '/?tool=show-albums&simulation=show-albums'\n * ```\n */\nexport function createInspectorUrl(params: InspectorUrlParams, basePath = '/'): string {\n const searchParams = new URLSearchParams();\n\n if (params.tool !== undefined) {\n searchParams.set('tool', params.tool);\n }\n if (params.simulation !== undefined) {\n searchParams.set('simulation', params.simulation);\n }\n if (params.host !== undefined) {\n searchParams.set('host', params.host);\n }\n if (params.theme !== undefined) {\n searchParams.set('theme', params.theme);\n }\n if (params.displayMode !== undefined) {\n searchParams.set('displayMode', params.displayMode);\n }\n if (params.locale !== undefined) {\n searchParams.set('locale', params.locale);\n }\n if (params.maxHeight !== undefined) {\n searchParams.set('maxHeight', String(params.maxHeight));\n }\n if (params.deviceType !== undefined) {\n searchParams.set('deviceType', params.deviceType);\n }\n if (params.hover !== undefined) {\n searchParams.set('hover', String(params.hover));\n }\n if (params.touch !== undefined) {\n searchParams.set('touch', String(params.touch));\n }\n if (params.safeAreaTop !== undefined) {\n searchParams.set('safeAreaTop', String(params.safeAreaTop));\n }\n if (params.safeAreaBottom !== undefined) {\n searchParams.set('safeAreaBottom', String(params.safeAreaBottom));\n }\n if (params.safeAreaLeft !== undefined) {\n searchParams.set('safeAreaLeft', String(params.safeAreaLeft));\n }\n if (params.safeAreaRight !== undefined) {\n searchParams.set('safeAreaRight', String(params.safeAreaRight));\n }\n if (params.prodResources !== undefined) {\n searchParams.set('prodResources', String(params.prodResources));\n }\n if (params.sidebar !== undefined) {\n searchParams.set('sidebar', String(params.sidebar));\n }\n if (params.devOverlay !== undefined) {\n searchParams.set('devOverlay', String(params.devOverlay));\n }\n\n const queryString = searchParams.toString();\n return queryString ? `${basePath}?${queryString}` : basePath;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqKA,SAAgB,mBAAmB,QAA4B,WAAW,KAAa;CACrF,MAAM,eAAe,IAAI,iBAAiB;AAE1C,KAAI,OAAO,SAAS,KAAA,EAClB,cAAa,IAAI,QAAQ,OAAO,KAAK;AAEvC,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,WAAW;AAEnD,KAAI,OAAO,SAAS,KAAA,EAClB,cAAa,IAAI,QAAQ,OAAO,KAAK;AAEvC,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,MAAM;AAEzC,KAAI,OAAO,gBAAgB,KAAA,EACzB,cAAa,IAAI,eAAe,OAAO,YAAY;AAErD,KAAI,OAAO,WAAW,KAAA,EACpB,cAAa,IAAI,UAAU,OAAO,OAAO;AAE3C,KAAI,OAAO,cAAc,KAAA,EACvB,cAAa,IAAI,aAAa,OAAO,OAAO,UAAU,CAAC;AAEzD,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,WAAW;AAEnD,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;AAEjD,KAAI,OAAO,UAAU,KAAA,EACnB,cAAa,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;AAEjD,KAAI,OAAO,gBAAgB,KAAA,EACzB,cAAa,IAAI,eAAe,OAAO,OAAO,YAAY,CAAC;AAE7D,KAAI,OAAO,mBAAmB,KAAA,EAC5B,cAAa,IAAI,kBAAkB,OAAO,OAAO,eAAe,CAAC;AAEnE,KAAI,OAAO,iBAAiB,KAAA,EAC1B,cAAa,IAAI,gBAAgB,OAAO,OAAO,aAAa,CAAC;AAE/D,KAAI,OAAO,kBAAkB,KAAA,EAC3B,cAAa,IAAI,iBAAiB,OAAO,OAAO,cAAc,CAAC;AAEjE,KAAI,OAAO,kBAAkB,KAAA,EAC3B,cAAa,IAAI,iBAAiB,OAAO,OAAO,cAAc,CAAC;AAEjE,KAAI,OAAO,YAAY,KAAA,EACrB,cAAa,IAAI,WAAW,OAAO,OAAO,QAAQ,CAAC;AAErD,KAAI,OAAO,eAAe,KAAA,EACxB,cAAa,IAAI,cAAc,OAAO,OAAO,WAAW,CAAC;CAG3D,MAAM,cAAc,aAAa,UAAU;AAC3C,QAAO,cAAc,GAAG,SAAS,GAAG,gBAAgB"}
|
package/dist/mcp/index.cjs
CHANGED
|
@@ -9109,6 +9109,10 @@ function injectDefaultDomain(meta, clientName, serverUrl) {
|
|
|
9109
9109
|
//#region src/mcp/server.ts
|
|
9110
9110
|
var localDevServerUrl = "http://localhost:8000";
|
|
9111
9111
|
var localHmrWsUrl = "ws://localhost:24678";
|
|
9112
|
+
var lastToolTimingMs = null;
|
|
9113
|
+
function isDevOverlayEnabled() {
|
|
9114
|
+
return process.env.SUNPEAK_DEV_OVERLAY !== "false";
|
|
9115
|
+
}
|
|
9112
9116
|
/**
|
|
9113
9117
|
* Detect whether this request needs pre-built HTML (no Vite HMR).
|
|
9114
9118
|
*
|
|
@@ -9128,12 +9132,66 @@ function needsProdBuild(headers) {
|
|
|
9128
9132
|
* Read pre-built resource HTML (production mode).
|
|
9129
9133
|
* The HTML file is generated by `sunpeak build` with JS and CSS inlined.
|
|
9130
9134
|
*/
|
|
9131
|
-
function readResourceHtmlProd(distPath) {
|
|
9135
|
+
function readResourceHtmlProd(distPath, resourceName) {
|
|
9132
9136
|
const htmlPath = node_path.default.resolve(distPath);
|
|
9133
|
-
if (!node_fs.default.existsSync(htmlPath))
|
|
9137
|
+
if (!node_fs.default.existsSync(htmlPath)) {
|
|
9138
|
+
const label = resourceName ? `Resource "${resourceName}"` : "Resource";
|
|
9139
|
+
throw new Error(`${label}: built HTML not found at ${htmlPath}. Run "sunpeak build" to generate it.`);
|
|
9140
|
+
}
|
|
9134
9141
|
return node_fs.default.readFileSync(htmlPath, "utf8");
|
|
9135
9142
|
}
|
|
9136
9143
|
/**
|
|
9144
|
+
* Generate an inline script that shows a dev overlay with resource served timestamp
|
|
9145
|
+
* and tool call request timing.
|
|
9146
|
+
*
|
|
9147
|
+
* The resource timestamp is baked into the HTML at readResource time. Tool timing
|
|
9148
|
+
* arrives two ways:
|
|
9149
|
+
* 1. Baked-in `toolMs` from readResource time (works when the tool call precedes the
|
|
9150
|
+
* resource read, which is the case for Claude and the inspector's initial render).
|
|
9151
|
+
* 2. `_meta._sunpeak.requestTimeMs` on the tool result PostMessage (handles inspector
|
|
9152
|
+
* Re-run and hosts that pass `_meta` through to the resource iframe).
|
|
9153
|
+
*
|
|
9154
|
+
* NOTE: Keep in sync with bin/lib/dev-overlay.mjs (used by the standalone inspector).
|
|
9155
|
+
*
|
|
9156
|
+
* @param servedAt - Unix timestamp (ms) when the resource HTML was generated/served.
|
|
9157
|
+
* @param toolMs - Most recent tool call duration (ms), or null if no call yet.
|
|
9158
|
+
*/
|
|
9159
|
+
function getDevOverlayScript(servedAt, toolMs) {
|
|
9160
|
+
return `<script>
|
|
9161
|
+
(function(){
|
|
9162
|
+
var servedAt=${servedAt};
|
|
9163
|
+
var el=null,hidden=false,lastMs=${toolMs ?? "null"};
|
|
9164
|
+
function fmt(ts){var d=new Date(ts);var h=d.getHours(),m=d.getMinutes(),s=d.getSeconds();return (h<10?'0':'')+h+':'+(m<10?'0':'')+m+':'+(s<10?'0':'')+s}
|
|
9165
|
+
function make(){
|
|
9166
|
+
var existing=document.getElementById('__sunpeak-dev-timing');
|
|
9167
|
+
if(existing)return existing;
|
|
9168
|
+
var b=document.createElement('button');b.id='__sunpeak-dev-timing';
|
|
9169
|
+
b.style.cssText='position:fixed;bottom:8px;right:8px;z-index:2147483647;display:grid;grid-template-columns:auto auto;gap:0 6px;align-items:baseline;padding:5px 8px;border-radius:6px;border:1px solid rgba(128,128,128,0.25);background:rgba(0,0,0,0.75);backdrop-filter:blur(8px);color:#e5e5e5;font-size:11px;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;line-height:1.4;cursor:pointer;user-select:none;opacity:0.85;transition:opacity 150ms;';
|
|
9170
|
+
b.onmouseenter=function(){b.style.opacity='1'};
|
|
9171
|
+
b.onmouseleave=function(){b.style.opacity='0.85'};
|
|
9172
|
+
b.onclick=function(){hidden=!hidden;upd()};
|
|
9173
|
+
document.body.appendChild(b);return b;
|
|
9174
|
+
}
|
|
9175
|
+
function upd(){
|
|
9176
|
+
if(!el)el=make();
|
|
9177
|
+
if(hidden){el.title='Show dev info';el.innerHTML='<span style="grid-column:1/-1;font-size:9px;text-align:center">DEV</span>';return}
|
|
9178
|
+
var h='';
|
|
9179
|
+
h+='<span style="text-align:right;color:rgba(255,255,255,0.5);white-space:nowrap">Resource:</span><span style="white-space:nowrap">'+fmt(servedAt)+'</span>';
|
|
9180
|
+
if(lastMs!=null)h+='<span style="text-align:right;color:rgba(255,255,255,0.5);white-space:nowrap">Tool:</span><span style="white-space:nowrap">'+(lastMs%1===0?lastMs:lastMs.toFixed(1))+'ms</span>';
|
|
9181
|
+
el.title='Hide dev info';el.innerHTML=h;
|
|
9182
|
+
}
|
|
9183
|
+
upd();
|
|
9184
|
+
window.addEventListener('message',function(e){
|
|
9185
|
+
var d=e.data;if(!d||typeof d!=='object')return;
|
|
9186
|
+
if(d.method!=='ui/notifications/tool-result')return;
|
|
9187
|
+
var p=d.params;if(!p)return;
|
|
9188
|
+
var ms=p._meta&&p._meta._sunpeak&&p._meta._sunpeak.requestTimeMs;
|
|
9189
|
+
if(typeof ms==='number'){lastMs=ms;upd()}
|
|
9190
|
+
});
|
|
9191
|
+
})();
|
|
9192
|
+
<\/script>`;
|
|
9193
|
+
}
|
|
9194
|
+
/**
|
|
9137
9195
|
* Generate HTML that loads from Vite dev server with HMR.
|
|
9138
9196
|
* Used for direct connections (e.g. ChatGPT connecting to localhost).
|
|
9139
9197
|
*/
|
|
@@ -9176,6 +9234,7 @@ function getViteResourceHtml(srcPath) {
|
|
|
9176
9234
|
src: srcPath,
|
|
9177
9235
|
component: componentName
|
|
9178
9236
|
}).toString()}`}"><\/script>
|
|
9237
|
+
${isDevOverlayEnabled() ? getDevOverlayScript(Date.now(), lastToolTimingMs) : ""}
|
|
9179
9238
|
</body>
|
|
9180
9239
|
</html>`;
|
|
9181
9240
|
}
|
|
@@ -9187,7 +9246,9 @@ function getViteResourceHtml(srcPath) {
|
|
|
9187
9246
|
*/
|
|
9188
9247
|
function getResourceHtml(simulation, viteMode, prodBuild) {
|
|
9189
9248
|
if (viteMode && simulation.srcPath && !prodBuild) return getViteResourceHtml(simulation.srcPath);
|
|
9190
|
-
|
|
9249
|
+
let html = readResourceHtmlProd(simulation.distPath, simulation.name);
|
|
9250
|
+
if (isDevOverlayEnabled()) html = html.replace("</body>", `${getDevOverlayScript(Date.now(), lastToolTimingMs)}\n</body>`);
|
|
9251
|
+
return html;
|
|
9191
9252
|
}
|
|
9192
9253
|
/**
|
|
9193
9254
|
* Inject localhost Vite dev server URLs into CSP metadata.
|
|
@@ -9308,14 +9369,29 @@ function createAppServer(config, simulations, viteMode) {
|
|
|
9308
9369
|
const realHandler = simulation.handler;
|
|
9309
9370
|
if (realHandler && (config.prodTools || !hasMockResult)) {
|
|
9310
9371
|
console.log(`[MCP] CallTool: ${tool.name}${argsStr} → live handler`);
|
|
9372
|
+
const startTime = performance.now();
|
|
9311
9373
|
try {
|
|
9312
9374
|
const result = await realHandler(args, extra);
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9375
|
+
const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
|
|
9376
|
+
lastToolTimingMs = durationMs;
|
|
9377
|
+
if (typeof result === "string") return {
|
|
9378
|
+
content: [{
|
|
9379
|
+
type: "text",
|
|
9380
|
+
text: result
|
|
9381
|
+
}],
|
|
9382
|
+
_meta: { _sunpeak: { requestTimeMs: durationMs } }
|
|
9383
|
+
};
|
|
9384
|
+
const typed = result;
|
|
9385
|
+
return {
|
|
9386
|
+
...typed,
|
|
9387
|
+
_meta: {
|
|
9388
|
+
...typed._meta,
|
|
9389
|
+
_sunpeak: { requestTimeMs: durationMs }
|
|
9390
|
+
}
|
|
9391
|
+
};
|
|
9318
9392
|
} catch (error) {
|
|
9393
|
+
const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
|
|
9394
|
+
lastToolTimingMs = durationMs;
|
|
9319
9395
|
const msg = error instanceof Error ? error.message : String(error);
|
|
9320
9396
|
console.error(`[MCP] CallTool error (${tool.name}): ${msg}`);
|
|
9321
9397
|
return {
|
|
@@ -9323,7 +9399,8 @@ function createAppServer(config, simulations, viteMode) {
|
|
|
9323
9399
|
type: "text",
|
|
9324
9400
|
text: `Error: ${msg}`
|
|
9325
9401
|
}],
|
|
9326
|
-
isError: true
|
|
9402
|
+
isError: true,
|
|
9403
|
+
_meta: { _sunpeak: { requestTimeMs: durationMs } }
|
|
9327
9404
|
};
|
|
9328
9405
|
}
|
|
9329
9406
|
}
|
|
@@ -9565,7 +9642,8 @@ function runMCPServer(config) {
|
|
|
9565
9642
|
viteServer.ws.handleUpgrade(request, socket, head);
|
|
9566
9643
|
});
|
|
9567
9644
|
httpServer.on("clientError", (err, socket) => {
|
|
9568
|
-
if (err.code
|
|
9645
|
+
if (err.code === "ECONNRESET") {} else if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] === 22) console.error("Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000");
|
|
9646
|
+
else console.error("HTTP client error", err);
|
|
9569
9647
|
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
9570
9648
|
});
|
|
9571
9649
|
let resolveReady;
|
|
@@ -9575,14 +9653,16 @@ function runMCPServer(config) {
|
|
|
9575
9653
|
const onListening = () => {
|
|
9576
9654
|
const actualPort = httpServer.address().port;
|
|
9577
9655
|
localDevServerUrl = `http://localhost:${actualPort}`;
|
|
9578
|
-
console.log(`Sunpeak MCP server listening on http://localhost:${actualPort}`);
|
|
9656
|
+
if (actualPort !== requestedPort) console.log(`Sunpeak MCP server listening on http://localhost:${actualPort} (port ${requestedPort} was in use)`);
|
|
9657
|
+
else console.log(`Sunpeak MCP server listening on http://localhost:${actualPort}`);
|
|
9579
9658
|
console.log(` MCP endpoint: http://localhost:${actualPort}${MCP_PATH$1}`);
|
|
9580
9659
|
if (viteMode) console.log(` Vite HMR: enabled (source files served with hot reload)`);
|
|
9581
9660
|
resolveReady();
|
|
9582
9661
|
};
|
|
9662
|
+
const requestedPort = port;
|
|
9583
9663
|
httpServer.on("error", (err) => {
|
|
9584
9664
|
if (err.code === "EADDRINUSE") {
|
|
9585
|
-
console.warn(`Port ${
|
|
9665
|
+
console.warn(`Port ${requestedPort} is in use, trying another port...`);
|
|
9586
9666
|
httpServer.listen(0);
|
|
9587
9667
|
} else throw err;
|
|
9588
9668
|
});
|
|
@@ -10135,19 +10215,22 @@ function startProductionHttpServer(config, portOrOptions) {
|
|
|
10135
10215
|
}
|
|
10136
10216
|
});
|
|
10137
10217
|
httpServer.on("clientError", (err, socket) => {
|
|
10138
|
-
log("error", "HTTP
|
|
10218
|
+
if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] === 22) log("error", "Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000");
|
|
10219
|
+
else log("error", "HTTP client error", { error: err.message });
|
|
10139
10220
|
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
10140
10221
|
});
|
|
10141
10222
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
10223
|
+
const requestedPort = port;
|
|
10142
10224
|
const onListening = () => {
|
|
10143
10225
|
const addr = httpServer.address();
|
|
10144
|
-
log("info", `Server listening on http://${displayHost}:${addr.port}`);
|
|
10226
|
+
if (addr.port !== requestedPort) log("info", `Server listening on http://${displayHost}:${addr.port} (port ${requestedPort} was in use)`);
|
|
10227
|
+
else log("info", `Server listening on http://${displayHost}:${addr.port}`);
|
|
10145
10228
|
log("info", `MCP endpoint: http://${displayHost}:${addr.port}${MCP_PATH}`);
|
|
10146
10229
|
log("info", `Health check: http://${displayHost}:${addr.port}/health`);
|
|
10147
10230
|
};
|
|
10148
10231
|
httpServer.on("error", (err) => {
|
|
10149
10232
|
if (err.code === "EADDRINUSE") {
|
|
10150
|
-
log("warn", `Port ${
|
|
10233
|
+
log("warn", `Port ${requestedPort} is in use, trying another port...`);
|
|
10151
10234
|
httpServer.listen(0, host);
|
|
10152
10235
|
} else throw err;
|
|
10153
10236
|
});
|