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.
Files changed (63) hide show
  1. package/bin/commands/dev.mjs +9 -5
  2. package/bin/commands/inspect.mjs +13 -1
  3. package/bin/commands/new.mjs +5 -0
  4. package/bin/lib/dev-overlay.mjs +50 -0
  5. package/bin/lib/live/live-config.d.mts +3 -0
  6. package/bin/lib/live/live-config.mjs +3 -1
  7. package/bin/lib/sandbox-server.mjs +19 -0
  8. package/dist/chatgpt/index.cjs +2 -3
  9. package/dist/chatgpt/index.js +2 -3
  10. package/dist/claude/index.cjs +1 -2
  11. package/dist/claude/index.js +1 -2
  12. package/dist/host/chatgpt/index.cjs +0 -1
  13. package/dist/host/chatgpt/index.cjs.map +1 -1
  14. package/dist/host/chatgpt/index.js +0 -1
  15. package/dist/host/chatgpt/index.js.map +1 -1
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/inspector/index.cjs +2 -3
  19. package/dist/inspector/index.js +2 -3
  20. package/dist/inspector/inspector-url.d.ts +13 -0
  21. package/dist/inspector/use-inspector-state.d.ts +2 -0
  22. package/dist/{inspector-DRD_Q66E.cjs → inspector-CTMccsz9.cjs} +71 -22
  23. package/dist/inspector-CTMccsz9.cjs.map +1 -0
  24. package/dist/{inspector-CjSoXm6N.js → inspector-DkS75JCk.js} +71 -22
  25. package/dist/inspector-DkS75JCk.js.map +1 -0
  26. package/dist/{inspector-url-7qhtJwY6.cjs → inspector-url-C3LTKgXt.cjs} +3 -1
  27. package/dist/inspector-url-C3LTKgXt.cjs.map +1 -0
  28. package/dist/{inspector-url-DuEFmxLP.js → inspector-url-CyQcuBI9.js} +3 -1
  29. package/dist/inspector-url-CyQcuBI9.js.map +1 -0
  30. package/dist/mcp/index.cjs +98 -15
  31. package/dist/mcp/index.cjs.map +1 -1
  32. package/dist/mcp/index.js +98 -15
  33. package/dist/mcp/index.js.map +1 -1
  34. package/dist/style.css +4 -0
  35. package/package.json +5 -5
  36. package/template/dist/albums/albums.html +1 -1
  37. package/template/dist/albums/albums.json +1 -1
  38. package/template/dist/carousel/carousel.html +1 -1
  39. package/template/dist/carousel/carousel.json +1 -1
  40. package/template/dist/map/map.html +1 -1
  41. package/template/dist/map/map.json +1 -1
  42. package/template/dist/review/review.html +1 -1
  43. package/template/dist/review/review.json +1 -1
  44. package/template/node_modules/.bin/vite +2 -2
  45. package/template/node_modules/.bin/vitest +2 -2
  46. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  47. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js +9 -6
  48. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js.map +1 -1
  49. package/template/node_modules/.vite-mcp/deps/_metadata.json +21 -21
  50. package/template/node_modules/.vite-mcp/deps/vitest.js +366 -128
  51. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  52. package/template/package.json +2 -2
  53. package/template/tests/e2e/albums.spec.ts +1 -1
  54. package/template/tests/e2e/carousel.spec.ts +1 -1
  55. package/template/tests/e2e/dev-overlay.spec.ts +118 -0
  56. package/template/tests/e2e/helpers.ts +13 -0
  57. package/template/tests/e2e/map.spec.ts +1 -1
  58. package/template/tests/e2e/review.spec.ts +1 -1
  59. package/template/tests/live/playwright.config.ts +1 -1
  60. package/dist/inspector-CjSoXm6N.js.map +0 -1
  61. package/dist/inspector-DRD_Q66E.cjs.map +0 -1
  62. package/dist/inspector-url-7qhtJwY6.cjs.map +0 -1
  63. 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-7qhtJwY6.cjs.map
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-DuEFmxLP.js.map
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"}
@@ -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)) throw new Error(`Resource HTML file not found at ${htmlPath}. Run "sunpeak build" to generate the built app.`);
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
- return readResourceHtmlProd(simulation.distPath);
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
- if (typeof result === "string") return { content: [{
9314
- type: "text",
9315
- text: result
9316
- }] };
9317
- return result;
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 !== "ECONNRESET") console.error("HTTP client error", err);
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 ${port} in use, finding an available 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 client error", { error: err.message });
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 ${port} in use, finding an available 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
  });