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
package/dist/mcp/index.js CHANGED
@@ -9105,6 +9105,10 @@ function injectDefaultDomain(meta, clientName, serverUrl) {
9105
9105
  //#region src/mcp/server.ts
9106
9106
  var localDevServerUrl = "http://localhost:8000";
9107
9107
  var localHmrWsUrl = "ws://localhost:24678";
9108
+ var lastToolTimingMs = null;
9109
+ function isDevOverlayEnabled() {
9110
+ return process.env.SUNPEAK_DEV_OVERLAY !== "false";
9111
+ }
9108
9112
  /**
9109
9113
  * Detect whether this request needs pre-built HTML (no Vite HMR).
9110
9114
  *
@@ -9124,12 +9128,66 @@ function needsProdBuild(headers) {
9124
9128
  * Read pre-built resource HTML (production mode).
9125
9129
  * The HTML file is generated by `sunpeak build` with JS and CSS inlined.
9126
9130
  */
9127
- function readResourceHtmlProd(distPath) {
9131
+ function readResourceHtmlProd(distPath, resourceName) {
9128
9132
  const htmlPath = path.resolve(distPath);
9129
- if (!fs.existsSync(htmlPath)) throw new Error(`Resource HTML file not found at ${htmlPath}. Run "sunpeak build" to generate the built app.`);
9133
+ if (!fs.existsSync(htmlPath)) {
9134
+ const label = resourceName ? `Resource "${resourceName}"` : "Resource";
9135
+ throw new Error(`${label}: built HTML not found at ${htmlPath}. Run "sunpeak build" to generate it.`);
9136
+ }
9130
9137
  return fs.readFileSync(htmlPath, "utf8");
9131
9138
  }
9132
9139
  /**
9140
+ * Generate an inline script that shows a dev overlay with resource served timestamp
9141
+ * and tool call request timing.
9142
+ *
9143
+ * The resource timestamp is baked into the HTML at readResource time. Tool timing
9144
+ * arrives two ways:
9145
+ * 1. Baked-in `toolMs` from readResource time (works when the tool call precedes the
9146
+ * resource read, which is the case for Claude and the inspector's initial render).
9147
+ * 2. `_meta._sunpeak.requestTimeMs` on the tool result PostMessage (handles inspector
9148
+ * Re-run and hosts that pass `_meta` through to the resource iframe).
9149
+ *
9150
+ * NOTE: Keep in sync with bin/lib/dev-overlay.mjs (used by the standalone inspector).
9151
+ *
9152
+ * @param servedAt - Unix timestamp (ms) when the resource HTML was generated/served.
9153
+ * @param toolMs - Most recent tool call duration (ms), or null if no call yet.
9154
+ */
9155
+ function getDevOverlayScript(servedAt, toolMs) {
9156
+ return `<script>
9157
+ (function(){
9158
+ var servedAt=${servedAt};
9159
+ var el=null,hidden=false,lastMs=${toolMs ?? "null"};
9160
+ 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}
9161
+ function make(){
9162
+ var existing=document.getElementById('__sunpeak-dev-timing');
9163
+ if(existing)return existing;
9164
+ var b=document.createElement('button');b.id='__sunpeak-dev-timing';
9165
+ 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;';
9166
+ b.onmouseenter=function(){b.style.opacity='1'};
9167
+ b.onmouseleave=function(){b.style.opacity='0.85'};
9168
+ b.onclick=function(){hidden=!hidden;upd()};
9169
+ document.body.appendChild(b);return b;
9170
+ }
9171
+ function upd(){
9172
+ if(!el)el=make();
9173
+ if(hidden){el.title='Show dev info';el.innerHTML='<span style="grid-column:1/-1;font-size:9px;text-align:center">DEV</span>';return}
9174
+ var h='';
9175
+ 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>';
9176
+ 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>';
9177
+ el.title='Hide dev info';el.innerHTML=h;
9178
+ }
9179
+ upd();
9180
+ window.addEventListener('message',function(e){
9181
+ var d=e.data;if(!d||typeof d!=='object')return;
9182
+ if(d.method!=='ui/notifications/tool-result')return;
9183
+ var p=d.params;if(!p)return;
9184
+ var ms=p._meta&&p._meta._sunpeak&&p._meta._sunpeak.requestTimeMs;
9185
+ if(typeof ms==='number'){lastMs=ms;upd()}
9186
+ });
9187
+ })();
9188
+ <\/script>`;
9189
+ }
9190
+ /**
9133
9191
  * Generate HTML that loads from Vite dev server with HMR.
9134
9192
  * Used for direct connections (e.g. ChatGPT connecting to localhost).
9135
9193
  */
@@ -9172,6 +9230,7 @@ function getViteResourceHtml(srcPath) {
9172
9230
  src: srcPath,
9173
9231
  component: componentName
9174
9232
  }).toString()}`}"><\/script>
9233
+ ${isDevOverlayEnabled() ? getDevOverlayScript(Date.now(), lastToolTimingMs) : ""}
9175
9234
  </body>
9176
9235
  </html>`;
9177
9236
  }
@@ -9183,7 +9242,9 @@ function getViteResourceHtml(srcPath) {
9183
9242
  */
9184
9243
  function getResourceHtml(simulation, viteMode, prodBuild) {
9185
9244
  if (viteMode && simulation.srcPath && !prodBuild) return getViteResourceHtml(simulation.srcPath);
9186
- return readResourceHtmlProd(simulation.distPath);
9245
+ let html = readResourceHtmlProd(simulation.distPath, simulation.name);
9246
+ if (isDevOverlayEnabled()) html = html.replace("</body>", `${getDevOverlayScript(Date.now(), lastToolTimingMs)}\n</body>`);
9247
+ return html;
9187
9248
  }
9188
9249
  /**
9189
9250
  * Inject localhost Vite dev server URLs into CSP metadata.
@@ -9304,14 +9365,29 @@ function createAppServer(config, simulations, viteMode) {
9304
9365
  const realHandler = simulation.handler;
9305
9366
  if (realHandler && (config.prodTools || !hasMockResult)) {
9306
9367
  console.log(`[MCP] CallTool: ${tool.name}${argsStr} → live handler`);
9368
+ const startTime = performance.now();
9307
9369
  try {
9308
9370
  const result = await realHandler(args, extra);
9309
- if (typeof result === "string") return { content: [{
9310
- type: "text",
9311
- text: result
9312
- }] };
9313
- return result;
9371
+ const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
9372
+ lastToolTimingMs = durationMs;
9373
+ if (typeof result === "string") return {
9374
+ content: [{
9375
+ type: "text",
9376
+ text: result
9377
+ }],
9378
+ _meta: { _sunpeak: { requestTimeMs: durationMs } }
9379
+ };
9380
+ const typed = result;
9381
+ return {
9382
+ ...typed,
9383
+ _meta: {
9384
+ ...typed._meta,
9385
+ _sunpeak: { requestTimeMs: durationMs }
9386
+ }
9387
+ };
9314
9388
  } catch (error) {
9389
+ const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
9390
+ lastToolTimingMs = durationMs;
9315
9391
  const msg = error instanceof Error ? error.message : String(error);
9316
9392
  console.error(`[MCP] CallTool error (${tool.name}): ${msg}`);
9317
9393
  return {
@@ -9319,7 +9395,8 @@ function createAppServer(config, simulations, viteMode) {
9319
9395
  type: "text",
9320
9396
  text: `Error: ${msg}`
9321
9397
  }],
9322
- isError: true
9398
+ isError: true,
9399
+ _meta: { _sunpeak: { requestTimeMs: durationMs } }
9323
9400
  };
9324
9401
  }
9325
9402
  }
@@ -9561,7 +9638,8 @@ function runMCPServer(config) {
9561
9638
  viteServer.ws.handleUpgrade(request, socket, head);
9562
9639
  });
9563
9640
  httpServer.on("clientError", (err, socket) => {
9564
- if (err.code !== "ECONNRESET") console.error("HTTP client error", err);
9641
+ 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");
9642
+ else console.error("HTTP client error", err);
9565
9643
  socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
9566
9644
  });
9567
9645
  let resolveReady;
@@ -9571,14 +9649,16 @@ function runMCPServer(config) {
9571
9649
  const onListening = () => {
9572
9650
  const actualPort = httpServer.address().port;
9573
9651
  localDevServerUrl = `http://localhost:${actualPort}`;
9574
- console.log(`Sunpeak MCP server listening on http://localhost:${actualPort}`);
9652
+ if (actualPort !== requestedPort) console.log(`Sunpeak MCP server listening on http://localhost:${actualPort} (port ${requestedPort} was in use)`);
9653
+ else console.log(`Sunpeak MCP server listening on http://localhost:${actualPort}`);
9575
9654
  console.log(` MCP endpoint: http://localhost:${actualPort}${MCP_PATH$1}`);
9576
9655
  if (viteMode) console.log(` Vite HMR: enabled (source files served with hot reload)`);
9577
9656
  resolveReady();
9578
9657
  };
9658
+ const requestedPort = port;
9579
9659
  httpServer.on("error", (err) => {
9580
9660
  if (err.code === "EADDRINUSE") {
9581
- console.warn(`Port ${port} in use, finding an available port...`);
9661
+ console.warn(`Port ${requestedPort} is in use, trying another port...`);
9582
9662
  httpServer.listen(0);
9583
9663
  } else throw err;
9584
9664
  });
@@ -10131,19 +10211,22 @@ function startProductionHttpServer(config, portOrOptions) {
10131
10211
  }
10132
10212
  });
10133
10213
  httpServer.on("clientError", (err, socket) => {
10134
- log("error", "HTTP client error", { error: err.message });
10214
+ 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");
10215
+ else log("error", "HTTP client error", { error: err.message });
10135
10216
  socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
10136
10217
  });
10137
10218
  const displayHost = host === "0.0.0.0" ? "localhost" : host;
10219
+ const requestedPort = port;
10138
10220
  const onListening = () => {
10139
10221
  const addr = httpServer.address();
10140
- log("info", `Server listening on http://${displayHost}:${addr.port}`);
10222
+ if (addr.port !== requestedPort) log("info", `Server listening on http://${displayHost}:${addr.port} (port ${requestedPort} was in use)`);
10223
+ else log("info", `Server listening on http://${displayHost}:${addr.port}`);
10141
10224
  log("info", `MCP endpoint: http://${displayHost}:${addr.port}${MCP_PATH}`);
10142
10225
  log("info", `Health check: http://${displayHost}:${addr.port}/health`);
10143
10226
  };
10144
10227
  httpServer.on("error", (err) => {
10145
10228
  if (err.code === "EADDRINUSE") {
10146
- log("warn", `Port ${port} in use, finding an available port...`);
10229
+ log("warn", `Port ${requestedPort} is in use, trying another port...`);
10147
10230
  httpServer.listen(0, host);
10148
10231
  } else throw err;
10149
10232
  });