sunpeak 0.17.4 → 0.17.6

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 (48) hide show
  1. package/bin/commands/dev.mjs +4 -3
  2. package/bin/commands/inspect.mjs +58 -33
  3. package/dist/chatgpt/globals.css +5 -0
  4. package/dist/chatgpt/index.cjs +2 -2
  5. package/dist/chatgpt/index.js +2 -2
  6. package/dist/claude/index.cjs +1 -1
  7. package/dist/claude/index.js +1 -1
  8. package/dist/index.cjs +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/mcp/index.cjs +1 -1
  11. package/dist/mcp/index.cjs.map +1 -1
  12. package/dist/mcp/index.js +1 -1
  13. package/dist/mcp/index.js.map +1 -1
  14. package/dist/mcp/types.d.ts +7 -0
  15. package/dist/simulator/index.cjs +2 -2
  16. package/dist/simulator/index.cjs.map +1 -1
  17. package/dist/simulator/index.js +2 -2
  18. package/dist/simulator/index.js.map +1 -1
  19. package/dist/simulator/simulator-url.d.ts +32 -41
  20. package/dist/simulator/simulator.d.ts +14 -10
  21. package/dist/simulator/use-mcp-connection.d.ts +12 -7
  22. package/dist/simulator/use-simulator-state.d.ts +1 -1
  23. package/dist/{simulator-Dl8B-Ljb.js → simulator-BijjlOXb.js} +278 -143
  24. package/dist/simulator-BijjlOXb.js.map +1 -0
  25. package/dist/{simulator-CH9hs0N6.cjs → simulator-DqWETA_1.cjs} +278 -143
  26. package/dist/simulator-DqWETA_1.cjs.map +1 -0
  27. package/dist/{simulator-url-CozKF1jf.cjs → simulator-url-3ATCsPOT.cjs} +11 -30
  28. package/dist/simulator-url-3ATCsPOT.cjs.map +1 -0
  29. package/dist/{simulator-url-KoS_ToP6.js → simulator-url-BbuuWa7S.js} +11 -30
  30. package/dist/simulator-url-BbuuWa7S.js.map +1 -0
  31. package/dist/style.css +5 -0
  32. package/package.json +1 -1
  33. package/template/dist/albums/albums.html +1 -1
  34. package/template/dist/albums/albums.json +1 -1
  35. package/template/dist/carousel/carousel.html +1 -1
  36. package/template/dist/carousel/carousel.json +1 -1
  37. package/template/dist/map/map.html +1 -1
  38. package/template/dist/map/map.json +1 -1
  39. package/template/dist/review/review.html +1 -1
  40. package/template/dist/review/review.json +1 -1
  41. package/template/tests/e2e/albums.spec.ts +3 -9
  42. package/template/tests/e2e/carousel.spec.ts +3 -9
  43. package/template/tests/e2e/map.spec.ts +3 -9
  44. package/template/tests/e2e/review.spec.ts +3 -9
  45. package/dist/simulator-CH9hs0N6.cjs.map +0 -1
  46. package/dist/simulator-Dl8B-Ljb.js.map +0 -1
  47. package/dist/simulator-url-CozKF1jf.cjs.map +0 -1
  48. package/dist/simulator-url-KoS_ToP6.js.map +0 -1
@@ -2186,13 +2186,14 @@ var DEFAULT_PLATFORM = "desktop";
2186
2186
  * - touch: 'true' | 'false'
2187
2187
  * - safeAreaTop, safeAreaBottom, safeAreaLeft, safeAreaRight: number
2188
2188
  * - host: 'chatgpt' | 'claude'
2189
- * - prodTools: 'true' | 'false'
2189
+ * - tool: tool name (e.g., 'show-albums') selects tool without mock data
2190
2190
  * - prodResources: 'true' | 'false'
2191
2191
  */
2192
2192
  function parseUrlParams() {
2193
2193
  if (typeof window === "undefined") return {};
2194
2194
  const params = new URLSearchParams(window.location.search);
2195
2195
  const simulation = params.get("simulation") ?? void 0;
2196
+ const tool = params.get("tool") ?? void 0;
2196
2197
  const theme = params.get("theme");
2197
2198
  const displayMode = params.get("displayMode");
2198
2199
  const locale = params.get("locale");
@@ -2201,8 +2202,6 @@ function parseUrlParams() {
2201
2202
  const maxWidthParam = params.get("maxWidth");
2202
2203
  const containerMaxWidth = maxWidthParam ? Number(maxWidthParam) : void 0;
2203
2204
  const host = params.get("host") ?? void 0;
2204
- const prodToolsParam = params.get("prodTools");
2205
- const prodTools = prodToolsParam === "true" ? true : prodToolsParam === "false" ? false : void 0;
2206
2205
  const prodResourcesParam = params.get("prodResources");
2207
2206
  const prodResources = prodResourcesParam === "true" ? true : prodResourcesParam === "false" ? false : void 0;
2208
2207
  const deviceType = params.get("deviceType");
@@ -2227,6 +2226,7 @@ function parseUrlParams() {
2227
2226
  } : void 0;
2228
2227
  return {
2229
2228
  simulation,
2229
+ tool,
2230
2230
  theme: theme ?? void 0,
2231
2231
  displayMode: displayMode ?? void 0,
2232
2232
  locale: locale ?? void 0,
@@ -2236,7 +2236,6 @@ function parseUrlParams() {
2236
2236
  deviceCapabilities,
2237
2237
  safeAreaInsets,
2238
2238
  host: host ?? void 0,
2239
- prodTools,
2240
2239
  prodResources
2241
2240
  };
2242
2241
  }
@@ -2487,7 +2486,7 @@ function useSimulatorState({ simulations, defaultHost = "chatgpt" }) {
2487
2486
  csp,
2488
2487
  permissions: resourceMeta?.permissions,
2489
2488
  prefersBorder: resourceMeta?.prefersBorder ?? false,
2490
- urlProdTools: urlParams.prodTools,
2489
+ urlTool: urlParams.tool,
2491
2490
  urlProdResources: urlParams.prodResources
2492
2491
  };
2493
2492
  }
@@ -2496,17 +2495,21 @@ function useSimulatorState({ simulations, defaultHost = "chatgpt" }) {
2496
2495
  /**
2497
2496
  * Hook for managing MCP server connection status via the dev server proxy.
2498
2497
  *
2499
- * On mount (when `serverUrl` is provided), verifies the connection is alive
2500
- * by fetching `/__sunpeak/list-tools`. Tracks connection status for display
2501
- * in the sidebar (colored dot indicator).
2498
+ * On mount (when `initialServerUrl` is provided), verifies the connection is alive
2499
+ * by fetching `/__sunpeak/list-tools`. URL changes are handled by the caller
2500
+ * via `reconnect()`, which posts to `/__sunpeak/connect`.
2502
2501
  *
2503
- * Tool calling is handled separately via the `onCallTool` prop this
2504
- * hook only manages the connection lifecycle and status display.
2502
+ * This split avoids React StrictMode issues: the mount-only health check runs
2503
+ * once (or safely twice with cancellation), while explicit `reconnect()` calls
2504
+ * are triggered by the Simulator's URL-change effect.
2505
2505
  */
2506
- function useMcpConnection(serverUrl) {
2507
- const [status, setStatus] = (0, react.useState)(serverUrl ? "connecting" : "disconnected");
2506
+ function useMcpConnection(initialServerUrl) {
2507
+ const [status, setStatus] = (0, react.useState)(initialServerUrl ? "connecting" : "disconnected");
2508
2508
  const [error, setError] = (0, react.useState)();
2509
+ const [simulations, setSimulations] = (0, react.useState)();
2510
+ const [hasReconnected, setHasReconnected] = (0, react.useState)(false);
2509
2511
  const reconnect = (0, react.useCallback)(async (url) => {
2512
+ setHasReconnected(true);
2510
2513
  setStatus("connecting");
2511
2514
  setError(void 0);
2512
2515
  try {
@@ -2516,23 +2519,27 @@ function useMcpConnection(serverUrl) {
2516
2519
  body: JSON.stringify({ url })
2517
2520
  });
2518
2521
  if (!res.ok) {
2519
- const text = await res.text();
2520
- throw new Error(text || `Connection failed (${res.status})`);
2522
+ let message = `Connection failed (${res.status})`;
2523
+ try {
2524
+ const json = await res.json();
2525
+ if (json.error) message = json.error;
2526
+ } catch {}
2527
+ throw new Error(message);
2521
2528
  }
2529
+ const data = await res.json();
2522
2530
  setStatus("connected");
2531
+ setSimulations(data.simulations ?? void 0);
2523
2532
  } catch (err) {
2524
2533
  setError(err instanceof Error ? err.message : String(err));
2525
2534
  setStatus("error");
2535
+ setSimulations(void 0);
2526
2536
  }
2527
2537
  }, []);
2528
2538
  (0, react.useEffect)(() => {
2529
- if (!serverUrl) {
2530
- setStatus("disconnected");
2531
- return;
2532
- }
2539
+ if (!initialServerUrl) return;
2533
2540
  let cancelled = false;
2541
+ setStatus("connecting");
2534
2542
  (async () => {
2535
- setStatus("connecting");
2536
2543
  try {
2537
2544
  const res = await fetch("/__sunpeak/list-tools");
2538
2545
  if (cancelled) return;
@@ -2547,10 +2554,12 @@ function useMcpConnection(serverUrl) {
2547
2554
  return () => {
2548
2555
  cancelled = true;
2549
2556
  };
2550
- }, [serverUrl]);
2557
+ }, []);
2551
2558
  return {
2552
2559
  status,
2553
2560
  error,
2561
+ simulations,
2562
+ hasReconnected,
2554
2563
  reconnect
2555
2564
  };
2556
2565
  }
@@ -2704,7 +2713,7 @@ function SimpleSidebar({ children, controls, headerRight }) {
2704
2713
  ]
2705
2714
  });
2706
2715
  }
2707
- var DOCS_BASE_URL = "https://sunpeak.ai/docs";
2716
+ var DOCS_BASE_URL$1 = "https://sunpeak.ai/docs";
2708
2717
  function HelpIcon({ tooltip, docsPath }) {
2709
2718
  const [pos, setPos] = react.useState(null);
2710
2719
  const ref = react.useRef(null);
@@ -2717,7 +2726,7 @@ function HelpIcon({ tooltip, docsPath }) {
2717
2726
  };
2718
2727
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
2719
2728
  ref,
2720
- href: `${DOCS_BASE_URL}/${docsPath}`,
2729
+ href: `${DOCS_BASE_URL$1}/${docsPath}`,
2721
2730
  target: "_blank",
2722
2731
  rel: "noopener noreferrer",
2723
2732
  className: "inline-flex items-center justify-center no-underline flex-shrink-0 transition-colors",
@@ -2839,6 +2848,13 @@ function SidebarInput({ value, onChange, applyOnBlur = false, placeholder, type
2839
2848
  setIsEditing(false);
2840
2849
  onChange(draft);
2841
2850
  },
2851
+ onKeyDown: (e) => {
2852
+ if (e.key === "Enter") {
2853
+ setIsEditing(false);
2854
+ onChange(draft);
2855
+ e.target.blur();
2856
+ }
2857
+ },
2842
2858
  placeholder,
2843
2859
  disabled,
2844
2860
  className: "w-full h-7 text-xs rounded-md px-2 outline-none disabled:opacity-50 disabled:cursor-not-allowed",
@@ -2949,54 +2965,142 @@ function resolveServerToolResult(mock, args) {
2949
2965
  }
2950
2966
  //#endregion
2951
2967
  //#region src/simulator/simulator.tsx
2952
- function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdTools = false, defaultProdResources = false, hideSimulatorModes = false, sandboxUrl, mcpServerUrl }) {
2953
- const isInspectMode = mcpServerUrl != null;
2968
+ var DOCS_BASE_URL = "https://sunpeak.ai/docs";
2969
+ /** Check whether a simulation has user-authored fixture data. */
2970
+ function hasFixtureData(sim) {
2971
+ return sim.toolResult != null || sim.toolInput != null || sim.serverTools != null;
2972
+ }
2973
+ function Simulator({ children, simulations: initialSimulations = {}, appName = "Sunpeak", appIcon, defaultHost = "chatgpt", onCallTool, onCallToolDirect, defaultProdResources = false, hideSimulatorModes = false, demoMode = false, sandboxUrl, mcpServerUrl }) {
2974
+ const [simulations, setSimulations] = react.useState(initialSimulations);
2975
+ react.useEffect(() => {
2976
+ setSimulations(initialSimulations);
2977
+ }, [initialSimulations]);
2978
+ const toolMap = react.useMemo(() => {
2979
+ const map = /* @__PURE__ */ new Map();
2980
+ for (const [simName, sim] of Object.entries(simulations)) {
2981
+ if (!sim.resource) continue;
2982
+ const toolName = sim.tool.name;
2983
+ if (!map.has(toolName)) map.set(toolName, {
2984
+ tool: sim.tool,
2985
+ resource: sim.resource,
2986
+ simNames: [],
2987
+ fixtureSimNames: []
2988
+ });
2989
+ const info = map.get(toolName);
2990
+ info.simNames.push(simName);
2991
+ if (hasFixtureData(sim)) info.fixtureSimNames.push(simName);
2992
+ }
2993
+ return map;
2994
+ }, [simulations]);
2995
+ const toolNames = react.useMemo(() => Array.from(toolMap.keys()).sort((a, b) => {
2996
+ const infoA = toolMap.get(a);
2997
+ const infoB = toolMap.get(b);
2998
+ const labelA = infoA.tool.title || a;
2999
+ const labelB = infoB.tool.title || b;
3000
+ return labelA.localeCompare(labelB);
3001
+ }), [toolMap]);
3002
+ const initUrlParams = react.useMemo(() => {
3003
+ if (typeof window === "undefined") return {
3004
+ tool: null,
3005
+ simulation: null,
3006
+ noMockData: false
3007
+ };
3008
+ const params = new URLSearchParams(window.location.search);
3009
+ const prodTools = params.get("prodTools") === "true";
3010
+ return {
3011
+ tool: params.get("tool"),
3012
+ simulation: params.get("simulation"),
3013
+ noMockData: prodTools
3014
+ };
3015
+ }, []);
3016
+ const [selectedToolName, setSelectedToolName] = react.useState(() => {
3017
+ if (initUrlParams.tool && toolMap.has(initUrlParams.tool)) return initUrlParams.tool;
3018
+ if (initUrlParams.simulation) {
3019
+ for (const [toolName, info] of toolMap) if (info.simNames.includes(initUrlParams.simulation)) return toolName;
3020
+ }
3021
+ return toolNames[0] ?? "";
3022
+ });
3023
+ const prevToolNamesRef = react.useRef(toolNames);
3024
+ if (prevToolNamesRef.current !== toolNames) {
3025
+ prevToolNamesRef.current = toolNames;
3026
+ if (toolNames.length > 0 && !toolMap.has(selectedToolName)) setSelectedToolName(toolNames[0]);
3027
+ }
3028
+ const selectedToolInfo = toolMap.get(selectedToolName);
3029
+ const [activeSimulationName, setActiveSimulationName] = react.useState(() => {
3030
+ if (!selectedToolInfo) return null;
3031
+ if (initUrlParams.noMockData) return null;
3032
+ if (initUrlParams.tool && !initUrlParams.simulation) return null;
3033
+ if (initUrlParams.simulation && selectedToolInfo.fixtureSimNames.includes(initUrlParams.simulation)) return initUrlParams.simulation;
3034
+ return selectedToolInfo.fixtureSimNames[0] ?? null;
3035
+ });
3036
+ const prevToolNameRef = react.useRef(selectedToolName);
3037
+ if (prevToolNameRef.current !== selectedToolName) {
3038
+ prevToolNameRef.current = selectedToolName;
3039
+ setActiveSimulationName(toolMap.get(selectedToolName)?.fixtureSimNames[0] ?? null);
3040
+ }
3041
+ const effectiveSimulationName = activeSimulationName ?? selectedToolInfo?.simNames[0] ?? "";
3042
+ const currentSim = simulations[effectiveSimulationName];
2954
3043
  const state = useSimulatorState({
2955
3044
  simulations,
2956
3045
  defaultHost
2957
3046
  });
2958
- const connection = useMcpConnection(mcpServerUrl);
2959
- const [prodTools, setProdTools] = react.useState(isInspectMode ? true : state.urlProdTools ?? defaultProdTools);
3047
+ const [serverUrl, setServerUrl] = react.useState(mcpServerUrl ?? "");
3048
+ const connection = useMcpConnection(mcpServerUrl || void 0);
2960
3049
  const [prodResources, setProdResources] = react.useState(state.urlProdResources ?? defaultProdResources);
2961
3050
  const [isRunning, setIsRunning] = react.useState(false);
2962
3051
  const [hasRun, setHasRun] = react.useState(false);
2963
3052
  const [showCheck, setShowCheck] = react.useState(false);
2964
3053
  const checkTimerRef = react.useRef(void 0);
2965
3054
  react.useEffect(() => {
2966
- if (prodTools) {
2967
- setHasRun(false);
2968
- state.setToolResult(void 0);
2969
- state.setToolResultJson("");
2970
- state.setToolResultError("");
3055
+ state.setSelectedSimulationName(effectiveSimulationName);
3056
+ }, [effectiveSimulationName]);
3057
+ const prevServerUrlRef = react.useRef(serverUrl);
3058
+ react.useEffect(() => {
3059
+ const urlChanged = serverUrl !== prevServerUrlRef.current;
3060
+ prevServerUrlRef.current = serverUrl;
3061
+ if (!urlChanged) return;
3062
+ if (serverUrl) connection.reconnect(serverUrl);
3063
+ }, [serverUrl, connection.reconnect]);
3064
+ react.useEffect(() => {
3065
+ if (connection.simulations) setSimulations(connection.simulations);
3066
+ else if (connection.status === "error" && connection.hasReconnected) setSimulations({});
3067
+ }, [
3068
+ connection.simulations,
3069
+ connection.status,
3070
+ connection.hasReconnected
3071
+ ]);
3072
+ const { setToolResult, setToolResultJson, setToolResultError } = state;
3073
+ react.useEffect(() => {
3074
+ if (activeSimulationName === null) {
3075
+ setToolResult(void 0);
3076
+ setToolResultJson("");
3077
+ setToolResultError("");
2971
3078
  } else {
2972
- const simResult = state.selectedSim?.toolResult ?? void 0;
2973
- state.setToolResult(simResult);
2974
- }
2975
- }, [prodTools, state.selectedSimulationName]);
2976
- react.useEffect(() => () => clearTimeout(checkTimerRef.current), []);
2977
- const toolOptions = react.useMemo(() => {
2978
- if (!prodTools) return [];
2979
- const seen = /* @__PURE__ */ new Map();
2980
- for (const simName of state.simulationNames) {
2981
- const toolName = simulations[simName].tool.name;
2982
- if (!seen.has(toolName)) seen.set(toolName, simName);
3079
+ const result = simulations[activeSimulationName]?.toolResult ?? void 0;
3080
+ setToolResult(result);
3081
+ setToolResultJson(result ? JSON.stringify(result, null, 2) : "");
3082
+ setToolResultError("");
2983
3083
  }
2984
- return Array.from(seen.entries()).map(([toolName, simName]) => ({
2985
- value: simName,
2986
- label: simulations[simName].tool.title || toolName
2987
- }));
2988
3084
  }, [
2989
- prodTools,
2990
- state.simulationNames,
2991
- simulations
3085
+ activeSimulationName,
3086
+ effectiveSimulationName,
3087
+ simulations,
3088
+ setToolResult,
3089
+ setToolResultJson,
3090
+ setToolResultError
2992
3091
  ]);
3092
+ react.useEffect(() => {
3093
+ setHasRun(false);
3094
+ }, [effectiveSimulationName]);
3095
+ react.useEffect(() => () => clearTimeout(checkTimerRef.current), []);
2993
3096
  const handleRun = react.useCallback(async () => {
2994
3097
  const caller = onCallToolDirect ?? onCallTool;
2995
- if (!caller || !state.selectedSim) return;
2996
- const toolName = state.selectedSim.tool.name;
3098
+ const sim = simulations[effectiveSimulationName];
3099
+ if (!caller || !sim) return;
3100
+ const toolName = sim.tool.name;
2997
3101
  setIsRunning(true);
2998
3102
  try {
2999
- const result = (isInspectMode ? state.selectedSim.toolResult : void 0) ?? await caller({
3103
+ const result = await caller({
3000
3104
  name: toolName,
3001
3105
  arguments: state.toolInput
3002
3106
  });
@@ -3023,14 +3127,16 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3023
3127
  }],
3024
3128
  isError: true
3025
3129
  }, null, 2));
3130
+ setHasRun(true);
3026
3131
  } finally {
3027
3132
  setIsRunning(false);
3028
3133
  }
3029
3134
  }, [
3030
3135
  onCallTool,
3031
3136
  onCallToolDirect,
3032
- state,
3033
- isInspectMode
3137
+ simulations,
3138
+ effectiveSimulationName,
3139
+ state
3034
3140
  ]);
3035
3141
  const activeShell = getHostShell(state.activeHost);
3036
3142
  const registeredHosts = getRegisteredHosts();
@@ -3064,8 +3170,8 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3064
3170
  } else prevPageStyleKeysRef.current = [];
3065
3171
  }, [activeShell]);
3066
3172
  const handleCallTool = react.useCallback((params) => {
3067
- if (!prodTools) {
3068
- const mock = state.selectedSim?.serverTools?.[params.name];
3173
+ if (activeSimulationName) {
3174
+ const mock = simulations[activeSimulationName]?.serverTools?.[params.name];
3069
3175
  if (mock) {
3070
3176
  const result = resolveServerToolResult(mock, params.arguments);
3071
3177
  if (result) return result;
@@ -3074,15 +3180,15 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3074
3180
  if (onCallTool) return onCallTool(params);
3075
3181
  return { content: [{
3076
3182
  type: "text",
3077
- text: `[Simulator] Tool "${params.name}" called — no serverTools mock found in simulation "${state.selectedSimulationName}".`
3183
+ text: `[Simulator] Tool "${params.name}" called — no serverTools mock found in simulation "${effectiveSimulationName}".`
3078
3184
  }] };
3079
3185
  }, [
3080
3186
  onCallTool,
3081
- prodTools,
3082
- state.selectedSim,
3083
- state.selectedSimulationName
3187
+ activeSimulationName,
3188
+ simulations,
3189
+ effectiveSimulationName
3084
3190
  ]);
3085
- const prodToolsUserMessage = prodTools && state.selectedSim ? `Call my ${state.selectedSim.tool.title || state.selectedSim.tool.name} tool` : void 0;
3191
+ const userMessage = currentSim ? currentSim.userMessage ?? `Call my ${currentSim.tool.title || currentSim.tool.name} tool` : void 0;
3086
3192
  const prodResourcesPath = react.useMemo(() => {
3087
3193
  if (!prodResources || !state.selectedSim?.resource) return void 0;
3088
3194
  const name = state.selectedSim.resource.name;
@@ -3123,10 +3229,23 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3123
3229
  }, [prodResourcesPath]);
3124
3230
  const effectiveResourceUrl = (prodResourcesPath && prodResourcesReady ? prodResourcesPath : void 0) ?? state.resourceUrl;
3125
3231
  const prodResourcesLoading = !!prodResourcesPath && !prodResourcesReady;
3126
- const showEmptyState = prodTools && !hasRun;
3232
+ const hasTools = toolNames.length > 0;
3233
+ const showEmptyState = !(activeSimulationName !== null && currentSim?.toolResult != null) && !hasRun;
3127
3234
  let content;
3128
3235
  const iframeBg = "var(--sim-bg-conversation, var(--color-background-primary, transparent))";
3129
- if (showEmptyState) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3236
+ if (!hasTools) {
3237
+ const isConnected = connection.status === "connected";
3238
+ const isError = connection.status === "error";
3239
+ content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3240
+ className: "h-full w-full flex items-center justify-center",
3241
+ style: { background: iframeBg },
3242
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3243
+ className: "text-sm text-center max-w-xs",
3244
+ style: { color: "var(--color-text-secondary)" },
3245
+ 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"
3246
+ })
3247
+ });
3248
+ } else if (showEmptyState) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3130
3249
  className: "h-full w-full flex items-center justify-center",
3131
3250
  style: { background: iframeBg },
3132
3251
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -3170,7 +3289,7 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3170
3289
  injectOpenAIRuntime: state.activeHost === "chatgpt",
3171
3290
  sandboxUrl,
3172
3291
  className: "h-full w-full"
3173
- }, `${state.activeHost}-${state.selectedSimulationName}-${prodResources}-${prodResourcesGeneration}`)
3292
+ }, `${state.activeHost}-${effectiveResourceUrl}-${prodResources}-${prodResourcesGeneration}`)
3174
3293
  });
3175
3294
  else if (!prodResources && state.resourceScript) content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3176
3295
  className: "h-full w-full",
@@ -3195,10 +3314,37 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3195
3314
  injectOpenAIRuntime: state.activeHost === "chatgpt",
3196
3315
  sandboxUrl,
3197
3316
  className: "h-full w-full"
3198
- }, `${state.activeHost}-${state.selectedSimulationName}`)
3317
+ }, `${state.activeHost}-${state.resourceScript}`)
3199
3318
  });
3200
3319
  else content = children;
3201
3320
  const applyTheme = activeShell?.applyTheme;
3321
+ const runButton = !demoMode && onCallTool && currentSim && activeSimulationName === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3322
+ type: "button",
3323
+ onClick: handleRun,
3324
+ disabled: isRunning,
3325
+ className: "rounded-full px-3 py-1 text-sm font-medium transition-opacity disabled:opacity-40 flex items-center gap-1.5 cursor-pointer",
3326
+ style: {
3327
+ backgroundColor: "var(--color-text-primary)",
3328
+ color: "var(--color-background-primary)"
3329
+ },
3330
+ children: [showCheck ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3331
+ width: "12",
3332
+ height: "12",
3333
+ viewBox: "0 0 12 12",
3334
+ fill: "none",
3335
+ stroke: "currentColor",
3336
+ strokeWidth: "2",
3337
+ strokeLinecap: "round",
3338
+ strokeLinejoin: "round",
3339
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6L5 9L10 3" })
3340
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3341
+ width: "10",
3342
+ height: "12",
3343
+ viewBox: "0 0 10 12",
3344
+ fill: "currentColor",
3345
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M0 0L10 6L0 12V0Z" })
3346
+ }), "Run"]
3347
+ }) : void 0;
3202
3348
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, {
3203
3349
  theme: state.theme,
3204
3350
  applyTheme,
@@ -3206,39 +3352,82 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3206
3352
  controls: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3207
3353
  className: "space-y-1",
3208
3354
  children: [
3209
- isInspectMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3355
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3210
3356
  label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3211
3357
  className: "flex items-center gap-1.5",
3212
- children: ["MCP Server", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3358
+ children: ["MCP Server", serverUrl && !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3213
3359
  className: "inline-block w-2 h-2 rounded-full",
3214
- "data-testid": "inspect-connection-status",
3360
+ "data-testid": "connection-status",
3215
3361
  style: { backgroundColor: connection.status === "connected" ? "#22c55e" : connection.status === "connecting" ? "#eab308" : connection.status === "error" ? "#ef4444" : "#6b7280" },
3216
3362
  title: connection.error ?? connection.status
3217
3363
  })]
3218
3364
  }),
3219
- tooltip: "MCP server URL (set via --server flag)",
3220
- "data-testid": "inspect-server-url",
3365
+ tooltip: "MCP server URL",
3366
+ "data-testid": "server-url",
3221
3367
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarInput, {
3222
- value: mcpServerUrl ?? "",
3223
- onChange: () => {},
3224
- disabled: true,
3225
- placeholder: "http://localhost:8000/mcp"
3368
+ value: demoMode ? "http://localhost:8000/mcp" : serverUrl,
3369
+ onChange: demoMode ? () => {} : setServerUrl,
3370
+ applyOnBlur: true,
3371
+ placeholder: "http://localhost:8000/mcp",
3372
+ disabled: demoMode
3226
3373
  })
3227
3374
  }),
3228
- !isInspectMode && !hideSimulatorModes && onCallTool && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
3229
- checked: prodTools,
3230
- onChange: setProdTools,
3231
- label: "Prod Tools",
3232
- tooltip: "Use real tool handlers instead of simulations",
3233
- docsPath: "api-reference/cli/dev#prod-tools-and-prod-resources-flags"
3234
- }),
3235
- !isInspectMode && !hideSimulatorModes && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
3375
+ !hideSimulatorModes && !demoMode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCheckbox, {
3236
3376
  checked: prodResources,
3237
3377
  onChange: setProdResources,
3238
3378
  label: "Prod Resources",
3239
3379
  tooltip: "Load resources from dist/ builds instead of HMR",
3240
3380
  docsPath: "api-reference/cli/dev#prod-tools-and-prod-resources-flags"
3241
3381
  }),
3382
+ hasTools && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3383
+ className: "grid grid-cols-2 gap-2",
3384
+ "data-testid": "tool-simulation-row",
3385
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3386
+ label: "Tool",
3387
+ tooltip: "Tool to inspect",
3388
+ docsPath: "api-reference/cli/dev",
3389
+ "data-testid": "tool-selector",
3390
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
3391
+ value: selectedToolName,
3392
+ onChange: (value) => setSelectedToolName(value),
3393
+ options: toolNames.map((name) => {
3394
+ return {
3395
+ value: name,
3396
+ label: toolMap.get(name).tool.title || name
3397
+ };
3398
+ })
3399
+ })
3400
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3401
+ label: selectedToolInfo && selectedToolInfo.fixtureSimNames.length > 0 ? "Simulation" : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3402
+ href: `${DOCS_BASE_URL}/api-reference/simulations/simulation`,
3403
+ target: "_blank",
3404
+ rel: "noopener noreferrer",
3405
+ className: "no-underline transition-colors",
3406
+ style: { color: "var(--color-text-secondary)" },
3407
+ onMouseEnter: (e) => {
3408
+ e.target.style.color = "var(--color-text-primary)";
3409
+ },
3410
+ onMouseLeave: (e) => {
3411
+ e.target.style.color = "var(--color-text-secondary)";
3412
+ },
3413
+ children: "Simulation"
3414
+ }),
3415
+ tooltip: selectedToolInfo && selectedToolInfo.fixtureSimNames.length > 0 ? "Test fixture with mock data" : "Create simulations for faster testing",
3416
+ docsPath: "api-reference/simulations/simulation",
3417
+ "data-testid": "simulation-selector",
3418
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
3419
+ value: activeSimulationName ?? "__none__",
3420
+ onChange: (value) => setActiveSimulationName(value === "__none__" ? null : value),
3421
+ options: [...demoMode ? [] : [{
3422
+ value: "__none__",
3423
+ label: selectedToolInfo && selectedToolInfo.fixtureSimNames.length > 0 ? "None (call server)" : "None"
3424
+ }], ...(selectedToolInfo?.fixtureSimNames ?? []).map((simName) => ({
3425
+ value: simName,
3426
+ label: simName
3427
+ }))]
3428
+ })
3429
+ })]
3430
+ }),
3242
3431
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3243
3432
  className: "grid grid-cols-2 gap-2",
3244
3433
  children: [registeredHosts.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
@@ -3281,34 +3470,6 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3281
3470
  })
3282
3471
  })]
3283
3472
  }),
3284
- prodTools && toolOptions.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3285
- label: "Tool",
3286
- tooltip: "Tool to call with prod handler",
3287
- docsPath: "api-reference/cli/dev",
3288
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
3289
- value: state.selectedSimulationName,
3290
- onChange: (value) => state.setSelectedSimulationName(value),
3291
- options: toolOptions
3292
- })
3293
- }),
3294
- !prodTools && state.simulationNames.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarControl, {
3295
- label: "Simulation",
3296
- tooltip: "Test fixture to render",
3297
- docsPath: "api-reference/simulations/simulation",
3298
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSelect, {
3299
- value: state.selectedSimulationName,
3300
- onChange: (value) => state.setSelectedSimulationName(value),
3301
- options: state.simulationNames.map((name) => {
3302
- const sim = simulations[name];
3303
- const resourceTitle = sim.resource ? sim.resource.title || sim.resource.name : void 0;
3304
- const toolTitle = sim.tool.title || sim.tool.name;
3305
- return {
3306
- value: name,
3307
- label: resourceTitle ? `${resourceTitle}: ${toolTitle}` : toolTitle
3308
- };
3309
- })
3310
- })
3311
- }),
3312
3473
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
3313
3474
  label: "Host Context",
3314
3475
  defaultCollapsed: false,
@@ -3590,7 +3751,7 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3590
3751
  }),
3591
3752
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
3592
3753
  label: "Tool Input (JSON)",
3593
- defaultCollapsed: !prodTools,
3754
+ defaultCollapsed: false,
3594
3755
  tooltip: "Arguments passed to the tool",
3595
3756
  docsPath: "api-reference/hooks/use-tool-data",
3596
3757
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTextarea, {
@@ -3601,10 +3762,10 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3601
3762
  error: state.toolInputError,
3602
3763
  maxRows: 8
3603
3764
  })
3604
- }, `tool-input-${prodTools}`),
3765
+ }),
3605
3766
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarCollapsibleControl, {
3606
3767
  label: "Tool Result (JSON)",
3607
- defaultCollapsed: prodTools,
3768
+ defaultCollapsed: false,
3608
3769
  tooltip: "Structured content returned by the tool",
3609
3770
  docsPath: "api-reference/hooks/use-tool-data",
3610
3771
  "data-testid": "tool-result-section",
@@ -3627,7 +3788,7 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3627
3788
  error: state.toolResultError,
3628
3789
  maxRows: 8
3629
3790
  })
3630
- }, `tool-result-${prodTools}`)
3791
+ })
3631
3792
  ]
3632
3793
  }),
3633
3794
  children: ShellConversation ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellConversation, {
@@ -3637,35 +3798,9 @@ function Simulator({ children, simulations = {}, appName = "Sunpeak", appIcon, d
3637
3798
  onRequestDisplayMode: state.handleDisplayModeChange,
3638
3799
  appName,
3639
3800
  appIcon,
3640
- userMessage: prodToolsUserMessage ?? state.selectedSim?.userMessage,
3801
+ userMessage,
3641
3802
  onContentWidthChange: state.handleContentWidthChange,
3642
- headerAction: prodTools && onCallTool ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3643
- type: "button",
3644
- onClick: handleRun,
3645
- disabled: isRunning,
3646
- className: "rounded-full px-3 py-1 text-sm font-medium transition-opacity disabled:opacity-40 flex items-center gap-1.5 cursor-pointer",
3647
- style: {
3648
- backgroundColor: "var(--color-text-primary)",
3649
- color: "var(--color-background-primary)"
3650
- },
3651
- children: [showCheck ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3652
- width: "12",
3653
- height: "12",
3654
- viewBox: "0 0 12 12",
3655
- fill: "none",
3656
- stroke: "currentColor",
3657
- strokeWidth: "2",
3658
- strokeLinecap: "round",
3659
- strokeLinejoin: "round",
3660
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 6L5 9L10 3" })
3661
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
3662
- width: "10",
3663
- height: "12",
3664
- viewBox: "0 0 10 12",
3665
- fill: "currentColor",
3666
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M0 0L10 6L0 12V0Z" })
3667
- }), "Run"]
3668
- }) : void 0,
3803
+ headerAction: runButton,
3669
3804
  children: content
3670
3805
  }) : content
3671
3806
  })
@@ -3805,4 +3940,4 @@ Object.defineProperty(exports, "useThemeContext", {
3805
3940
  }
3806
3941
  });
3807
3942
 
3808
- //# sourceMappingURL=simulator-CH9hs0N6.cjs.map
3943
+ //# sourceMappingURL=simulator-DqWETA_1.cjs.map