sunpeak 0.16.28 → 0.17.1

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 (141) hide show
  1. package/bin/commands/dev.mjs +169 -342
  2. package/bin/commands/inspect.mjs +763 -0
  3. package/bin/commands/new.mjs +2 -2
  4. package/bin/lib/inspect/inspect-config.d.mts +20 -0
  5. package/bin/lib/inspect/inspect-config.mjs +76 -0
  6. package/bin/lib/live/global-setup.mjs +6 -1
  7. package/bin/sunpeak.js +11 -1
  8. package/dist/chatgpt/globals.css +35 -18
  9. package/dist/chatgpt/index.cjs +3 -11
  10. package/dist/chatgpt/index.cjs.map +1 -1
  11. package/dist/chatgpt/index.d.ts +2 -2
  12. package/dist/chatgpt/index.js +4 -8
  13. package/dist/chatgpt/index.js.map +1 -1
  14. package/dist/claude/index.cjs +1 -1
  15. package/dist/claude/index.js +1 -1
  16. package/dist/discovery-Cgoegt62.js +114 -0
  17. package/dist/discovery-Cgoegt62.js.map +1 -0
  18. package/dist/discovery-Clu4uHp1.cjs +161 -0
  19. package/dist/discovery-Clu4uHp1.cjs.map +1 -0
  20. package/dist/index.cjs +1 -4
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.js +2 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/discovery-cli.cjs +1 -1
  25. package/dist/lib/discovery-cli.js +1 -1
  26. package/dist/lib/discovery.d.ts +7 -67
  27. package/dist/lib/index.d.ts +0 -1
  28. package/dist/mcp/index.cjs +34 -23
  29. package/dist/mcp/index.cjs.map +1 -1
  30. package/dist/mcp/index.js +34 -23
  31. package/dist/mcp/index.js.map +1 -1
  32. package/dist/mcp/types.d.ts +5 -0
  33. package/dist/simulator/index.cjs +5 -11
  34. package/dist/simulator/index.cjs.map +1 -1
  35. package/dist/simulator/index.d.ts +4 -2
  36. package/dist/simulator/index.js +5 -8
  37. package/dist/simulator/index.js.map +1 -1
  38. package/dist/simulator/simple-sidebar.d.ts +7 -4
  39. package/dist/simulator/simulator-url.d.ts +8 -0
  40. package/dist/simulator/simulator.d.ts +15 -2
  41. package/dist/simulator/use-mcp-connection.d.ts +19 -0
  42. package/dist/{simulator-BYIH-xqQ.cjs → simulator-CH9hs0N6.cjs} +159 -52
  43. package/dist/simulator-CH9hs0N6.cjs.map +1 -0
  44. package/dist/{simulator-CmgNnWBO.js → simulator-Dl8B-Ljb.js} +154 -53
  45. package/dist/simulator-Dl8B-Ljb.js.map +1 -0
  46. package/dist/{simulator-url-BDGD4vZD.cjs → simulator-url-CozKF1jf.cjs} +3 -1
  47. package/dist/simulator-url-CozKF1jf.cjs.map +1 -0
  48. package/dist/{simulator-url-Bkxj43yT.js → simulator-url-KoS_ToP6.js} +3 -1
  49. package/dist/simulator-url-KoS_ToP6.js.map +1 -0
  50. package/dist/style.css +35 -18
  51. package/package.json +9 -1
  52. package/template/dist/albums/albums.html +105 -0
  53. package/template/dist/albums/albums.json +16 -0
  54. package/template/dist/carousel/carousel.html +105 -0
  55. package/template/dist/carousel/carousel.json +16 -0
  56. package/template/dist/map/map.html +3060 -0
  57. package/template/dist/map/map.json +22 -0
  58. package/template/dist/review/review.html +105 -0
  59. package/template/dist/review/review.json +16 -0
  60. package/template/dist/server.js +15 -0
  61. package/template/dist/tools/review-diff.js +50 -0
  62. package/template/dist/tools/review-post.js +50 -0
  63. package/template/dist/tools/review-purchase.js +61 -0
  64. package/template/dist/tools/review.js +31 -0
  65. package/template/dist/tools/show-albums.js +56 -0
  66. package/template/dist/tools/show-carousel.js +41 -0
  67. package/template/dist/tools/show-map.js +47 -0
  68. package/template/node_modules/.vite/deps/_metadata.json +8 -0
  69. package/template/node_modules/.vite/deps/package.json +3 -0
  70. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +500 -0
  71. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -0
  72. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +563 -0
  73. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -0
  74. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +575 -0
  75. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -0
  76. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js +11363 -0
  77. package/template/node_modules/.vite-mcp/deps/@testing-library_react.js.map +1 -0
  78. package/template/node_modules/.vite-mcp/deps/_metadata.json +130 -0
  79. package/template/node_modules/.vite-mcp/deps/chunk-BoAXSpZd.js +33 -0
  80. package/template/node_modules/.vite-mcp/deps/client-CU1wWud4.js +14385 -0
  81. package/template/node_modules/.vite-mcp/deps/client-CU1wWud4.js.map +1 -0
  82. package/template/node_modules/.vite-mcp/deps/clsx.js +18 -0
  83. package/template/node_modules/.vite-mcp/deps/clsx.js.map +1 -0
  84. package/template/node_modules/.vite-mcp/deps/dist-uWX8WbjY.js +505 -0
  85. package/template/node_modules/.vite-mcp/deps/dist-uWX8WbjY.js.map +1 -0
  86. package/template/node_modules/.vite-mcp/deps/embla-carousel-react.js +1461 -0
  87. package/template/node_modules/.vite-mcp/deps/embla-carousel-react.js.map +1 -0
  88. package/template/node_modules/.vite-mcp/deps/embla-carousel-wheel-gestures.js +536 -0
  89. package/template/node_modules/.vite-mcp/deps/embla-carousel-wheel-gestures.js.map +1 -0
  90. package/template/node_modules/.vite-mcp/deps/magic-string.es-Cklsmr-5.js +1013 -0
  91. package/template/node_modules/.vite-mcp/deps/magic-string.es-Cklsmr-5.js.map +1 -0
  92. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js +46311 -0
  93. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js.map +1 -0
  94. package/template/node_modules/.vite-mcp/deps/package.json +3 -0
  95. package/template/node_modules/.vite-mcp/deps/protocol-CTflwIfG.js +2090 -0
  96. package/template/node_modules/.vite-mcp/deps/protocol-CTflwIfG.js.map +1 -0
  97. package/template/node_modules/.vite-mcp/deps/react-dom.js +186 -0
  98. package/template/node_modules/.vite-mcp/deps/react-dom.js.map +1 -0
  99. package/template/node_modules/.vite-mcp/deps/react-dom_client.js +2 -0
  100. package/template/node_modules/.vite-mcp/deps/react.js +769 -0
  101. package/template/node_modules/.vite-mcp/deps/react.js.map +1 -0
  102. package/template/node_modules/.vite-mcp/deps/react_jsx-dev-runtime.js +205 -0
  103. package/template/node_modules/.vite-mcp/deps/react_jsx-dev-runtime.js.map +1 -0
  104. package/template/node_modules/.vite-mcp/deps/react_jsx-runtime.js +209 -0
  105. package/template/node_modules/.vite-mcp/deps/react_jsx-runtime.js.map +1 -0
  106. package/template/node_modules/.vite-mcp/deps/schemas-NsgmY9QV.js +12157 -0
  107. package/template/node_modules/.vite-mcp/deps/schemas-NsgmY9QV.js.map +1 -0
  108. package/template/node_modules/.vite-mcp/deps/tailwind-merge.js +2025 -0
  109. package/template/node_modules/.vite-mcp/deps/tailwind-merge.js.map +1 -0
  110. package/template/node_modules/.vite-mcp/deps/vitest.js +14021 -0
  111. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -0
  112. package/template/node_modules/.vite-mcp/deps/zod.js +624 -0
  113. package/template/node_modules/.vite-mcp/deps/zod.js.map +1 -0
  114. package/template/src/tools/review-diff.test.ts +5 -1
  115. package/template/src/tools/review-diff.ts +1 -1
  116. package/template/src/tools/review-post.test.ts +5 -1
  117. package/template/src/tools/review-post.ts +1 -1
  118. package/template/src/tools/review-purchase.test.ts +5 -1
  119. package/template/src/tools/review-purchase.ts +1 -1
  120. package/template/src/tools/review.test.ts +5 -1
  121. package/template/src/tools/review.ts +1 -1
  122. package/template/src/tools/show-albums.test.ts +5 -1
  123. package/template/src/tools/show-albums.ts +1 -1
  124. package/template/src/tools/show-carousel.test.ts +5 -1
  125. package/template/src/tools/show-carousel.ts +1 -1
  126. package/template/src/tools/show-map.test.ts +5 -1
  127. package/template/src/tools/show-map.ts +1 -1
  128. package/template/tests/e2e/map.spec.ts +4 -2
  129. package/dist/discovery-BxKCIgG5.cjs +0 -332
  130. package/dist/discovery-BxKCIgG5.cjs.map +0 -1
  131. package/dist/discovery-Du4LHrih.js +0 -261
  132. package/dist/discovery-Du4LHrih.js.map +0 -1
  133. package/dist/simulator-BYIH-xqQ.cjs.map +0 -1
  134. package/dist/simulator-CmgNnWBO.js.map +0 -1
  135. package/dist/simulator-url-BDGD4vZD.cjs.map +0 -1
  136. package/dist/simulator-url-Bkxj43yT.js.map +0 -1
  137. package/template/.sunpeak/dev.tsx +0 -79
  138. package/template/.sunpeak/resource-loader.html +0 -20
  139. package/template/.sunpeak/resource-loader.tsx +0 -57
  140. package/template/index.html +0 -14
  141. package/template/src/resources/index.ts +0 -17
@@ -170,8 +170,8 @@ export async function init(projectName, resourcesArg, deps = defaultDeps) {
170
170
  filter: (src) => {
171
171
  const name = basename(src);
172
172
 
173
- // Skip node_modules and lock file
174
- if (name === 'node_modules' || name === 'pnpm-lock.yaml') {
173
+ // Skip node_modules, lock file, and legacy dev bootstrap files
174
+ if (name === 'node_modules' || name === 'pnpm-lock.yaml' || name === '.sunpeak' || name === 'index.html') {
175
175
  return false;
176
176
  }
177
177
 
@@ -0,0 +1,20 @@
1
+ export interface InspectConfigOptions {
2
+ /** MCP server URL or stdio command string (required) */
3
+ server: string;
4
+ /** Test directory (default: 'tests/e2e') */
5
+ testDir?: string;
6
+ /** Simulation JSON directory (opt-in, fixtures loaded only when specified) */
7
+ simulationsDir?: string;
8
+ /** Host shells to test (default: ['chatgpt', 'claude']) */
9
+ hosts?: ('chatgpt' | 'claude')[];
10
+ /** App name in simulator chrome */
11
+ name?: string;
12
+ /** Additional Playwright `use` options */
13
+ use?: Record<string, unknown>;
14
+ }
15
+
16
+ /**
17
+ * Create a complete Playwright config for testing an external MCP server
18
+ * using the sunpeak simulator.
19
+ */
20
+ export function defineInspectConfig(options: InspectConfigOptions): Record<string, unknown>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Playwright config factory for inspect mode (BYOS — Bring Your Own Server).
3
+ *
4
+ * Generates a complete Playwright config that starts `sunpeak inspect` as the
5
+ * webServer and runs e2e tests against the simulator. Follows the same pattern
6
+ * as `defineLiveConfig` for live tests.
7
+ *
8
+ * Usage in playwright.config.ts:
9
+ * import { defineInspectConfig } from 'sunpeak/test/inspect/config';
10
+ * export default defineInspectConfig({
11
+ * server: 'http://localhost:8000/mcp',
12
+ * });
13
+ */
14
+ import { getPortSync } from '../get-port.mjs';
15
+
16
+ /**
17
+ * Create a complete Playwright config for testing an external MCP server.
18
+ *
19
+ * @param {Object} options
20
+ * @param {string} options.server - MCP server URL or stdio command (required)
21
+ * @param {string} [options.testDir='tests/e2e'] - Test directory
22
+ * @param {string} [options.simulationsDir='tests/simulations'] - Simulation JSON directory
23
+ * @param {string[]} [options.hosts=['chatgpt', 'claude']] - Host shells to test
24
+ * @param {string} [options.name] - App name in simulator chrome
25
+ * @param {Object} [options.use] - Additional Playwright `use` options
26
+ * @returns {import('@playwright/test').PlaywrightTestConfig}
27
+ */
28
+ export function defineInspectConfig(options) {
29
+ const {
30
+ server,
31
+ testDir = 'tests/e2e',
32
+ simulationsDir,
33
+ hosts = ['chatgpt', 'claude'],
34
+ name,
35
+ use: userUse,
36
+ } = options;
37
+
38
+ if (!server) {
39
+ throw new Error('defineInspectConfig: `server` option is required');
40
+ }
41
+
42
+ const port = Number(process.env.SUNPEAK_TEST_PORT) || getPortSync(6776);
43
+ const sandboxPort = Number(process.env.SUNPEAK_SANDBOX_PORT) || getPortSync(24680);
44
+
45
+ // Build the sunpeak inspect command
46
+ const serverArg = server.includes(' ') ? `"${server}"` : server;
47
+ const command = [
48
+ 'npx sunpeak inspect',
49
+ `--server ${serverArg}`,
50
+ ...(simulationsDir ? [`--simulations ${simulationsDir}`] : []),
51
+ `--port ${port}`,
52
+ ...(name ? [`--name "${name}"`] : []),
53
+ ].join(' ');
54
+
55
+ return {
56
+ testDir,
57
+ fullyParallel: true,
58
+ forbidOnly: !!process.env.CI,
59
+ retries: process.env.CI ? 2 : 1,
60
+ // Limit workers to avoid overwhelming the double-iframe sandbox proxy.
61
+ workers: process.env.CI ? 1 : 2,
62
+ reporter: 'list',
63
+ use: {
64
+ baseURL: `http://localhost:${port}`,
65
+ trace: 'on-first-retry',
66
+ ...userUse,
67
+ },
68
+ projects: hosts.map((host) => ({ name: host })),
69
+ webServer: {
70
+ command: `SUNPEAK_SANDBOX_PORT=${sandboxPort} ${command}`,
71
+ url: `http://localhost:${port}/health`,
72
+ reuseExistingServer: !process.env.CI,
73
+ timeout: 60_000,
74
+ },
75
+ };
76
+ }
@@ -23,7 +23,12 @@ import { dirname } from 'path';
23
23
  import { ANTI_BOT_ARGS, CHROME_USER_AGENT, resolvePlaywright, getAppName } from './utils.mjs';
24
24
  import { ChatGPTPage, CHATGPT_SELECTORS, CHATGPT_URLS } from './chatgpt-page.mjs';
25
25
 
26
- /** Auth state expires after 24 hours — ChatGPT session cookies are short-lived. */
26
+ /**
27
+ * Auth file retention window. The file is kept for 24 hours, but sessions
28
+ * typically expire much sooner (a few hours) because Cloudflare's HttpOnly
29
+ * `cf_clearance` cookie cannot be captured by storageState(). When it
30
+ * expires, the next run will need re-authentication even if the file is fresh.
31
+ */
27
32
  const MAX_AGE_MS = 24 * 60 * 60 * 1000;
28
33
 
29
34
  const CHATGPT_URL = CHATGPT_URLS.base;
package/bin/sunpeak.js CHANGED
@@ -37,7 +37,7 @@ function getVersion() {
37
37
  }
38
38
 
39
39
  // Commands that don't require a package.json
40
- const standaloneCommands = ['new', 'upgrade', 'help', undefined];
40
+ const standaloneCommands = ['new', 'upgrade', 'inspect', 'help', undefined];
41
41
 
42
42
  if (command && !standaloneCommands.includes(command)) {
43
43
  checkPackageJson();
@@ -72,6 +72,13 @@ function getVersion() {
72
72
  }
73
73
  break;
74
74
 
75
+ case 'inspect':
76
+ {
77
+ const { inspect } = await import(join(COMMANDS_DIR, 'inspect.mjs'));
78
+ await inspect(args);
79
+ }
80
+ break;
81
+
75
82
  case 'upgrade':
76
83
  {
77
84
  const { upgrade } = await import(join(COMMANDS_DIR, 'upgrade.mjs'));
@@ -100,6 +107,9 @@ Usage:
100
107
  sunpeak build Build resources + tools for production
101
108
  sunpeak start Start production MCP server
102
109
  --port, -p Server port (default: 8000, or PORT env)
110
+ sunpeak inspect Test an external MCP server in the simulator
111
+ --server, -s <url|cmd> MCP server URL or stdio command (required)
112
+ --simulations <dir> Simulation JSON directory
103
113
  sunpeak upgrade Upgrade sunpeak to latest version
104
114
  sunpeak --version Show version number
105
115
 
@@ -806,6 +806,10 @@
806
806
  height: calc(var(--spacing) * 6);
807
807
  }
808
808
 
809
+ .h-2 {
810
+ height: calc(var(--spacing) * 2);
811
+ }
812
+
809
813
  .h-3 {
810
814
  height: calc(var(--spacing) * 3);
811
815
  }
@@ -898,6 +902,10 @@
898
902
  width: calc(var(--spacing) * 1);
899
903
  }
900
904
 
905
+ .w-2 {
906
+ width: calc(var(--spacing) * 2);
907
+ }
908
+
901
909
  .w-3 {
902
910
  width: calc(var(--spacing) * 3);
903
911
  }
@@ -938,10 +946,6 @@
938
946
  width: calc(var(--spacing) * 40);
939
947
  }
940
948
 
941
- .w-56 {
942
- width: calc(var(--spacing) * 56);
943
- }
944
-
945
949
  .w-\[18px\] {
946
950
  width: 18px;
947
951
  }
@@ -1056,10 +1060,6 @@
1056
1060
  grid-template-columns: repeat(7, minmax(0, 1fr));
1057
1061
  }
1058
1062
 
1059
- .grid-cols-\[1fr_auto_1fr\] {
1060
- grid-template-columns: 1fr auto 1fr;
1061
- }
1062
-
1063
1063
  .grid-cols-\[2fr_1fr\] {
1064
1064
  grid-template-columns: 2fr 1fr;
1065
1065
  }
@@ -1100,10 +1100,6 @@
1100
1100
  justify-content: flex-end;
1101
1101
  }
1102
1102
 
1103
- .justify-start {
1104
- justify-content: flex-start;
1105
- }
1106
-
1107
1103
  .gap-0\.5 {
1108
1104
  gap: calc(var(--spacing) * .5);
1109
1105
  }
@@ -1224,8 +1220,12 @@
1224
1220
  border-radius: 10px;
1225
1221
  }
1226
1222
 
1227
- .rounded-\[18px\] {
1228
- border-radius: 18px;
1223
+ .rounded-\[22px\] {
1224
+ border-radius: 22px;
1225
+ }
1226
+
1227
+ .rounded-\[28px\] {
1228
+ border-radius: 28px;
1229
1229
  }
1230
1230
 
1231
1231
  .rounded-full {
@@ -1470,10 +1470,18 @@
1470
1470
  padding: calc(var(--spacing) * 1.5);
1471
1471
  }
1472
1472
 
1473
+ .p-1\.25 {
1474
+ padding: calc(var(--spacing) * 1.25);
1475
+ }
1476
+
1473
1477
  .p-2 {
1474
1478
  padding: calc(var(--spacing) * 2);
1475
1479
  }
1476
1480
 
1481
+ .p-2\.5 {
1482
+ padding: calc(var(--spacing) * 2.5);
1483
+ }
1484
+
1477
1485
  .p-3 {
1478
1486
  padding: calc(var(--spacing) * 3);
1479
1487
  }
@@ -1640,6 +1648,11 @@
1640
1648
  font-size: 11px;
1641
1649
  }
1642
1650
 
1651
+ .leading-6 {
1652
+ --tw-leading: calc(var(--spacing) * 6);
1653
+ line-height: calc(var(--spacing) * 6);
1654
+ }
1655
+
1643
1656
  .leading-none {
1644
1657
  --tw-leading: 1;
1645
1658
  line-height: 1;
@@ -1750,13 +1763,13 @@
1750
1763
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1751
1764
  }
1752
1765
 
1753
- .shadow-\[0px_0px_0px_1px_\#fff3\,0px_4px_12px_rgba\(0\,0\,0\,0\.12\)\] {
1754
- --tw-shadow: 0px 0px 0px 1px var(--tw-shadow-color, #fff3), 0px 4px 12px var(--tw-shadow-color, #0000001f);
1766
+ .shadow-\[0px_0px_0px_1px_var\(--color-border-primary\)\,0px_4px_12px_rgba\(0\,0\,0\,0\.12\)\] {
1767
+ --tw-shadow: 0px 0px 0px 1px var(--tw-shadow-color, var(--color-border-primary)), 0px 4px 12px var(--tw-shadow-color, #0000001f);
1755
1768
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1756
1769
  }
1757
1770
 
1758
- .shadow-\[0px_0px_0px_1px_\#fff3\,0px_6px_20px_rgba\(0\,0\,0\,0\.1\)\] {
1759
- --tw-shadow: 0px 0px 0px 1px var(--tw-shadow-color, #fff3), 0px 6px 20px var(--tw-shadow-color, #0000001a);
1771
+ .shadow-\[0px_0px_0px_1px_var\(--color-border-primary\)\,0px_6px_20px_rgba\(0\,0\,0\,0\.1\)\] {
1772
+ --tw-shadow: 0px 0px 0px 1px var(--tw-shadow-color, var(--color-border-primary)), 0px 6px 20px var(--tw-shadow-color, #0000001a);
1760
1773
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1761
1774
  }
1762
1775
 
@@ -2089,6 +2102,10 @@
2089
2102
  }
2090
2103
 
2091
2104
  @media (min-width: 48rem) {
2105
+ .md\:-start-6 {
2106
+ inset-inline-start: calc(var(--spacing) * -6);
2107
+ }
2108
+
2092
2109
  .md\:z-20 {
2093
2110
  z-index: 20;
2094
2111
  }
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-9hOWP6kD.cjs");
3
3
  require("../protocol-jbxhzcnS.cjs");
4
- const require_simulator = require("../simulator-BYIH-xqQ.cjs");
5
- const require_discovery = require("../discovery-BxKCIgG5.cjs");
6
- const require_simulator_url = require("../simulator-url-BDGD4vZD.cjs");
4
+ const require_simulator = require("../simulator-CH9hs0N6.cjs");
5
+ const require_simulator_url = require("../simulator-url-CozKF1jf.cjs");
6
+ const require_discovery = require("../discovery-Clu4uHp1.cjs");
7
7
  //#region src/chatgpt/index.ts
8
8
  var chatgpt_exports = /* @__PURE__ */ require_chunk.__exportAll({
9
9
  IframeResource: () => require_simulator.IframeResource,
@@ -11,10 +11,6 @@ var chatgpt_exports = /* @__PURE__ */ require_chunk.__exportAll({
11
11
  SCREEN_WIDTHS: () => require_simulator.SCREEN_WIDTHS,
12
12
  Simulator: () => require_simulator.Simulator,
13
13
  ThemeProvider: () => require_simulator.ThemeProvider,
14
- buildDevSimulations: () => require_discovery.buildDevSimulations,
15
- buildResourceMap: () => require_discovery.buildResourceMap,
16
- buildSimulations: () => require_discovery.buildSimulations,
17
- createResourceExports: () => require_discovery.createResourceExports,
18
14
  createSimulatorUrl: () => require_simulator_url.createSimulatorUrl,
19
15
  extractResourceCSP: () => require_simulator.extractResourceCSP,
20
16
  extractResourceKey: () => require_discovery.extractResourceKey,
@@ -32,16 +28,12 @@ exports.McpAppHost = require_simulator.McpAppHost;
32
28
  exports.SCREEN_WIDTHS = require_simulator.SCREEN_WIDTHS;
33
29
  exports.Simulator = require_simulator.Simulator;
34
30
  exports.ThemeProvider = require_simulator.ThemeProvider;
35
- exports.buildDevSimulations = require_discovery.buildDevSimulations;
36
- exports.buildResourceMap = require_discovery.buildResourceMap;
37
- exports.buildSimulations = require_discovery.buildSimulations;
38
31
  Object.defineProperty(exports, "chatgpt_exports", {
39
32
  enumerable: true,
40
33
  get: function() {
41
34
  return chatgpt_exports;
42
35
  }
43
36
  });
44
- exports.createResourceExports = require_discovery.createResourceExports;
45
37
  exports.createSimulatorUrl = require_simulator_url.createSimulatorUrl;
46
38
  exports.extractResourceCSP = require_simulator.extractResourceCSP;
47
39
  exports.extractResourceKey = require_discovery.extractResourceKey;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/chatgpt/index.ts"],"sourcesContent":["/**\n * ChatGPT-specific exports for the Sunpeak simulator.\n *\n * @module sunpeak/chatgpt\n */\n\n// Register ChatGPT host shell (side effect)\nimport './chatgpt-host';\n\n// Simulator\nexport { Simulator } from '../simulator/simulator';\n\n// Simulator types\nexport type { Simulation, ServerToolMock } from '../types/simulation';\nexport { resolveServerToolResult } from '../types/simulation';\nexport type { ScreenWidth, SimulatorConfig } from '../simulator/simulator-types';\nexport { SCREEN_WIDTHS } from '../simulator/simulator-types';\n\n// Host bridge (for building custom simulators or test harnesses)\nexport { McpAppHost } from '../simulator/mcp-app-host';\nexport type { McpAppHostOptions } from '../simulator/mcp-app-host';\n\n// Iframe rendering (used internally by simulator)\nexport { IframeResource, extractResourceCSP } from '../simulator/iframe-resource';\nexport type { ResourceCSP } from '../simulator/iframe-resource';\n\n// Theme provider\nexport * from '../simulator/theme-provider';\n\n// URL helpers\nexport { createSimulatorUrl } from '../simulator/simulator-url';\nexport type { SimulatorUrlParams } from '../simulator/simulator-url';\n\n// Discovery utilities for building simulations\nexport {\n buildDevSimulations,\n buildSimulations,\n buildResourceMap,\n createResourceExports,\n toPascalCase,\n extractResourceKey,\n extractSimulationKey,\n findResourceKey,\n getComponentName,\n findResourceDirs,\n} from '../lib/discovery';\nexport type {\n BuildSimulationsOptions,\n BuildDevSimulationsOptions,\n ResourceMetadata,\n ResourceDirInfo,\n FsOps,\n} from '../lib/discovery';\n"],"mappings":""}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/chatgpt/index.ts"],"sourcesContent":["/**\n * ChatGPT-specific exports for the Sunpeak simulator.\n *\n * @module sunpeak/chatgpt\n */\n\n// Register ChatGPT host shell (side effect)\nimport './chatgpt-host';\n\n// Simulator\nexport { Simulator } from '../simulator/simulator';\n\n// Simulator types\nexport type { Simulation, ServerToolMock } from '../types/simulation';\nexport { resolveServerToolResult } from '../types/simulation';\nexport type { ScreenWidth, SimulatorConfig } from '../simulator/simulator-types';\nexport { SCREEN_WIDTHS } from '../simulator/simulator-types';\n\n// Host bridge (for building custom simulators or test harnesses)\nexport { McpAppHost } from '../simulator/mcp-app-host';\nexport type { McpAppHostOptions } from '../simulator/mcp-app-host';\n\n// Iframe rendering (used internally by simulator)\nexport { IframeResource, extractResourceCSP } from '../simulator/iframe-resource';\nexport type { ResourceCSP } from '../simulator/iframe-resource';\n\n// Theme provider\nexport * from '../simulator/theme-provider';\n\n// URL helpers\nexport { createSimulatorUrl } from '../simulator/simulator-url';\nexport type { SimulatorUrlParams } from '../simulator/simulator-url';\n\n// Discovery utilities\nexport {\n toPascalCase,\n extractResourceKey,\n extractSimulationKey,\n findResourceKey,\n getComponentName,\n findResourceDirs,\n} from '../lib/discovery';\nexport type { ResourceDirInfo, FsOps } from '../lib/discovery';\n"],"mappings":""}
@@ -10,5 +10,5 @@ export type { ResourceCSP } from '../simulator/iframe-resource';
10
10
  export * from '../simulator/theme-provider';
11
11
  export { createSimulatorUrl } from '../simulator/simulator-url';
12
12
  export type { SimulatorUrlParams } from '../simulator/simulator-url';
13
- export { buildDevSimulations, buildSimulations, buildResourceMap, createResourceExports, toPascalCase, extractResourceKey, extractSimulationKey, findResourceKey, getComponentName, findResourceDirs, } from '../lib/discovery';
14
- export type { BuildSimulationsOptions, BuildDevSimulationsOptions, ResourceMetadata, ResourceDirInfo, FsOps, } from '../lib/discovery';
13
+ export { toPascalCase, extractResourceKey, extractSimulationKey, findResourceKey, getComponentName, findResourceDirs, } from '../lib/discovery';
14
+ export type { ResourceDirInfo, FsOps } from '../lib/discovery';
@@ -1,8 +1,8 @@
1
1
  import { r as __exportAll } from "../chunk-D6g4UhsZ.js";
2
2
  import "../protocol-DJmRaBzO.js";
3
- import { _ as SCREEN_WIDTHS, d as ThemeProvider, f as useThemeContext, g as McpAppHost, h as extractResourceCSP, m as IframeResource, n as resolveServerToolResult, t as Simulator } from "../simulator-CmgNnWBO.js";
4
- import { a as extractResourceKey, c as findResourceKey, d as getComponentName, f as toPascalCase, i as createResourceExports, n as buildResourceMap, o as extractSimulationKey, r as buildSimulations, s as findResourceDirs, t as buildDevSimulations } from "../discovery-Du4LHrih.js";
5
- import { t as createSimulatorUrl } from "../simulator-url-Bkxj43yT.js";
3
+ import { _ as McpAppHost, d as ThemeProvider, f as useThemeContext, g as extractResourceCSP, h as IframeResource, n as resolveServerToolResult, t as Simulator, v as SCREEN_WIDTHS } from "../simulator-Dl8B-Ljb.js";
4
+ import { t as createSimulatorUrl } from "../simulator-url-KoS_ToP6.js";
5
+ import { c as toPascalCase, i as findResourceKey, n as extractSimulationKey, r as findResourceDirs, s as getComponentName, t as extractResourceKey } from "../discovery-Cgoegt62.js";
6
6
  //#region src/chatgpt/index.ts
7
7
  var chatgpt_exports = /* @__PURE__ */ __exportAll({
8
8
  IframeResource: () => IframeResource,
@@ -10,10 +10,6 @@ var chatgpt_exports = /* @__PURE__ */ __exportAll({
10
10
  SCREEN_WIDTHS: () => SCREEN_WIDTHS,
11
11
  Simulator: () => Simulator,
12
12
  ThemeProvider: () => ThemeProvider,
13
- buildDevSimulations: () => buildDevSimulations,
14
- buildResourceMap: () => buildResourceMap,
15
- buildSimulations: () => buildSimulations,
16
- createResourceExports: () => createResourceExports,
17
13
  createSimulatorUrl: () => createSimulatorUrl,
18
14
  extractResourceCSP: () => extractResourceCSP,
19
15
  extractResourceKey: () => extractResourceKey,
@@ -26,6 +22,6 @@ var chatgpt_exports = /* @__PURE__ */ __exportAll({
26
22
  useThemeContext: () => useThemeContext
27
23
  });
28
24
  //#endregion
29
- export { IframeResource, McpAppHost, SCREEN_WIDTHS, Simulator, ThemeProvider, buildDevSimulations, buildResourceMap, buildSimulations, createResourceExports, createSimulatorUrl, extractResourceCSP, extractResourceKey, extractSimulationKey, findResourceDirs, findResourceKey, getComponentName, resolveServerToolResult, chatgpt_exports as t, toPascalCase, useThemeContext };
25
+ export { IframeResource, McpAppHost, SCREEN_WIDTHS, Simulator, ThemeProvider, createSimulatorUrl, extractResourceCSP, extractResourceKey, extractSimulationKey, findResourceDirs, findResourceKey, getComponentName, resolveServerToolResult, chatgpt_exports as t, toPascalCase, useThemeContext };
30
26
 
31
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/chatgpt/index.ts"],"sourcesContent":["/**\n * ChatGPT-specific exports for the Sunpeak simulator.\n *\n * @module sunpeak/chatgpt\n */\n\n// Register ChatGPT host shell (side effect)\nimport './chatgpt-host';\n\n// Simulator\nexport { Simulator } from '../simulator/simulator';\n\n// Simulator types\nexport type { Simulation, ServerToolMock } from '../types/simulation';\nexport { resolveServerToolResult } from '../types/simulation';\nexport type { ScreenWidth, SimulatorConfig } from '../simulator/simulator-types';\nexport { SCREEN_WIDTHS } from '../simulator/simulator-types';\n\n// Host bridge (for building custom simulators or test harnesses)\nexport { McpAppHost } from '../simulator/mcp-app-host';\nexport type { McpAppHostOptions } from '../simulator/mcp-app-host';\n\n// Iframe rendering (used internally by simulator)\nexport { IframeResource, extractResourceCSP } from '../simulator/iframe-resource';\nexport type { ResourceCSP } from '../simulator/iframe-resource';\n\n// Theme provider\nexport * from '../simulator/theme-provider';\n\n// URL helpers\nexport { createSimulatorUrl } from '../simulator/simulator-url';\nexport type { SimulatorUrlParams } from '../simulator/simulator-url';\n\n// Discovery utilities for building simulations\nexport {\n buildDevSimulations,\n buildSimulations,\n buildResourceMap,\n createResourceExports,\n toPascalCase,\n extractResourceKey,\n extractSimulationKey,\n findResourceKey,\n getComponentName,\n findResourceDirs,\n} from '../lib/discovery';\nexport type {\n BuildSimulationsOptions,\n BuildDevSimulationsOptions,\n ResourceMetadata,\n ResourceDirInfo,\n FsOps,\n} from '../lib/discovery';\n"],"mappings":""}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/chatgpt/index.ts"],"sourcesContent":["/**\n * ChatGPT-specific exports for the Sunpeak simulator.\n *\n * @module sunpeak/chatgpt\n */\n\n// Register ChatGPT host shell (side effect)\nimport './chatgpt-host';\n\n// Simulator\nexport { Simulator } from '../simulator/simulator';\n\n// Simulator types\nexport type { Simulation, ServerToolMock } from '../types/simulation';\nexport { resolveServerToolResult } from '../types/simulation';\nexport type { ScreenWidth, SimulatorConfig } from '../simulator/simulator-types';\nexport { SCREEN_WIDTHS } from '../simulator/simulator-types';\n\n// Host bridge (for building custom simulators or test harnesses)\nexport { McpAppHost } from '../simulator/mcp-app-host';\nexport type { McpAppHostOptions } from '../simulator/mcp-app-host';\n\n// Iframe rendering (used internally by simulator)\nexport { IframeResource, extractResourceCSP } from '../simulator/iframe-resource';\nexport type { ResourceCSP } from '../simulator/iframe-resource';\n\n// Theme provider\nexport * from '../simulator/theme-provider';\n\n// URL helpers\nexport { createSimulatorUrl } from '../simulator/simulator-url';\nexport type { SimulatorUrlParams } from '../simulator/simulator-url';\n\n// Discovery utilities\nexport {\n toPascalCase,\n extractResourceKey,\n extractSimulationKey,\n findResourceKey,\n getComponentName,\n findResourceDirs,\n} from '../lib/discovery';\nexport type { ResourceDirInfo, FsOps } from '../lib/discovery';\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../chunk-9hOWP6kD.cjs");
3
3
  require("../protocol-jbxhzcnS.cjs");
4
- const require_simulator = require("../simulator-BYIH-xqQ.cjs");
4
+ const require_simulator = require("../simulator-CH9hs0N6.cjs");
5
5
  exports.Simulator = require_simulator.Simulator;
@@ -1,3 +1,3 @@
1
1
  import "../protocol-DJmRaBzO.js";
2
- import { t as Simulator } from "../simulator-CmgNnWBO.js";
2
+ import { t as Simulator } from "../simulator-Dl8B-Ljb.js";
3
3
  export { Simulator };
@@ -0,0 +1,114 @@
1
+ //#region src/lib/discovery.ts
2
+ /**
3
+ * Discovery utilities for resources, tools, and simulations.
4
+ *
5
+ * String helpers (toPascalCase, extractResourceKey, etc.) are used by both
6
+ * browser and Node.js code. Node.js utilities (findResourceDirs, findToolFiles,
7
+ * etc.) are used by CLI commands for build-time and runtime discovery.
8
+ */
9
+ /**
10
+ * Convert a kebab-case string to PascalCase
11
+ * @example toPascalCase('review') // 'Review'
12
+ * @example toPascalCase('album-art') // 'AlbumArt'
13
+ */
14
+ function toPascalCase(str) {
15
+ return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
16
+ }
17
+ /**
18
+ * Extract the resource key from a resource file path.
19
+ * Matches {name}.tsx (e.g., './albums/albums.tsx' → 'albums')
20
+ */
21
+ function extractResourceKey(path) {
22
+ const match = path.match(/([^/]+)\.tsx$/);
23
+ return match ? match[1] : void 0;
24
+ }
25
+ /**
26
+ * Extract the simulation key from a simulation file path.
27
+ * Matches any *.json file (e.g., './show-albums.json' → 'show-albums')
28
+ */
29
+ function extractSimulationKey(path) {
30
+ const match = path.match(/([^/]+)\.json$/);
31
+ return match ? match[1] : void 0;
32
+ }
33
+ /**
34
+ * Find the best matching resource key for a simulation key.
35
+ * Matches the longest resource name that is a prefix of the simulation key.
36
+ * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'
37
+ * @example findResourceKey('albums', ['albums', 'review']) // 'albums'
38
+ */
39
+ function findResourceKey(simulationKey, resourceKeys) {
40
+ const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);
41
+ for (const resourceKey of sorted) if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + "-")) return resourceKey;
42
+ }
43
+ /**
44
+ * Get the expected component export name for a resource
45
+ * @example getComponentName('review') // 'ReviewResource'
46
+ * @example getComponentName('album-art') // 'AlbumArtResource'
47
+ */
48
+ function getComponentName(resourceKey) {
49
+ return `${toPascalCase(resourceKey)}Resource`;
50
+ }
51
+ /**
52
+ * Find all resource directories in a base directory.
53
+ * Each valid resource directory contains a file matching the expected pattern.
54
+ *
55
+ * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')
56
+ * @param filePattern - Function to generate expected filename from resource key
57
+ * @param fs - File system operations (for testing)
58
+ *
59
+ * @example
60
+ * // Find source resources (tsx files)
61
+ * const resources = findResourceDirs('src/resources', key => `${key}.tsx`);
62
+ *
63
+ * @example
64
+ * // Find built resources (js files)
65
+ * const resources = findResourceDirs('dist', key => `${key}.js`);
66
+ */
67
+ function findResourceDirs(baseDir, filePattern, fs) {
68
+ if (!fs.existsSync(baseDir)) return [];
69
+ return fs.readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
70
+ const key = entry.name;
71
+ const dir = `${baseDir}/${key}`;
72
+ const resourcePath = `${dir}/${filePattern(key)}`;
73
+ if (!fs.existsSync(resourcePath)) return null;
74
+ return {
75
+ key,
76
+ dir,
77
+ resourcePath
78
+ };
79
+ }).filter((info) => info !== null);
80
+ }
81
+ /**
82
+ * Find all tool files in a tools directory.
83
+ * Matches *.ts files directly in the directory (not recursive).
84
+ *
85
+ * @example
86
+ * findToolFiles('src/tools', fs)
87
+ * // [{ name: 'show-albums', path: 'src/tools/show-albums.ts' }]
88
+ */
89
+ function findToolFiles(toolsDir, fs) {
90
+ if (!fs.existsSync(toolsDir)) return [];
91
+ return fs.readdirSync(toolsDir, { withFileTypes: true }).filter((entry) => !entry.isDirectory() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")).map((entry) => ({
92
+ name: entry.name.replace(/\.ts$/, ""),
93
+ path: `${toolsDir}/${entry.name}`
94
+ }));
95
+ }
96
+ /**
97
+ * Find all simulation JSON files in a flat simulations directory.
98
+ * Matches any *.json file directly in the directory.
99
+ *
100
+ * @example
101
+ * findSimulationFilesFlat('tests/simulations', fs)
102
+ * // [{ name: 'show-albums', path: 'tests/simulations/show-albums.json' }]
103
+ */
104
+ function findSimulationFilesFlat(simulationsDir, fs) {
105
+ if (!fs.existsSync(simulationsDir)) return [];
106
+ return fs.readdirSync(simulationsDir, { withFileTypes: true }).filter((entry) => !entry.isDirectory() && entry.name.endsWith(".json")).map((entry) => ({
107
+ name: entry.name.replace(/\.json$/, ""),
108
+ path: `${simulationsDir}/${entry.name}`
109
+ }));
110
+ }
111
+ //#endregion
112
+ export { findSimulationFilesFlat as a, toPascalCase as c, findResourceKey as i, extractSimulationKey as n, findToolFiles as o, findResourceDirs as r, getComponentName as s, extractResourceKey as t };
113
+
114
+ //# sourceMappingURL=discovery-Cgoegt62.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-Cgoegt62.js","names":[],"sources":["../src/lib/discovery.ts"],"sourcesContent":["/**\n * Discovery utilities for resources, tools, and simulations.\n *\n * String helpers (toPascalCase, extractResourceKey, etc.) are used by both\n * browser and Node.js code. Node.js utilities (findResourceDirs, findToolFiles,\n * etc.) are used by CLI commands for build-time and runtime discovery.\n */\n\n/**\n * Convert a kebab-case string to PascalCase\n * @example toPascalCase('review') // 'Review'\n * @example toPascalCase('album-art') // 'AlbumArt'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract the resource key from a resource file path.\n * Matches {name}.tsx (e.g., './albums/albums.tsx' → 'albums')\n */\nexport function extractResourceKey(path: string): string | undefined {\n const match = path.match(/([^/]+)\\.tsx$/);\n return match ? match[1] : undefined;\n}\n\n/**\n * Extract the simulation key from a simulation file path.\n * Matches any *.json file (e.g., './show-albums.json' → 'show-albums')\n */\nexport function extractSimulationKey(path: string): string | undefined {\n const match = path.match(/([^/]+)\\.json$/);\n return match ? match[1] : undefined;\n}\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'\n * @example findResourceKey('albums', ['albums', 'review']) // 'albums'\n */\nexport function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\n/**\n * Get the expected component export name for a resource\n * @example getComponentName('review') // 'ReviewResource'\n * @example getComponentName('album-art') // 'AlbumArtResource'\n */\nexport function getComponentName(resourceKey: string): string {\n return `${toPascalCase(resourceKey)}Resource`;\n}\n\n// --- Node.js utilities for CLI commands ---\n// These utilities use standard Node.js APIs and can be imported by build/push/mcp commands.\n\n/**\n * Information about a discovered resource directory\n */\nexport interface ResourceDirInfo {\n /** Resource key (directory name), e.g., 'albums', 'carousel' */\n key: string;\n /** Full path to the resource directory */\n dir: string;\n /** Full path to the main resource file (tsx or json depending on context) */\n resourcePath: string;\n}\n\n/**\n * File system operations interface for dependency injection in tests\n */\nexport interface FsOps {\n readdirSync: (\n path: string,\n options: { withFileTypes: true }\n ) => Array<{ name: string; isDirectory: () => boolean }>;\n existsSync: (path: string) => boolean;\n}\n\n/**\n * Find all resource directories in a base directory.\n * Each valid resource directory contains a file matching the expected pattern.\n *\n * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')\n * @param filePattern - Function to generate expected filename from resource key\n * @param fs - File system operations (for testing)\n *\n * @example\n * // Find source resources (tsx files)\n * const resources = findResourceDirs('src/resources', key => `${key}.tsx`);\n *\n * @example\n * // Find built resources (js files)\n * const resources = findResourceDirs('dist', key => `${key}.js`);\n */\nexport function findResourceDirs(\n baseDir: string,\n filePattern: (key: string) => string,\n fs: FsOps\n): ResourceDirInfo[] {\n if (!fs.existsSync(baseDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(baseDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const key = entry.name;\n const dir = `${baseDir}/${key}`;\n const resourcePath = `${dir}/${filePattern(key)}`;\n\n if (!fs.existsSync(resourcePath)) {\n return null;\n }\n\n return { key, dir, resourcePath };\n })\n .filter((info): info is ResourceDirInfo => info !== null);\n}\n\n// --- Tool files + flat simulations discovery ---\n\n/**\n * Information about a discovered tool file\n */\nexport interface ToolFileInfo {\n /** Tool name derived from filename (e.g., 'show-albums') */\n name: string;\n /** Full path to the tool file */\n path: string;\n}\n\n/**\n * Find all tool files in a tools directory.\n * Matches *.ts files directly in the directory (not recursive).\n *\n * @example\n * findToolFiles('src/tools', fs)\n * // [{ name: 'show-albums', path: 'src/tools/show-albums.ts' }]\n */\nexport function findToolFiles(\n toolsDir: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): ToolFileInfo[] {\n if (!fs.existsSync(toolsDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(toolsDir, { withFileTypes: true });\n\n return entries\n .filter(\n (entry) =>\n !entry.isDirectory() && entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')\n )\n .map((entry) => ({\n name: entry.name.replace(/\\.ts$/, ''),\n path: `${toolsDir}/${entry.name}`,\n }));\n}\n\n/**\n * Information about a discovered simulation file (flat convention)\n */\nexport interface SimulationFileInfo {\n /** Filename without extension (e.g., 'show-albums') */\n name: string;\n /** Full path to the simulation file */\n path: string;\n}\n\n/**\n * Find all simulation JSON files in a flat simulations directory.\n * Matches any *.json file directly in the directory.\n *\n * @example\n * findSimulationFilesFlat('tests/simulations', fs)\n * // [{ name: 'show-albums', path: 'tests/simulations/show-albums.json' }]\n */\nexport function findSimulationFilesFlat(\n simulationsDir: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): SimulationFileInfo[] {\n if (!fs.existsSync(simulationsDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(simulationsDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => !entry.isDirectory() && entry.name.endsWith('.json'))\n .map((entry) => ({\n name: entry.name.replace(/\\.json$/, ''),\n path: `${simulationsDir}/${entry.name}`,\n }));\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,aAAa,KAAqB;AAChD,QAAO,IACJ,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;;;;;;AAOb,SAAgB,mBAAmB,MAAkC;CACnE,MAAM,QAAQ,KAAK,MAAM,gBAAgB;AACzC,QAAO,QAAQ,MAAM,KAAK,KAAA;;;;;;AAO5B,SAAgB,qBAAqB,MAAkC;CACrE,MAAM,QAAQ,KAAK,MAAM,iBAAiB;AAC1C,QAAO,QAAQ,MAAM,KAAK,KAAA;;;;;;;;AAS5B,SAAgB,gBAAgB,eAAuB,cAA4C;CAEjG,MAAM,SAAS,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO;AACpE,MAAK,MAAM,eAAe,OACxB,KAAI,kBAAkB,eAAe,cAAc,WAAW,cAAc,IAAI,CAC9E,QAAO;;;;;;;AAWb,SAAgB,iBAAiB,aAA6B;AAC5D,QAAO,GAAG,aAAa,YAAY,CAAC;;;;;;;;;;;;;;;;;;AA6CtC,SAAgB,iBACd,SACA,aACA,IACmB;AACnB,KAAI,CAAC,GAAG,WAAW,QAAQ,CACzB,QAAO,EAAE;AAKX,QAFgB,GAAG,YAAY,SAAS,EAAE,eAAe,MAAM,CAAC,CAG7D,QAAQ,UAAU,MAAM,aAAa,CAAC,CACtC,KAAK,UAAU;EACd,MAAM,MAAM,MAAM;EAClB,MAAM,MAAM,GAAG,QAAQ,GAAG;EAC1B,MAAM,eAAe,GAAG,IAAI,GAAG,YAAY,IAAI;AAE/C,MAAI,CAAC,GAAG,WAAW,aAAa,CAC9B,QAAO;AAGT,SAAO;GAAE;GAAK;GAAK;GAAc;GACjC,CACD,QAAQ,SAAkC,SAAS,KAAK;;;;;;;;;;AAuB7D,SAAgB,cACd,UACA,IACgB;AAChB,KAAI,CAAC,GAAG,WAAW,SAAS,CAC1B,QAAO,EAAE;AAKX,QAFgB,GAAG,YAAY,UAAU,EAAE,eAAe,MAAM,CAAC,CAG9D,QACE,UACC,CAAC,MAAM,aAAa,IAAI,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC,MAAM,KAAK,SAAS,WAAW,CACzF,CACA,KAAK,WAAW;EACf,MAAM,MAAM,KAAK,QAAQ,SAAS,GAAG;EACrC,MAAM,GAAG,SAAS,GAAG,MAAM;EAC5B,EAAE;;;;;;;;;;AAqBP,SAAgB,wBACd,gBACA,IACsB;AACtB,KAAI,CAAC,GAAG,WAAW,eAAe,CAChC,QAAO,EAAE;AAKX,QAFgB,GAAG,YAAY,gBAAgB,EAAE,eAAe,MAAM,CAAC,CAGpE,QAAQ,UAAU,CAAC,MAAM,aAAa,IAAI,MAAM,KAAK,SAAS,QAAQ,CAAC,CACvE,KAAK,WAAW;EACf,MAAM,MAAM,KAAK,QAAQ,WAAW,GAAG;EACvC,MAAM,GAAG,eAAe,GAAG,MAAM;EAClC,EAAE"}