sunpeak 0.20.23 → 0.20.29

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 (62) hide show
  1. package/README.md +29 -4
  2. package/bin/commands/inspect.mjs +18 -0
  3. package/dist/chatgpt/index.cjs +1 -1
  4. package/dist/chatgpt/index.js +1 -1
  5. package/dist/claude/index.cjs +1 -1
  6. package/dist/claude/index.js +1 -1
  7. package/dist/embed.css +2 -0
  8. package/dist/host/chatgpt/index.cjs +1 -1
  9. package/dist/host/chatgpt/index.js +1 -1
  10. package/dist/index.cjs +3 -3
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.js +3 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/inspector/app-flatten.d.ts +7 -0
  15. package/dist/inspector/app-types.d.ts +83 -0
  16. package/dist/inspector/iframe-resource.d.ts +19 -1
  17. package/dist/inspector/index.cjs +3 -1
  18. package/dist/inspector/index.cjs.map +1 -1
  19. package/dist/inspector/index.d.ts +2 -0
  20. package/dist/inspector/index.js +3 -2
  21. package/dist/inspector/index.js.map +1 -1
  22. package/dist/inspector/inline-helper-script.d.ts +22 -0
  23. package/dist/inspector/inspector-api.d.ts +3 -0
  24. package/dist/inspector/inspector.d.ts +21 -1
  25. package/dist/inspector/simple-sidebar.d.ts +13 -1
  26. package/dist/inspector/use-inspector-state.d.ts +1 -0
  27. package/dist/inspector/use-mcp-connection.d.ts +1 -1
  28. package/dist/{inspector-CJNvLoHo.js → inspector-B1355aXh.js} +393 -49
  29. package/dist/inspector-B1355aXh.js.map +1 -0
  30. package/dist/{inspector-DtEighD9.cjs → inspector-CJPO4f12.cjs} +397 -47
  31. package/dist/inspector-CJPO4f12.cjs.map +1 -0
  32. package/dist/mcp/index.cjs +1 -1
  33. package/dist/mcp/index.cjs.map +1 -1
  34. package/dist/mcp/index.js +1 -1
  35. package/dist/mcp/index.js.map +1 -1
  36. package/dist/sandbox-proxy.html +173 -0
  37. package/dist/style.css +9 -1
  38. package/dist/types/simulation.d.ts +3 -8
  39. package/dist/{use-app-cSBm5Pjl.js → use-app-C2pGHlnF.js} +2 -2
  40. package/dist/{use-app-cSBm5Pjl.js.map → use-app-C2pGHlnF.js.map} +1 -1
  41. package/dist/{use-app-CxtSfkSF.cjs → use-app-DIWh7-3f.cjs} +2 -2
  42. package/dist/{use-app-CxtSfkSF.cjs.map → use-app-DIWh7-3f.cjs.map} +1 -1
  43. package/package.json +6 -3
  44. package/template/dist/albums/albums.html +1 -1
  45. package/template/dist/albums/albums.json +1 -1
  46. package/template/dist/carousel/carousel.html +1 -1
  47. package/template/dist/carousel/carousel.json +1 -1
  48. package/template/dist/map/map.html +1 -1
  49. package/template/dist/map/map.json +1 -1
  50. package/template/dist/review/review.html +1 -1
  51. package/template/dist/review/review.json +1 -1
  52. package/template/node_modules/.vite/deps/_metadata.json +3 -3
  53. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +1 -1
  54. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
  55. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +1 -1
  56. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
  57. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +1 -1
  58. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
  59. package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
  60. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-claude-linux.png +0 -0
  61. package/dist/inspector-CJNvLoHo.js.map +0 -1
  62. package/dist/inspector-DtEighD9.cjs.map +0 -1
@@ -4224,7 +4224,7 @@ registerHostShell({
4224
4224
  }`
4225
4225
  });
4226
4226
  //#endregion
4227
- //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.1_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__react-_3f5c1a6a4033ae62a19a2bb834f0e050/node_modules/@modelcontextprotocol/ext-apps/dist/src/app-bridge.js
4227
+ //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.2_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__react-_f5b843da9146ebea748e10ad8dfce46a/node_modules/@modelcontextprotocol/ext-apps/dist/src/app-bridge.js
4228
4228
  ((X) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(X, { get: (Y, Z) => (typeof require < "u" ? require : Y)[Z] }) : X)(function(X) {
4229
4229
  if (typeof require < "u") return require.apply(this, arguments);
4230
4230
  throw Error("Dynamic require of \"" + X + "\" is not supported");
@@ -5546,6 +5546,145 @@ iframe { border: none; width: 100%; height: 100%; display: block; }
5546
5546
  </body>
5547
5547
  </html>`;
5548
5548
  }
5549
+ var SUNPEAK_INLINE_HELPER_SCRIPT = `
5550
+ (function() {
5551
+ if (window.sunpeak) return; // already installed (e.g. parent hot-reload)
5552
+ // Opt-out: resource HTML using the real MCP Apps SDK can suppress the
5553
+ // helper to avoid sending two ui/initialize requests. Manual lowercase
5554
+ // comparison so content="off" / "OFF" / "Off" all match (the Selectors
5555
+ // L4 case-insensitive flag isn't supported in all test environments).
5556
+ try {
5557
+ var metas = document.querySelectorAll('meta[name="sunpeak-helper"]');
5558
+ for (var mi = 0; mi < metas.length; mi++) {
5559
+ var contentAttr = metas[mi].getAttribute('content');
5560
+ if (contentAttr && contentAttr.toLowerCase() === 'off') return;
5561
+ }
5562
+ } catch (e) { /* document not ready or querySelector missing */ }
5563
+
5564
+ var listeners = {
5565
+ toolInput: [],
5566
+ toolInputPartial: [],
5567
+ toolResult: [],
5568
+ toolCancelled: [],
5569
+ hostContext: []
5570
+ };
5571
+ var last = {
5572
+ toolInput: undefined,
5573
+ toolInputPartial: undefined,
5574
+ toolResult: undefined,
5575
+ toolCancelled: undefined,
5576
+ hostContext: undefined
5577
+ };
5578
+ function dispatch(channel, value) {
5579
+ last[channel] = value;
5580
+ // Iterate over a SNAPSHOT — callbacks that unsubscribe themselves
5581
+ // mutate the live list, which would skip subsequent callbacks under a
5582
+ // for-by-index loop on the original array.
5583
+ var list = listeners[channel].slice();
5584
+ for (var i = 0; i < list.length; i++) {
5585
+ try { list[i](value); } catch (e) { console.error('[sunpeak] callback error:', e); }
5586
+ }
5587
+ }
5588
+ // Map channel names to the public method name for error messages.
5589
+ var channelMethodName = {
5590
+ toolInput: 'onToolInput',
5591
+ toolInputPartial: 'onToolInputPartial',
5592
+ toolResult: 'onToolResult',
5593
+ toolCancelled: 'onToolCancelled',
5594
+ hostContext: 'onHostContextChange'
5595
+ };
5596
+ function subscribe(channel) {
5597
+ return function(cb) {
5598
+ if (typeof cb !== 'function') {
5599
+ throw new TypeError('window.sunpeak.' + channelMethodName[channel] + ' expects a function');
5600
+ }
5601
+ listeners[channel].push(cb);
5602
+ if (last[channel] !== undefined) {
5603
+ try { cb(last[channel]); } catch (e) { console.error('[sunpeak] callback error:', e); }
5604
+ }
5605
+ return function unsubscribe() {
5606
+ var idx = listeners[channel].indexOf(cb);
5607
+ if (idx >= 0) listeners[channel].splice(idx, 1);
5608
+ };
5609
+ };
5610
+ }
5611
+
5612
+ window.sunpeak = {
5613
+ onToolInput: subscribe('toolInput'),
5614
+ onToolInputPartial: subscribe('toolInputPartial'),
5615
+ onToolResult: subscribe('toolResult'),
5616
+ onToolCancelled: subscribe('toolCancelled'),
5617
+ onHostContextChange: subscribe('hostContext')
5618
+ };
5619
+
5620
+ var nextId = 1;
5621
+ var pending = {};
5622
+
5623
+ function sendRequest(method, params) {
5624
+ var id = nextId++;
5625
+ return new Promise(function(resolve, reject) {
5626
+ pending[id] = { resolve: resolve, reject: reject };
5627
+ try {
5628
+ window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, '*');
5629
+ } catch (e) {
5630
+ delete pending[id];
5631
+ reject(e);
5632
+ }
5633
+ });
5634
+ }
5635
+ function sendNotification(method, params) {
5636
+ try {
5637
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params || {} }, '*');
5638
+ } catch (e) { /* parent detached */ }
5639
+ }
5640
+
5641
+ window.addEventListener('message', function(ev) {
5642
+ // Only trust messages from the actual parent (the sandbox proxy).
5643
+ // Without this guard, a sibling iframe in the same browsing context or
5644
+ // a browser extension content script could forge JSON-RPC notifications
5645
+ // and drive the embedder's onToolResult/onToolInput callbacks with
5646
+ // attacker-controlled data.
5647
+ if (ev.source !== window.parent) return;
5648
+ var msg = ev.data;
5649
+ if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;
5650
+ if (typeof msg.id !== 'undefined' && pending[msg.id]) {
5651
+ var p = pending[msg.id];
5652
+ delete pending[msg.id];
5653
+ if (msg.error) p.reject(new Error((msg.error && msg.error.message) || 'request error'));
5654
+ else p.resolve(msg.result);
5655
+ return;
5656
+ }
5657
+ if (msg.method === 'ui/notifications/tool-input') {
5658
+ dispatch('toolInput', (msg.params && msg.params.arguments) || {});
5659
+ } else if (msg.method === 'ui/notifications/tool-input-partial') {
5660
+ dispatch('toolInputPartial', (msg.params && msg.params.arguments) || {});
5661
+ } else if (msg.method === 'ui/notifications/tool-result') {
5662
+ // tool-result params are the CallToolResult directly per the spec.
5663
+ dispatch('toolResult', msg.params || {});
5664
+ } else if (msg.method === 'ui/notifications/tool-cancelled') {
5665
+ // Clear cached input / partial / result so a late onToolResult
5666
+ // subscriber doesn't get the cancelled-then-stale value replayed.
5667
+ // hostContext stays — the environment hasn't changed.
5668
+ last.toolInput = undefined;
5669
+ last.toolInputPartial = undefined;
5670
+ last.toolResult = undefined;
5671
+ dispatch('toolCancelled', msg.params || {});
5672
+ } else if (msg.method === 'ui/notifications/host-context-changed') {
5673
+ dispatch('hostContext', msg.params || {});
5674
+ }
5675
+ });
5676
+
5677
+ sendRequest('ui/initialize', {
5678
+ appInfo: { name: 'sunpeak-inline-helper', version: '1.0.0' },
5679
+ appCapabilities: {},
5680
+ protocolVersion: '2026-01-26'
5681
+ }).then(function() {
5682
+ sendNotification('ui/notifications/initialized');
5683
+ }).catch(function(err) {
5684
+ console.warn('[sunpeak] ui/initialize failed:', err && err.message);
5685
+ });
5686
+ })();
5687
+ `.trim();
5549
5688
  //#endregion
5550
5689
  //#region src/inspector/iframe-resource.tsx
5551
5690
  /**
@@ -5684,6 +5823,23 @@ requestAnimationFrame(function(){
5684
5823
  e.source.postMessage({jsonrpc:"2.0",method:"sunpeak/fence-ack",params:{fenceId:fid}},"*");
5685
5824
  });}});`;
5686
5825
  /**
5826
+ * Inject the paint-fence responder and (optionally) a platform runtime script
5827
+ * into a user-provided HTML document. Used for the `html` prop mode where the
5828
+ * embedder hands us a complete document from `mcpClient.readResource(...)` and
5829
+ * we need to splice in the same infrastructure that the `scriptSrc` wrapper
5830
+ * provides. The injection is placed before `</head>` when present, falling
5831
+ * back to the start of `<body>` or the document start.
5832
+ */
5833
+ function injectInfraScripts(html, platformScript) {
5834
+ const fenceTag = `<script data-sunpeak-fence>${PAINT_FENCE_SCRIPT}<\/script>`;
5835
+ const injection = `${platformScript ? `<script>${platformScript}<\/script>` : ""}${fenceTag}${`<script data-sunpeak-helper>${SUNPEAK_INLINE_HELPER_SCRIPT}<\/script>`}`;
5836
+ const headMatch = html.match(/<\/head\s*>/i);
5837
+ if (headMatch) return html.replace(headMatch[0], `${injection}${headMatch[0]}`);
5838
+ const bodyMatch = html.match(/<body([^>]*)>/i);
5839
+ if (bodyMatch) return html.replace(bodyMatch[0], `${bodyMatch[0]}${injection}`);
5840
+ return injection + html;
5841
+ }
5842
+ /**
5687
5843
  * Generates HTML wrapper for a script URL.
5688
5844
  * The MCP Apps SDK in the loaded script handles communication via PostMessageTransport.
5689
5845
  */
@@ -5747,7 +5903,7 @@ function buildIframeAllow(permissions) {
5747
5903
  * connects via PostMessageTransport to window.parent. The parent side uses
5748
5904
  * McpAppHost (wrapping AppBridge) to communicate.
5749
5905
  */
5750
- function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputPartial, toolResult, hostOptions, csp, permissions, prefersBorder, className, style, onDisplayModeReady, debugInjectState, injectOpenAIRuntime, sandboxUrl }) {
5906
+ function IframeResource({ src, scriptSrc, html, hostContext, toolInput, toolInputPartial, toolResult, hostOptions, csp, permissions, prefersBorder, className, style, onDisplayModeReady, debugInjectState, injectOpenAIRuntime, sandboxUrl }) {
5751
5907
  const iframeRef = (0, react.useRef)(null);
5752
5908
  const hostRef = (0, react.useRef)(null);
5753
5909
  const resourceUrl = src ?? scriptSrc;
@@ -5755,6 +5911,8 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5755
5911
  srcRef.current = src;
5756
5912
  const scriptSrcRef = (0, react.useRef)(scriptSrc);
5757
5913
  scriptSrcRef.current = scriptSrc;
5914
+ const htmlRef = (0, react.useRef)(html);
5915
+ htmlRef.current = html;
5758
5916
  const cspRef = (0, react.useRef)(csp);
5759
5917
  cspRef.current = csp;
5760
5918
  const hostContextRef = (0, react.useRef)(hostContext);
@@ -5804,9 +5962,20 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5804
5962
  onSandboxReady: () => {
5805
5963
  const currentSrc = srcRef.current;
5806
5964
  const currentScriptSrc = scriptSrcRef.current;
5965
+ const currentHtml = htmlRef.current;
5807
5966
  const currentHost = hostRef.current;
5808
5967
  if (!currentHost) return;
5809
- if (currentScriptSrc) {
5968
+ if (currentHtml) {
5969
+ const finalHtml = injectInfraScripts(currentHtml, injectOpenAIRuntimeRef.current ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
5970
+ currentHost.sendSandboxResourceReady({
5971
+ html: finalHtml,
5972
+ sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
5973
+ });
5974
+ if (iframeRef.current) {
5975
+ iframeRef.current.style.opacity = "1";
5976
+ iframeRef.current.style.transition = "opacity 100ms";
5977
+ }
5978
+ } else if (currentScriptSrc) {
5810
5979
  const absoluteScriptSrc = currentScriptSrc.startsWith("/") ? `${window.location.origin}${currentScriptSrc}` : currentScriptSrc;
5811
5980
  const cspPolicy = generateCSP(cspRef.current, absoluteScriptSrc);
5812
5981
  const appHtml = generateScriptHtml(absoluteScriptSrc, hostContextRef.current?.theme ?? "dark", cspPolicy, injectOpenAIRuntimeRef.current ? MOCK_OPENAI_RUNTIME_SCRIPT : void 0);
@@ -5877,7 +6046,17 @@ function IframeResource({ src, scriptSrc, hostContext, toolInput, toolInputParti
5877
6046
  const borderStyle = prefersBorder ? { border: "1px solid var(--color-border-primary, #e5e7eb)" } : { border: "none" };
5878
6047
  const sandboxSrc = (0, react.useMemo)(() => {
5879
6048
  if (!sandboxUrl) return void 0;
5880
- const url = new URL("/proxy", sandboxUrl);
6049
+ let url;
6050
+ try {
6051
+ const parsed = new URL(sandboxUrl);
6052
+ url = parsed.pathname && parsed.pathname !== "/" ? parsed : new URL("/proxy", sandboxUrl);
6053
+ } catch {
6054
+ return;
6055
+ }
6056
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
6057
+ console.warn("[IframeResource] Ignoring non-http(s) sandboxUrl:", sandboxUrl);
6058
+ return;
6059
+ }
5881
6060
  if (injectOpenAIRuntime) url.searchParams.set("platform", "chatgpt");
5882
6061
  return url.toString();
5883
6062
  }, [sandboxUrl, injectOpenAIRuntime]);
@@ -6317,6 +6496,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6317
6496
  }, [toolResult, modelContext]);
6318
6497
  const resourceUrl = selectedSim?.resourceUrl;
6319
6498
  const resourceScript = selectedSim?.resourceScript;
6499
+ const resourceHtml = selectedSim?.resourceHtml;
6320
6500
  const csp = selectedSim?.resource ? extractResourceCSP(selectedSim.resource) : void 0;
6321
6501
  const resourceMeta = (selectedSim?.resource?._meta)?.ui;
6322
6502
  return {
@@ -6383,6 +6563,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6383
6563
  handleUpdateModelContext,
6384
6564
  resourceUrl,
6385
6565
  resourceScript,
6566
+ resourceHtml,
6386
6567
  csp,
6387
6568
  permissions: resourceMeta?.permissions,
6388
6569
  prefersBorder: resourceMeta?.prefersBorder ?? false,
@@ -6393,6 +6574,40 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6393
6574
  };
6394
6575
  }
6395
6576
  //#endregion
6577
+ //#region src/inspector/inspector-api.ts
6578
+ function inspectorApiEndpoint(path, apiBaseUrl) {
6579
+ if (!apiBaseUrl) return path;
6580
+ return `${apiBaseUrl.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
6581
+ }
6582
+ async function readInspectorJson(res, endpoint) {
6583
+ const text = await res.text();
6584
+ if (!text) return {};
6585
+ try {
6586
+ return JSON.parse(text);
6587
+ } catch {
6588
+ const contentType = res.headers.get("content-type");
6589
+ const preview = text.trim().replace(/\s+/g, " ").slice(0, 120);
6590
+ const typeHint = contentType ? ` (${contentType})` : "";
6591
+ throw new Error(`Expected JSON from ${endpoint} but received a non-JSON response${typeHint}: ${preview}`);
6592
+ }
6593
+ }
6594
+ function resolveInspectorResourceUrls(simulations, apiBaseUrl) {
6595
+ if (!apiBaseUrl || !simulations || typeof simulations !== "object") return simulations;
6596
+ const resolved = {};
6597
+ for (const [key, value] of Object.entries(simulations)) {
6598
+ if (!value || typeof value !== "object") {
6599
+ resolved[key] = value;
6600
+ continue;
6601
+ }
6602
+ const sim = value;
6603
+ resolved[key] = {
6604
+ ...sim,
6605
+ resourceUrl: typeof sim.resourceUrl === "string" && sim.resourceUrl.startsWith("/") ? inspectorApiEndpoint(sim.resourceUrl, apiBaseUrl) : sim.resourceUrl
6606
+ };
6607
+ }
6608
+ return resolved;
6609
+ }
6610
+ //#endregion
6396
6611
  //#region src/inspector/use-mcp-connection.ts
6397
6612
  /**
6398
6613
  * Hook for managing MCP server connection status via the dev server proxy.
@@ -6405,7 +6620,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt" }) {
6405
6620
  * once (or safely twice with cancellation), while explicit `reconnect()` calls
6406
6621
  * are triggered by the Inspector's URL-change effect.
6407
6622
  */
6408
- function useMcpConnection(initialServerUrl) {
6623
+ function useMcpConnection(initialServerUrl, inspectorApiBaseUrl) {
6409
6624
  const [status, setStatus] = (0, react.useState)(initialServerUrl ? "connecting" : "disconnected");
6410
6625
  const [error, setError] = (0, react.useState)();
6411
6626
  const [simulations, setSimulations] = (0, react.useState)();
@@ -6417,7 +6632,8 @@ function useMcpConnection(initialServerUrl) {
6417
6632
  try {
6418
6633
  const body = { url };
6419
6634
  if (auth && auth.type !== "none") body.auth = auth;
6420
- const res = await fetch("/__sunpeak/connect", {
6635
+ const endpoint = inspectorApiEndpoint("/__sunpeak/connect", inspectorApiBaseUrl);
6636
+ const res = await fetch(endpoint, {
6421
6637
  method: "POST",
6422
6638
  headers: { "Content-Type": "application/json" },
6423
6639
  body: JSON.stringify(body)
@@ -6425,7 +6641,7 @@ function useMcpConnection(initialServerUrl) {
6425
6641
  if (!res.ok) {
6426
6642
  let message;
6427
6643
  try {
6428
- const json = await res.json();
6644
+ const json = await readInspectorJson(res, endpoint);
6429
6645
  if (json.error) message = json.error;
6430
6646
  } catch {}
6431
6647
  if (!message) if (res.status === 404) message = "Server not found at this URL. Check the URL and make sure the server is running.";
@@ -6433,9 +6649,9 @@ function useMcpConnection(initialServerUrl) {
6433
6649
  else message = `Connection failed (${res.status})`;
6434
6650
  throw new Error(message);
6435
6651
  }
6436
- const data = await res.json();
6652
+ const data = await readInspectorJson(res, endpoint);
6437
6653
  setStatus("connected");
6438
- setSimulations(data.simulations ?? void 0);
6654
+ setSimulations(resolveInspectorResourceUrls(data.simulations, inspectorApiBaseUrl));
6439
6655
  } catch (err) {
6440
6656
  let message = err instanceof Error ? err.message : String(err);
6441
6657
  if (err instanceof TypeError && message === "Failed to fetch") message = "Cannot reach MCP server. Is it running?";
@@ -6443,20 +6659,21 @@ function useMcpConnection(initialServerUrl) {
6443
6659
  setStatus("error");
6444
6660
  setSimulations(void 0);
6445
6661
  }
6446
- }, []);
6662
+ }, [inspectorApiBaseUrl]);
6447
6663
  const setConnected = (0, react.useCallback)((sims) => {
6448
6664
  setHasReconnected(true);
6449
6665
  setStatus("connected");
6450
6666
  setError(void 0);
6451
- setSimulations(sims);
6452
- }, []);
6667
+ setSimulations(resolveInspectorResourceUrls(sims, inspectorApiBaseUrl));
6668
+ }, [inspectorApiBaseUrl]);
6453
6669
  (0, react.useEffect)(() => {
6454
6670
  if (!initialServerUrl) return;
6455
6671
  let cancelled = false;
6456
6672
  setStatus("connecting");
6457
6673
  (async () => {
6458
6674
  try {
6459
- const res = await fetch("/__sunpeak/list-tools");
6675
+ const endpoint = inspectorApiEndpoint("/__sunpeak/list-tools", inspectorApiBaseUrl);
6676
+ const res = await fetch(endpoint);
6460
6677
  if (cancelled) return;
6461
6678
  if (!res.ok) {
6462
6679
  const msg = res.status === 404 ? "MCP server not reachable. Is it running?" : `Health check failed (${res.status}). Check the MCP server logs.`;
@@ -6485,7 +6702,14 @@ function useMcpConnection(initialServerUrl) {
6485
6702
  //#endregion
6486
6703
  //#region src/inspector/theme-provider.tsx
6487
6704
  var ThemeProviderContext = react.createContext(void 0);
6488
- /** Default theme applier: sets data-theme attribute on document.documentElement */
6705
+ /**
6706
+ * Default theme applier: sets `data-theme` on `document.documentElement`.
6707
+ * Kept for callers using `ThemeProvider` outside the bundled `<Inspector />`
6708
+ * (e.g. custom-inspector builds composed from this package's primitives).
6709
+ * The bundled Inspector overrides this with a no-op applier and applies
6710
+ * theme to its own root element, so embedding it inside a host React app
6711
+ * leaves the host's document attributes untouched.
6712
+ */
6489
6713
  function defaultApplyTheme(theme) {
6490
6714
  if (typeof document !== "undefined") document.documentElement.setAttribute("data-theme", theme);
6491
6715
  }
@@ -6528,7 +6752,7 @@ function ChevronRightIcon() {
6528
6752
  })
6529
6753
  });
6530
6754
  }
6531
- function SimpleSidebar({ children, controls, headerRight }) {
6755
+ function SimpleSidebar({ children, controls, headerRight, rootRef, fillParent = false }) {
6532
6756
  const [isDrawerOpen, setIsDrawerOpen] = react.useState(false);
6533
6757
  const [sidebarWidth, setSidebarWidth] = react.useState(DEFAULT_SIDEBAR_WIDTH);
6534
6758
  const [isResizing, setIsResizing] = react.useState(false);
@@ -6553,7 +6777,8 @@ function SimpleSidebar({ children, controls, headerRight }) {
6553
6777
  };
6554
6778
  }, [isResizing]);
6555
6779
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6556
- className: "sunpeak-inspector-root flex h-screen w-full overflow-hidden relative",
6780
+ ref: rootRef,
6781
+ className: `sunpeak-inspector-root flex ${fillParent ? "h-full w-full" : "h-screen w-full"} overflow-hidden relative`,
6557
6782
  children: [
6558
6783
  isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "fixed inset-0 z-50 cursor-col-resize" }),
6559
6784
  isDrawerOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6883,13 +7108,85 @@ function resolveServerToolResult(mock, args) {
6883
7108
  for (const entry of mock) if (Object.entries(entry.when).every(([key, value]) => args != null && args[key] === value)) return entry.result;
6884
7109
  }
6885
7110
  //#endregion
7111
+ //#region src/inspector/app-flatten.ts
7112
+ /** Pull the output-template URI off a tool's _meta, if present. */
7113
+ function getOutputTemplate(toolMeta) {
7114
+ if (!toolMeta || typeof toolMeta !== "object") return void 0;
7115
+ const openai = toolMeta.openai;
7116
+ if (!openai || typeof openai !== "object") return void 0;
7117
+ const template = openai.outputTemplate;
7118
+ return typeof template === "string" ? template : void 0;
7119
+ }
7120
+ /** Build an MCP-shaped `Resource` from the embedder's input. Used purely for
7121
+ * sidebar metadata (CSP, permissions, prefersBorder); the actual HTML render
7122
+ * goes through `resourceHtml` on the resulting Simulation. */
7123
+ function toMcpResource(r) {
7124
+ return {
7125
+ uri: r.uri,
7126
+ mimeType: r.mimeType ?? "text/html",
7127
+ name: r.uri,
7128
+ ...r._meta ? { _meta: r._meta } : {}
7129
+ };
7130
+ }
7131
+ /**
7132
+ * Flatten an `InspectorApp` to the `Record<string, Simulation>` shape the
7133
+ * Inspector consumes internally. Returns an empty map if `app` is missing.
7134
+ */
7135
+ function flattenAppToSimulations(app) {
7136
+ if (!app) return {};
7137
+ const result = {};
7138
+ const resourcesByUri = /* @__PURE__ */ new Map();
7139
+ for (const r of app.resources) {
7140
+ if (resourcesByUri.has(r.uri)) console.warn(`[Inspector] Duplicate resource URI '${r.uri}' in app.resources — the second entry replaces the first.`);
7141
+ resourcesByUri.set(r.uri, r);
7142
+ }
7143
+ for (const appTool of app.tools) {
7144
+ const uri = getOutputTemplate(appTool.tool._meta);
7145
+ if (!uri) continue;
7146
+ const resource = resourcesByUri.get(uri);
7147
+ if (!resource) continue;
7148
+ const mcpResource = toMcpResource(resource);
7149
+ const sims = appTool.simulations && appTool.simulations.length > 0 ? appTool.simulations : [{ name: appTool.tool.name }];
7150
+ for (const sim of sims) {
7151
+ const key = `${appTool.tool.name}__${sim.name}`;
7152
+ if (key in result) console.warn(`[Inspector] Duplicate simulation name '${sim.name}' under tool '${appTool.tool.name}' — the second entry replaces the first.`);
7153
+ result[key] = {
7154
+ name: key,
7155
+ displayName: sim.name,
7156
+ resourceHtml: resource.html,
7157
+ userMessage: sim.userMessage,
7158
+ tool: appTool.tool,
7159
+ resource: mcpResource,
7160
+ toolInput: sim.toolInput,
7161
+ toolResult: sim.toolResult,
7162
+ serverTools: sim.serverTools
7163
+ };
7164
+ }
7165
+ }
7166
+ return result;
7167
+ }
7168
+ //#endregion
6886
7169
  //#region src/inspector/inspector.tsx
6887
7170
  var DOCS_BASE_URL = "https://sunpeak.ai/docs";
6888
7171
  /** Check whether a simulation has user-authored fixture data. */
6889
7172
  function hasFixtureData(sim) {
6890
7173
  return sim.toolResult != null || sim.toolInput != null || sim.serverTools != null;
6891
7174
  }
6892
- function Inspector({ children, simulations: initialSimulations = {}, appName = "Sunpeak", appIcon, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdResources = false, hideInspectorModes = false, demoMode = false, sandboxUrl, mcpServerUrl }) {
7175
+ var EMPTY_SIMULATIONS = Object.freeze({});
7176
+ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_SIMULATIONS, appName: appNameProp, appIcon: appIconProp, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdResources = false, hideInspectorModes = false, demoMode = false, sandboxUrl, mcpServerUrl, inspectorApiBaseUrl }) {
7177
+ const initialSimulations = react.useMemo(() => app ? flattenAppToSimulations(app) : initialSimulationsProp, [app, initialSimulationsProp]);
7178
+ const appName = app?.name ?? appNameProp ?? "Sunpeak";
7179
+ const appIcon = app?.icon ?? appIconProp;
7180
+ const isEmbedded = !!app;
7181
+ const conflictWarnedRef = react.useRef(false);
7182
+ react.useEffect(() => {
7183
+ if (conflictWarnedRef.current) return;
7184
+ if (app && initialSimulationsProp && Object.keys(initialSimulationsProp).length > 0) {
7185
+ conflictWarnedRef.current = true;
7186
+ console.warn("[Inspector] Both `app` and `simulations` were provided. `app` takes precedence; `simulations` is ignored.");
7187
+ }
7188
+ }, [app, initialSimulationsProp]);
7189
+ const rootRef = react.useRef(null);
6893
7190
  const [simulations, setSimulations] = react.useState(initialSimulations);
6894
7191
  react.useEffect(() => {
6895
7192
  setSimulations(initialSimulations);
@@ -6972,13 +7269,14 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
6972
7269
  const [oauthClientSecret, setOauthClientSecret] = react.useState("");
6973
7270
  const [oauthStatus, setOauthStatus] = react.useState("none");
6974
7271
  const [oauthError, setOauthError] = react.useState();
6975
- const connection = useMcpConnection(mcpServerUrl || void 0);
7272
+ const connection = useMcpConnection(isEmbedded ? void 0 : mcpServerUrl || void 0, inspectorApiBaseUrl);
6976
7273
  const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? defaultProdResources);
6977
7274
  const showSidebar = state.urlSidebar !== false;
6978
7275
  const showDevOverlay = state.urlDevOverlay !== false;
6979
7276
  const [isRunning, setIsRunning] = react.useState(false);
6980
7277
  const [hasRun, setHasRun] = react.useState(false);
6981
7278
  const [showCheck, setShowCheck] = react.useState(false);
7279
+ const [serverPreviewGeneration, setServerPreviewGeneration] = react.useState(0);
6982
7280
  const checkTimerRef = react.useRef(void 0);
6983
7281
  const oauthCleanupRef = react.useRef(void 0);
6984
7282
  react.useEffect(() => {
@@ -7014,7 +7312,8 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7014
7312
  setOauthError(void 0);
7015
7313
  const popup = window.open("about:blank", `sunpeak-oauth-${Date.now()}`, "width=600,height=700,popup=yes");
7016
7314
  try {
7017
- const res = await fetch("/__sunpeak/oauth/start", {
7315
+ const endpoint = inspectorApiEndpoint("/__sunpeak/oauth/start", inspectorApiBaseUrl);
7316
+ const res = await fetch(endpoint, {
7018
7317
  method: "POST",
7019
7318
  headers: { "Content-Type": "application/json" },
7020
7319
  body: JSON.stringify({
@@ -7024,15 +7323,12 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7024
7323
  clientSecret: oauthClientSecret || void 0
7025
7324
  })
7026
7325
  });
7326
+ const data = await readInspectorJson(res, endpoint);
7027
7327
  if (!res.ok) {
7028
7328
  let message = `OAuth start failed (${res.status})`;
7029
- try {
7030
- const json = await res.json();
7031
- if (json.error) message = json.error;
7032
- } catch {}
7329
+ if (data.error) message = data.error;
7033
7330
  throw new Error(message);
7034
7331
  }
7035
- const data = await res.json();
7036
7332
  if (data.error) {
7037
7333
  popup?.close();
7038
7334
  setOauthError(data.error);
@@ -7046,12 +7342,22 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7046
7342
  return;
7047
7343
  }
7048
7344
  if (data.status === "redirect" && data.authUrl) {
7345
+ let parsedAuthUrl = null;
7346
+ try {
7347
+ parsedAuthUrl = new URL(data.authUrl);
7348
+ } catch {}
7349
+ if (!parsedAuthUrl || parsedAuthUrl.protocol !== "http:" && parsedAuthUrl.protocol !== "https:") {
7350
+ popup?.close();
7351
+ setOauthError("OAuth authorization URL is not a valid http(s) URL.");
7352
+ setOauthStatus("error");
7353
+ return;
7354
+ }
7049
7355
  if (!popup || popup.closed) {
7050
7356
  setOauthError("Popup was blocked. Allow popups for this site and try again.");
7051
7357
  setOauthStatus("error");
7052
7358
  return;
7053
7359
  }
7054
- popup.location.href = data.authUrl;
7360
+ popup.location.href = parsedAuthUrl.toString();
7055
7361
  let checkClosed;
7056
7362
  let bc;
7057
7363
  const cleanup = () => {
@@ -7103,11 +7409,14 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7103
7409
  oauthClientId,
7104
7410
  oauthClientSecret,
7105
7411
  demoMode,
7106
- connection
7412
+ connection,
7413
+ inspectorApiBaseUrl
7107
7414
  ]);
7108
7415
  react.useEffect(() => {
7109
- if (connection.simulations) setSimulations(connection.simulations);
7110
- else if (connection.status === "error" && connection.hasReconnected) setSimulations({});
7416
+ if (connection.simulations) {
7417
+ setSimulations(connection.simulations);
7418
+ setServerPreviewGeneration((generation) => generation + 1);
7419
+ } else if (connection.status === "error" && connection.hasReconnected) setSimulations({});
7111
7420
  }, [
7112
7421
  connection.simulations,
7113
7422
  connection.status,
@@ -7240,15 +7549,23 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7240
7549
  displayMode,
7241
7550
  setDisplayMode
7242
7551
  ]);
7243
- react.useLayoutEffect(() => {
7244
- const vars = activeShell?.styleVariables;
7245
- if (!vars) return;
7246
- const root = document.documentElement;
7247
- for (const [key, value] of Object.entries(vars)) if (value) root.style.setProperty(key, value);
7248
- }, [activeShell]);
7552
+ const prevStyleVarKeysRef = react.useRef([]);
7249
7553
  const prevPageStyleKeysRef = react.useRef([]);
7250
7554
  react.useLayoutEffect(() => {
7251
- const root = document.documentElement;
7555
+ const root = rootRef.current;
7556
+ if (!root) return;
7557
+ root.setAttribute("data-theme", state.theme);
7558
+ root.style.colorScheme = state.theme;
7559
+ for (const key of prevStyleVarKeysRef.current) root.style.removeProperty(key);
7560
+ const vars = activeShell?.styleVariables;
7561
+ if (vars) {
7562
+ const keys = [];
7563
+ for (const [key, value] of Object.entries(vars)) if (value) {
7564
+ root.style.setProperty(key, value);
7565
+ keys.push(key);
7566
+ }
7567
+ prevStyleVarKeysRef.current = keys;
7568
+ } else prevStyleVarKeysRef.current = [];
7252
7569
  for (const key of prevPageStyleKeysRef.current) root.style.removeProperty(key);
7253
7570
  const pageStyles = activeShell?.pageStyles;
7254
7571
  if (pageStyles) {
@@ -7259,7 +7576,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7259
7576
  }
7260
7577
  prevPageStyleKeysRef.current = keys;
7261
7578
  } else prevPageStyleKeysRef.current = [];
7262
- }, [activeShell]);
7579
+ }, [activeShell, state.theme]);
7263
7580
  react.useLayoutEffect(() => {
7264
7581
  const fontCss = activeShell?.fontCss;
7265
7582
  const id = "sunpeak-host-fonts";
@@ -7350,7 +7667,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7350
7667
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7351
7668
  className: "text-sm text-center max-w-xs",
7352
7669
  style: { color: "var(--color-text-secondary)" },
7353
- children: isError ? "Could not connect to MCP server" : isConnected ? "No tools with UI resources found on this server" : serverUrl ? "Connecting…" : "Enter an MCP server URL to get started"
7670
+ children: isEmbedded ? "No tools with UI resources in this app" : isError ? "Could not connect to MCP server" : isConnected ? "No tools with UI resources found on this server" : serverUrl ? "Connecting…" : "Enter an MCP server URL to get started"
7354
7671
  })
7355
7672
  });
7356
7673
  } else if (showEmptyState) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -7375,6 +7692,30 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7375
7692
  children: "Building…"
7376
7693
  })
7377
7694
  });
7695
+ else if (state.resourceHtml) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7696
+ className: "h-full w-full",
7697
+ style: { background: iframeBg },
7698
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IframeResource, {
7699
+ html: state.resourceHtml,
7700
+ hostContext,
7701
+ toolInput: state.toolInput,
7702
+ toolResult: state.effectiveToolResult,
7703
+ hostOptions: {
7704
+ hostInfo: activeShell?.hostInfo,
7705
+ hostCapabilities: activeShell?.hostCapabilities,
7706
+ onDisplayModeChange: state.handleDisplayModeChange,
7707
+ onUpdateModelContext: state.handleUpdateModelContext,
7708
+ onCallTool: handleCallTool
7709
+ },
7710
+ permissions: state.permissions,
7711
+ prefersBorder: state.prefersBorder,
7712
+ onDisplayModeReady: state.handleDisplayModeReady,
7713
+ debugInjectState: state.modelContext,
7714
+ injectOpenAIRuntime: state.activeHost === "chatgpt",
7715
+ sandboxUrl,
7716
+ className: "h-full w-full"
7717
+ }, `${state.activeHost}-${state.selectedSimulationName}-html`)
7718
+ });
7378
7719
  else if (effectiveResourceUrl) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7379
7720
  className: "h-full w-full",
7380
7721
  style: { background: iframeBg },
@@ -7397,7 +7738,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7397
7738
  injectOpenAIRuntime: state.activeHost === "chatgpt",
7398
7739
  sandboxUrl,
7399
7740
  className: "h-full w-full"
7400
- }, `${state.activeHost}-${state.selectedSimulationName}-${effectiveResourceUrl}-${prodResources}-${prodResourcesGeneration}`)
7741
+ }, `${state.activeHost}-${state.selectedSimulationName}-${effectiveResourceUrl}-${prodResources}-${prodResourcesGeneration}-${serverPreviewGeneration}`)
7401
7742
  });
7402
7743
  else if (!prodResources && state.resourceScript) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7403
7744
  className: "h-full w-full",
@@ -7425,7 +7766,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7425
7766
  }, `${state.activeHost}-${state.selectedSimulationName}-${state.resourceScript}`)
7426
7767
  });
7427
7768
  else content = children;
7428
- const applyTheme = activeShell?.applyTheme;
7769
+ const applyTheme = react.useCallback((_theme) => {}, []);
7429
7770
  const runButton = !demoMode && onCallTool && currentSim && activeSimulationName === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7430
7771
  type: "button",
7431
7772
  onClick: handleRun,
@@ -7465,11 +7806,13 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7465
7806
  headerAction: runButton,
7466
7807
  children: content
7467
7808
  }) : content;
7809
+ const rootSizing = isEmbedded ? "h-full w-full" : "h-screen w-screen";
7468
7810
  if (!showSidebar) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
7469
7811
  theme: state.theme,
7470
7812
  applyTheme,
7471
7813
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7472
- className: "flex h-screen w-screen",
7814
+ ref: rootRef,
7815
+ className: `sunpeak-inspector-root flex ${rootSizing}`,
7473
7816
  children: conversationContent
7474
7817
  })
7475
7818
  });
@@ -7477,10 +7820,12 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7477
7820
  theme: state.theme,
7478
7821
  applyTheme,
7479
7822
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSidebar, {
7823
+ rootRef,
7824
+ fillParent: isEmbedded,
7480
7825
  controls: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7481
7826
  className: "space-y-1",
7482
7827
  children: [
7483
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
7828
+ !isEmbedded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
7484
7829
  label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7485
7830
  className: "flex items-center gap-1.5",
7486
7831
  children: ["MCP Server", serverUrl && !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -7499,8 +7844,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7499
7844
  placeholder: "http://localhost:8000/mcp",
7500
7845
  disabled: demoMode
7501
7846
  })
7502
- }),
7503
- !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
7847
+ }), !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
7504
7848
  label: "Authentication",
7505
7849
  defaultCollapsed: authType === "none",
7506
7850
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -7585,8 +7929,8 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7585
7929
  })
7586
7930
  ]
7587
7931
  })
7588
- }, `auth-${authType === "none" ? "none" : "active"}`),
7589
- !hideInspectorModes && !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
7932
+ }, `auth-${authType === "none" ? "none" : "active"}`)] }),
7933
+ !hideInspectorModes && !demoMode && !isEmbedded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
7590
7934
  checked: prodResources,
7591
7935
  onChange: setProdResources,
7592
7936
  label: "Prod Resources",
@@ -7637,7 +7981,7 @@ function Inspector({ children, simulations: initialSimulations = {}, appName = "
7637
7981
  label: selectedToolInfo && selectedToolInfo.fixtureSimNames.length > 0 ? "None (call server)" : "None"
7638
7982
  }], ...(selectedToolInfo?.fixtureSimNames ?? []).map((simName) => ({
7639
7983
  value: simName,
7640
- label: simName
7984
+ label: simulations[simName]?.displayName ?? simName
7641
7985
  }))]
7642
7986
  })
7643
7987
  })]
@@ -8113,6 +8457,12 @@ Object.defineProperty(exports, "extractResourceCSP", {
8113
8457
  return extractResourceCSP;
8114
8458
  }
8115
8459
  });
8460
+ Object.defineProperty(exports, "flattenAppToSimulations", {
8461
+ enumerable: true,
8462
+ get: function() {
8463
+ return flattenAppToSimulations;
8464
+ }
8465
+ });
8116
8466
  Object.defineProperty(exports, "getHostShell", {
8117
8467
  enumerable: true,
8118
8468
  get: function() {
@@ -8156,4 +8506,4 @@ Object.defineProperty(exports, "useThemeContext", {
8156
8506
  }
8157
8507
  });
8158
8508
 
8159
- //# sourceMappingURL=inspector-DtEighD9.cjs.map
8509
+ //# sourceMappingURL=inspector-CJPO4f12.cjs.map