@xdelivered/emberflow 0.2.0

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 (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/bin/commands.test.ts +185 -0
  4. package/bin/commands.ts +306 -0
  5. package/bin/emberflow.mjs +38 -0
  6. package/bin/init.test.ts +165 -0
  7. package/bin/init.ts +297 -0
  8. package/bin/runtime.mjs +105 -0
  9. package/bin/runtime.test.ts +167 -0
  10. package/bin/skillTargets.test.ts +108 -0
  11. package/bin/skillTargets.ts +36 -0
  12. package/bin/skillTemplates.test.ts +37 -0
  13. package/bin/tsconfig.json +23 -0
  14. package/dist/bin/commands.d.ts +24 -0
  15. package/dist/bin/commands.js +252 -0
  16. package/dist/bin/init.d.ts +17 -0
  17. package/dist/bin/init.js +241 -0
  18. package/dist/bin/runtime.d.mts +39 -0
  19. package/dist/bin/runtime.mjs +96 -0
  20. package/dist/bin/skillTargets.d.ts +13 -0
  21. package/dist/bin/skillTargets.js +22 -0
  22. package/dist/server/agents/claudeAdapter.d.ts +8 -0
  23. package/dist/server/agents/claudeAdapter.js +190 -0
  24. package/dist/server/agents/claudeParse.d.ts +2 -0
  25. package/dist/server/agents/claudeParse.js +162 -0
  26. package/dist/server/agents/codexAdapter.d.ts +13 -0
  27. package/dist/server/agents/codexAdapter.js +174 -0
  28. package/dist/server/agents/codexParse.d.ts +2 -0
  29. package/dist/server/agents/codexParse.js +74 -0
  30. package/dist/server/agents/detect.d.ts +23 -0
  31. package/dist/server/agents/detect.js +35 -0
  32. package/dist/server/agents/gitScope.d.ts +24 -0
  33. package/dist/server/agents/gitScope.js +95 -0
  34. package/dist/server/agents/modelRejectionHint.d.ts +8 -0
  35. package/dist/server/agents/modelRejectionHint.js +12 -0
  36. package/dist/server/agents/prompt.d.ts +79 -0
  37. package/dist/server/agents/prompt.js +364 -0
  38. package/dist/server/agents/runManager.d.ts +90 -0
  39. package/dist/server/agents/runManager.js +259 -0
  40. package/dist/server/agents/types.d.ts +15 -0
  41. package/dist/server/agents/types.js +1 -0
  42. package/dist/server/apiStore.d.ts +60 -0
  43. package/dist/server/apiStore.js +249 -0
  44. package/dist/server/auth/enforce.d.ts +1 -0
  45. package/dist/server/auth/enforce.js +6 -0
  46. package/dist/server/auth/types.d.ts +1 -0
  47. package/dist/server/auth/types.js +1 -0
  48. package/dist/server/auth/verifiers.d.ts +1 -0
  49. package/dist/server/auth/verifiers.js +4 -0
  50. package/dist/server/authAttach.d.ts +19 -0
  51. package/dist/server/authAttach.js +79 -0
  52. package/dist/server/cli.d.ts +7 -0
  53. package/dist/server/cli.js +804 -0
  54. package/dist/server/client.d.ts +130 -0
  55. package/dist/server/client.js +247 -0
  56. package/dist/server/environments.d.ts +103 -0
  57. package/dist/server/environments.js +486 -0
  58. package/dist/server/flowStore.d.ts +37 -0
  59. package/dist/server/flowStore.js +117 -0
  60. package/dist/server/httpOperations.d.ts +32 -0
  61. package/dist/server/httpOperations.js +62 -0
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1022 -0
  64. package/dist/server/infrastructure.d.ts +51 -0
  65. package/dist/server/infrastructure.js +116 -0
  66. package/dist/server/login.d.ts +21 -0
  67. package/dist/server/login.js +44 -0
  68. package/dist/server/mcp.d.ts +1 -0
  69. package/dist/server/mcp.js +182 -0
  70. package/dist/server/migrateFlows.d.ts +8 -0
  71. package/dist/server/migrateFlows.js +25 -0
  72. package/dist/server/mockHandler.d.ts +15 -0
  73. package/dist/server/mockHandler.js +35 -0
  74. package/dist/server/nodesPayload.d.ts +14 -0
  75. package/dist/server/nodesPayload.js +14 -0
  76. package/dist/server/normalizeFlow.d.ts +21 -0
  77. package/dist/server/normalizeFlow.js +48 -0
  78. package/dist/server/openBrowser.d.ts +8 -0
  79. package/dist/server/openBrowser.js +16 -0
  80. package/dist/server/operationResult.d.ts +12 -0
  81. package/dist/server/operationResult.js +26 -0
  82. package/dist/server/pathGuard.d.ts +1 -0
  83. package/dist/server/pathGuard.js +16 -0
  84. package/dist/server/pathSafety.d.ts +20 -0
  85. package/dist/server/pathSafety.js +35 -0
  86. package/dist/server/projectConfig.d.ts +47 -0
  87. package/dist/server/projectConfig.js +61 -0
  88. package/dist/server/projectMode.d.ts +26 -0
  89. package/dist/server/projectMode.js +44 -0
  90. package/dist/server/redact.d.ts +6 -0
  91. package/dist/server/redact.js +37 -0
  92. package/dist/server/runRegistry.d.ts +84 -0
  93. package/dist/server/runRegistry.js +200 -0
  94. package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
  95. package/dist/server/runtime/RuntimeAdapter.js +1 -0
  96. package/dist/server/runtime/expressAdapter.d.ts +19 -0
  97. package/dist/server/runtime/expressAdapter.js +56 -0
  98. package/dist/server/scenarioTest.d.ts +14 -0
  99. package/dist/server/scenarioTest.js +66 -0
  100. package/dist/server/subflowRunner.d.ts +32 -0
  101. package/dist/server/subflowRunner.js +72 -0
  102. package/dist/server/testRunner.d.ts +70 -0
  103. package/dist/server/testRunner.js +151 -0
  104. package/dist/src/config.d.ts +3 -0
  105. package/dist/src/config.js +2 -0
  106. package/dist/src/engine/authVerify.d.ts +33 -0
  107. package/dist/src/engine/authVerify.js +63 -0
  108. package/dist/src/engine/diagnostics.d.ts +62 -0
  109. package/dist/src/engine/diagnostics.js +148 -0
  110. package/dist/src/engine/executor.d.ts +151 -0
  111. package/dist/src/engine/executor.js +684 -0
  112. package/dist/src/engine/httpError.d.ts +7 -0
  113. package/dist/src/engine/httpError.js +14 -0
  114. package/dist/src/engine/index.d.ts +11 -0
  115. package/dist/src/engine/index.js +11 -0
  116. package/dist/src/engine/output.d.ts +11 -0
  117. package/dist/src/engine/output.js +20 -0
  118. package/dist/src/engine/publish.d.ts +30 -0
  119. package/dist/src/engine/publish.js +95 -0
  120. package/dist/src/engine/registry.d.ts +34 -0
  121. package/dist/src/engine/registry.js +67 -0
  122. package/dist/src/engine/scenarios.d.ts +12 -0
  123. package/dist/src/engine/scenarios.js +19 -0
  124. package/dist/src/engine/schemaCheck.d.ts +4 -0
  125. package/dist/src/engine/schemaCheck.js +34 -0
  126. package/dist/src/engine/trace.d.ts +12 -0
  127. package/dist/src/engine/trace.js +12 -0
  128. package/dist/src/engine/types.d.ts +308 -0
  129. package/dist/src/engine/types.js +1 -0
  130. package/dist/src/engine/validation.d.ts +26 -0
  131. package/dist/src/engine/validation.js +318 -0
  132. package/dist/src/flows/anomaly-flows.d.ts +23 -0
  133. package/dist/src/flows/anomaly-flows.js +151 -0
  134. package/dist/src/flows/city-sweep-flow.d.ts +10 -0
  135. package/dist/src/flows/city-sweep-flow.js +127 -0
  136. package/dist/src/flows/login-flow.d.ts +13 -0
  137. package/dist/src/flows/login-flow.js +145 -0
  138. package/dist/src/flows/pradar-flows.d.ts +5 -0
  139. package/dist/src/flows/pradar-flows.js +375 -0
  140. package/dist/src/flows/weather-flow.d.ts +12 -0
  141. package/dist/src/flows/weather-flow.js +150 -0
  142. package/dist/src/lib/pathParams.d.ts +3 -0
  143. package/dist/src/lib/pathParams.js +6 -0
  144. package/dist/src/nodes/anomaly.d.ts +2 -0
  145. package/dist/src/nodes/anomaly.js +556 -0
  146. package/dist/src/nodes/flow-control.d.ts +7 -0
  147. package/dist/src/nodes/flow-control.js +270 -0
  148. package/dist/src/nodes/index.d.ts +3 -0
  149. package/dist/src/nodes/index.js +20 -0
  150. package/dist/src/nodes/login.d.ts +7 -0
  151. package/dist/src/nodes/login.js +158 -0
  152. package/dist/src/nodes/loops.d.ts +11 -0
  153. package/dist/src/nodes/loops.js +60 -0
  154. package/dist/src/nodes/pradar/index.d.ts +7 -0
  155. package/dist/src/nodes/pradar/index.js +9 -0
  156. package/dist/src/nodes/pradar/logic.d.ts +189 -0
  157. package/dist/src/nodes/pradar/logic.js +370 -0
  158. package/dist/src/nodes/pradar/nodes.d.ts +42 -0
  159. package/dist/src/nodes/pradar/nodes.js +801 -0
  160. package/dist/src/nodes/requireAuth.d.ts +20 -0
  161. package/dist/src/nodes/requireAuth.js +48 -0
  162. package/dist/src/nodes/response.d.ts +8 -0
  163. package/dist/src/nodes/response.js +33 -0
  164. package/dist/src/nodes/weather.d.ts +33 -0
  165. package/dist/src/nodes/weather.js +260 -0
  166. package/package.json +92 -0
  167. package/server/agentRoute.test.ts +157 -0
  168. package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
  169. package/server/agents/__fixtures__/fake-claude.mjs +55 -0
  170. package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
  171. package/server/agents/__fixtures__/fake-codex.mjs +52 -0
  172. package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
  173. package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
  174. package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
  175. package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
  176. package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
  177. package/server/agents/claudeAdapter.test.ts +127 -0
  178. package/server/agents/claudeAdapter.ts +212 -0
  179. package/server/agents/claudeParse.ts +193 -0
  180. package/server/agents/codexAdapter.test.ts +125 -0
  181. package/server/agents/codexAdapter.ts +198 -0
  182. package/server/agents/codexParse.test.ts +58 -0
  183. package/server/agents/codexParse.ts +126 -0
  184. package/server/agents/detect.test.ts +65 -0
  185. package/server/agents/detect.ts +43 -0
  186. package/server/agents/gitScope.test.ts +129 -0
  187. package/server/agents/gitScope.ts +110 -0
  188. package/server/agents/modelRejectionHint.test.ts +30 -0
  189. package/server/agents/modelRejectionHint.ts +14 -0
  190. package/server/agents/prompt.test.ts +613 -0
  191. package/server/agents/prompt.ts +527 -0
  192. package/server/agents/runManager.test.ts +251 -0
  193. package/server/agents/runManager.ts +286 -0
  194. package/server/agents/types.ts +17 -0
  195. package/server/apiMount.test.ts +591 -0
  196. package/server/apiStore.test.ts +324 -0
  197. package/server/apiStore.ts +241 -0
  198. package/server/auth/enforce.test.ts +61 -0
  199. package/server/auth/enforce.ts +6 -0
  200. package/server/auth/types.ts +5 -0
  201. package/server/auth/verifiers.test.ts +37 -0
  202. package/server/auth/verifiers.ts +9 -0
  203. package/server/authAttach.test.ts +175 -0
  204. package/server/authAttach.ts +99 -0
  205. package/server/cli.ts +847 -0
  206. package/server/cliAuth.test.ts +229 -0
  207. package/server/cliCreate.test.ts +131 -0
  208. package/server/cliDoctor.test.ts +326 -0
  209. package/server/cliTest.test.ts +735 -0
  210. package/server/client.ts +314 -0
  211. package/server/demoProject.test.ts +29 -0
  212. package/server/diagnosticsRoute.test.ts +169 -0
  213. package/server/environments.test.ts +620 -0
  214. package/server/environments.ts +603 -0
  215. package/server/environmentsRoute.test.ts +385 -0
  216. package/server/errorWorkflow.test.ts +205 -0
  217. package/server/flowStore.test.ts +84 -0
  218. package/server/flowStore.ts +125 -0
  219. package/server/httpOperations.test.ts +377 -0
  220. package/server/httpOperations.ts +96 -0
  221. package/server/index.ts +1111 -0
  222. package/server/infrastructure.test.ts +154 -0
  223. package/server/infrastructure.ts +161 -0
  224. package/server/infrastructureRoute.test.ts +105 -0
  225. package/server/login.test.ts +153 -0
  226. package/server/login.ts +67 -0
  227. package/server/mcp.ts +278 -0
  228. package/server/migrateFlows.test.ts +55 -0
  229. package/server/migrateFlows.ts +23 -0
  230. package/server/mockHandler.test.ts +138 -0
  231. package/server/mockHandler.ts +52 -0
  232. package/server/mockRun.test.ts +367 -0
  233. package/server/mockServe.test.ts +467 -0
  234. package/server/nodesPayload.test.ts +29 -0
  235. package/server/nodesPayload.ts +26 -0
  236. package/server/normalizeFlow.test.ts +80 -0
  237. package/server/normalizeFlow.ts +59 -0
  238. package/server/openBrowser.test.ts +20 -0
  239. package/server/openBrowser.ts +22 -0
  240. package/server/operationResult.test.ts +67 -0
  241. package/server/operationResult.ts +27 -0
  242. package/server/pathGuard.test.ts +19 -0
  243. package/server/pathGuard.ts +12 -0
  244. package/server/pathSafety.test.ts +38 -0
  245. package/server/pathSafety.ts +33 -0
  246. package/server/projectConfig.test.ts +67 -0
  247. package/server/projectConfig.ts +101 -0
  248. package/server/projectMode.test.ts +106 -0
  249. package/server/projectMode.ts +59 -0
  250. package/server/redact.test.ts +88 -0
  251. package/server/redact.ts +35 -0
  252. package/server/redactEgress.test.ts +281 -0
  253. package/server/runRegistry.ts +270 -0
  254. package/server/runsAuth.test.ts +119 -0
  255. package/server/runtime/RuntimeAdapter.ts +24 -0
  256. package/server/runtime/expressAdapter.test.ts +38 -0
  257. package/server/runtime/expressAdapter.ts +70 -0
  258. package/server/scenarioTest.test.ts +148 -0
  259. package/server/scenarioTest.ts +88 -0
  260. package/server/setupStatus.test.ts +127 -0
  261. package/server/subflowRunner.ts +104 -0
  262. package/server/testRunner.ts +216 -0
  263. package/server/tsconfig.json +20 -0
  264. package/server/workflowTestRoute.test.ts +297 -0
  265. package/src/App.tsx +233 -0
  266. package/src/components/AgentConsole.tsx +461 -0
  267. package/src/components/BranchRulesEditor.tsx +156 -0
  268. package/src/components/CreateModal.tsx +275 -0
  269. package/src/components/Dock.tsx +487 -0
  270. package/src/components/EnvironmentDialog.tsx +569 -0
  271. package/src/components/EnvironmentPicker.tsx +357 -0
  272. package/src/components/EnvironmentsDialog.tsx +144 -0
  273. package/src/components/ExecutionPager.tsx +67 -0
  274. package/src/components/InfraPanel.test.tsx +103 -0
  275. package/src/components/InfraPanel.tsx +171 -0
  276. package/src/components/Inspector.tsx +1182 -0
  277. package/src/components/Json.tsx +50 -0
  278. package/src/components/JsonModal.tsx +52 -0
  279. package/src/components/LogMessage.tsx +61 -0
  280. package/src/components/NodeRunModal.tsx +213 -0
  281. package/src/components/RunLogPanel.tsx +397 -0
  282. package/src/components/RunbookView.tsx +1085 -0
  283. package/src/components/Scenarios.tsx +476 -0
  284. package/src/components/SettingsDialog.tsx +229 -0
  285. package/src/components/Sidebar.tsx +436 -0
  286. package/src/components/StatusBar.tsx +302 -0
  287. package/src/components/Toolbar.tsx +663 -0
  288. package/src/components/WelcomeDialog.test.tsx +107 -0
  289. package/src/components/WelcomeDialog.tsx +412 -0
  290. package/src/components/agentMarkdown.test.tsx +91 -0
  291. package/src/components/agentMarkdown.tsx +158 -0
  292. package/src/components/ui/badge.tsx +31 -0
  293. package/src/components/ui/button.tsx +46 -0
  294. package/src/components/ui/combobox.tsx +114 -0
  295. package/src/components/ui/command.tsx +82 -0
  296. package/src/components/ui/dialog.tsx +73 -0
  297. package/src/components/ui/input.tsx +20 -0
  298. package/src/components/ui/popover.tsx +32 -0
  299. package/src/components/ui/resizable.tsx +44 -0
  300. package/src/components/ui/tabs.tsx +36 -0
  301. package/src/config.ts +4 -0
  302. package/src/engine/authVerify.ts +81 -0
  303. package/src/engine/diagnostics.test.ts +437 -0
  304. package/src/engine/diagnostics.ts +211 -0
  305. package/src/engine/executor.test.ts +1339 -0
  306. package/src/engine/executor.ts +840 -0
  307. package/src/engine/httpError.test.ts +12 -0
  308. package/src/engine/httpError.ts +16 -0
  309. package/src/engine/index.ts +11 -0
  310. package/src/engine/output.ts +21 -0
  311. package/src/engine/publish.test.ts +176 -0
  312. package/src/engine/publish.ts +127 -0
  313. package/src/engine/registry.test.ts +64 -0
  314. package/src/engine/registry.ts +80 -0
  315. package/src/engine/scenarios.test.ts +55 -0
  316. package/src/engine/scenarios.ts +29 -0
  317. package/src/engine/schemaCheck.test.ts +21 -0
  318. package/src/engine/schemaCheck.ts +35 -0
  319. package/src/engine/trace.test.ts +19 -0
  320. package/src/engine/trace.ts +23 -0
  321. package/src/engine/types.test.ts +22 -0
  322. package/src/engine/types.ts +316 -0
  323. package/src/engine/validation.test.ts +314 -0
  324. package/src/engine/validation.ts +354 -0
  325. package/src/flows/anomaly-flows.test.ts +81 -0
  326. package/src/flows/anomaly-flows.ts +202 -0
  327. package/src/flows/city-sweep-flow.ts +131 -0
  328. package/src/flows/flows.test.ts +30 -0
  329. package/src/flows/login-flow.ts +150 -0
  330. package/src/flows/pradar-flows.test.ts +204 -0
  331. package/src/flows/pradar-flows.ts +419 -0
  332. package/src/flows/weather-flow.ts +154 -0
  333. package/src/globals.css +147 -0
  334. package/src/lib/pathParams.test.ts +24 -0
  335. package/src/lib/pathParams.ts +6 -0
  336. package/src/lib/registerLens.test.ts +79 -0
  337. package/src/lib/registerLens.ts +59 -0
  338. package/src/lib/runbookModel.test.ts +61 -0
  339. package/src/lib/runbookModel.ts +290 -0
  340. package/src/lib/runbookProjection.test.ts +417 -0
  341. package/src/lib/runbookProjection.ts +254 -0
  342. package/src/lib/useTailAnchor.ts +53 -0
  343. package/src/lib/utils.ts +7 -0
  344. package/src/main.tsx +13 -0
  345. package/src/nodes/anomaly.test.ts +98 -0
  346. package/src/nodes/anomaly.ts +637 -0
  347. package/src/nodes/effects-scan.test.ts +44 -0
  348. package/src/nodes/flow-control.test.ts +280 -0
  349. package/src/nodes/flow-control.ts +305 -0
  350. package/src/nodes/index.ts +22 -0
  351. package/src/nodes/login.test.ts +81 -0
  352. package/src/nodes/login.ts +186 -0
  353. package/src/nodes/loops.ts +69 -0
  354. package/src/nodes/pradar/index.ts +11 -0
  355. package/src/nodes/pradar/logic.test.ts +288 -0
  356. package/src/nodes/pradar/logic.ts +582 -0
  357. package/src/nodes/pradar/nodes.test.ts +146 -0
  358. package/src/nodes/pradar/nodes.ts +955 -0
  359. package/src/nodes/requireAuth.test.ts +75 -0
  360. package/src/nodes/requireAuth.ts +55 -0
  361. package/src/nodes/response.test.ts +33 -0
  362. package/src/nodes/response.ts +39 -0
  363. package/src/nodes/traceDetail.test.ts +39 -0
  364. package/src/nodes/weather.test.ts +65 -0
  365. package/src/nodes/weather.ts +306 -0
  366. package/src/store/agentClient.test.ts +103 -0
  367. package/src/store/agentClient.ts +131 -0
  368. package/src/store/apiTree.test.ts +17 -0
  369. package/src/store/apiTree.ts +73 -0
  370. package/src/store/builderStore.test.ts +1234 -0
  371. package/src/store/builderStore.ts +1879 -0
  372. package/src/store/infraClient.ts +63 -0
  373. package/src/store/nodeMeta.test.ts +24 -0
  374. package/src/store/nodeMeta.ts +21 -0
  375. package/src/store/persistence.test.ts +15 -0
  376. package/src/store/persistence.ts +54 -0
  377. package/src/store/serverRunner.ts +381 -0
  378. package/src/store/setupClient.ts +57 -0
  379. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  380. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  381. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  382. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  383. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  384. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  385. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  386. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  387. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  388. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  389. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  390. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  391. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  392. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  393. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  394. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  395. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  396. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  397. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  398. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  399. package/studio-dist/assets/index-DNJwW-hM.css +1 -0
  400. package/studio-dist/assets/index-O26dKRjW.js +65 -0
  401. package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  402. package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  403. package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  404. package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  405. package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  406. package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  407. package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  408. package/studio-dist/favicon.svg +1 -0
  409. package/studio-dist/icons.svg +24 -0
  410. package/studio-dist/index.html +14 -0
  411. package/templates/skills/emberflow-basics/SKILL.md +316 -0
  412. package/templates/skills/emberflow-model-process/SKILL.md +209 -0
  413. package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
  414. package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
@@ -0,0 +1,357 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { CheckIcon, ChevronDownIcon, Settings2Icon, ShieldIcon, ShieldOffIcon, TheaterIcon } from 'lucide-react';
3
+ import { cn } from '@/lib/utils';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogTitle,
10
+ } from '@/components/ui/dialog';
11
+ import { Input } from '@/components/ui/input';
12
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
13
+ import { EnvironmentDialog } from './EnvironmentDialog';
14
+ import { EnvironmentsDialog } from './EnvironmentsDialog';
15
+ import { useBuilderStore } from '../store/builderStore';
16
+
17
+ /**
18
+ * Environment + mode + safe-mode selector. One dropdown answers "where do
19
+ * runs point": Mock (example responses from scenarios — nothing executes) or
20
+ * a real environment. Picking Mock is instant; picking an environment while
21
+ * in Mock goes live through a confirm dialog (blocked outright when the
22
+ * runner's default environment is protected — served traffic always runs for
23
+ * real against it and ignores the studio's safe mode). Protected environments
24
+ * force safe mode on; disabling it there requires typing the env name back.
25
+ */
26
+ export function EnvironmentPicker() {
27
+ const environments = useBuilderStore((s) => s.environments);
28
+ const selected = useBuilderStore((s) => s.selectedEnvironment);
29
+ const safeMode = useBuilderStore((s) => s.safeMode);
30
+ const selectEnvironment = useBuilderStore((s) => s.selectEnvironment);
31
+ const setSafeMode = useBuilderStore((s) => s.setSafeMode);
32
+ const runnerOnline = useBuilderStore((s) => s.runnerOnline);
33
+ const [open, setOpen] = useState(false);
34
+ const [confirmOpen, setConfirmOpen] = useState(false);
35
+ const [manageEnv, setManageEnv] = useState<string | null>(null);
36
+ const [confirmText, setConfirmText] = useState('');
37
+ const runnerMock = useBuilderStore((s) => s.runnerMock);
38
+ const setServingMode = useBuilderStore((s) => s.setServingMode);
39
+ const environmentsDefault = useBuilderStore((s) => s.environmentsDefault);
40
+ const [liveConfirm, setLiveConfirm] = useState<{ env: string; blocked: boolean } | null>(null);
41
+ const [liveError, setLiveError] = useState<string | null>(null);
42
+ const [envsOpen, setEnvsOpen] = useState(false);
43
+
44
+ const current = environments.find((e) => e.name === selected);
45
+ const protectedEnv = current?.protected ?? false;
46
+ const offline = environments.length === 0;
47
+ // The unmissable state: writes will execute against a protected environment.
48
+ const live = protectedEnv && !safeMode;
49
+ const label = selected || 'browser';
50
+
51
+ const toggleSafe = () => {
52
+ if (safeMode) {
53
+ // Turning safe mode OFF on a protected env goes through the typed-confirm.
54
+ if (protectedEnv) {
55
+ setConfirmText('');
56
+ setConfirmOpen(true);
57
+ setOpen(false);
58
+ return;
59
+ }
60
+ setSafeMode(false);
61
+ } else {
62
+ setSafeMode(true);
63
+ }
64
+ };
65
+
66
+ const confirmDisable = () => {
67
+ if (setSafeMode(false, confirmText.trim())) {
68
+ setConfirmOpen(false);
69
+ }
70
+ };
71
+
72
+ const pickMock = () => {
73
+ setOpen(false);
74
+ if (runnerMock) return;
75
+ // Harmless direction — nothing will execute.
76
+ void setServingMode('mock').catch(() => {});
77
+ };
78
+
79
+ const pickEnvironment = (name: string) => {
80
+ setOpen(false);
81
+ if (!runnerMock) {
82
+ selectEnvironment(name);
83
+ return;
84
+ }
85
+ // Leaving Mock: mounted endpoints resume real execution against the
86
+ // runner's DEFAULT environment (not the picked one) — confirm, and block
87
+ // outright when that default is protected.
88
+ const servedEnv = environments.find((e) => e.name === environmentsDefault);
89
+ setLiveError(null);
90
+ setLiveConfirm({ env: name, blocked: !!servedEnv?.protected });
91
+ };
92
+
93
+ const openEnvironments = () => {
94
+ setOpen(false);
95
+ setEnvsOpen(true);
96
+ };
97
+
98
+ const confirmLive = () => {
99
+ if (!liveConfirm) return;
100
+ setLiveError(null);
101
+ setServingMode('real')
102
+ .then(() => {
103
+ selectEnvironment(liveConfirm.env);
104
+ setLiveConfirm(null);
105
+ })
106
+ .catch((err) =>
107
+ setLiveError(err instanceof Error ? err.message : 'Failed to switch serving mode'),
108
+ );
109
+ };
110
+
111
+ return (
112
+ <>
113
+ <Popover open={open} onOpenChange={setOpen}>
114
+ <PopoverTrigger asChild>
115
+ <button
116
+ className={cn(
117
+ 'flex h-7 cursor-pointer items-center gap-1.5 rounded-md px-2 text-[11.5px] transition-colors',
118
+ runnerMock
119
+ ? 'bg-warn/15 font-medium text-warn hover:bg-warn/25'
120
+ : live
121
+ ? 'bg-destructive/15 font-medium text-destructive-foreground hover:bg-destructive/25'
122
+ : 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
123
+ )}
124
+ title={
125
+ runnerMock
126
+ ? 'Mock mode — everything answers with example responses from scenarios. Nothing executes, no auth, no database. Pick an environment to go live.'
127
+ : 'Where runs point — pick Mock or a real environment'
128
+ }
129
+ >
130
+ {runnerMock ? (
131
+ <TheaterIcon className="size-3.5" />
132
+ ) : live ? (
133
+ <ShieldOffIcon className="size-3.5" />
134
+ ) : (
135
+ safeMode && <ShieldIcon className="size-3.5" />
136
+ )}
137
+ <span className={cn(live && !runnerMock && 'uppercase tracking-wide')}>
138
+ {runnerMock ? 'Mock' : live ? `LIVE · ${label}` : label}
139
+ </span>
140
+ <ChevronDownIcon className="size-3 opacity-50" />
141
+ </button>
142
+ </PopoverTrigger>
143
+ <PopoverContent className="w-64 p-1" align="end">
144
+ {runnerOnline === true && (
145
+ <>
146
+ <button
147
+ onClick={pickMock}
148
+ className="flex w-full cursor-pointer items-start gap-2 rounded-sm px-2.5 py-1.5 text-left transition-colors hover:bg-accent"
149
+ >
150
+ <CheckIcon className={cn('mt-0.5 size-3.5', runnerMock ? 'opacity-100' : 'opacity-0')} />
151
+ <TheaterIcon className={cn('mt-0.5 size-3.5', runnerMock ? 'text-warn' : 'text-muted-foreground')} />
152
+ <span className="flex min-w-0 flex-col gap-0.5">
153
+ <span className={cn('text-[12.5px] font-medium', runnerMock && 'text-warn')}>Mock</span>
154
+ <span className="text-[10.5px] leading-snug text-muted-foreground/80">
155
+ Example responses from scenarios — nothing executes.
156
+ </span>
157
+ </span>
158
+ </button>
159
+ <div className="my-1 h-px bg-border" />
160
+ </>
161
+ )}
162
+ {offline ? (
163
+ runnerOnline === true ? (
164
+ <div className="flex flex-col gap-1.5 px-2.5 py-2">
165
+ <span className="text-[12px] leading-snug text-muted-foreground">
166
+ No environments yet — you're in Mock, where nothing real is touched. Create
167
+ environments (dev? prod? test?) to go live.
168
+ </span>
169
+ <Button size="sm" variant="secondary" onClick={openEnvironments}>
170
+ <Settings2Icon className="size-3.5" />
171
+ Manage environments…
172
+ </Button>
173
+ </div>
174
+ ) : (
175
+ <div className="px-2.5 py-3 text-[12px] text-muted-foreground">
176
+ {runnerOnline === false
177
+ ? 'Runner offline — runs execute in the browser as environment "browser".'
178
+ : 'Loading environments…'}
179
+ </div>
180
+ )
181
+ ) : (
182
+ <div className="flex flex-col">
183
+ {environments.map((env) => (
184
+ <div
185
+ key={env.name}
186
+ className="group flex items-center gap-2 rounded-sm transition-colors hover:bg-accent"
187
+ >
188
+ <button
189
+ onClick={() => pickEnvironment(env.name)}
190
+ title={runnerMock ? `Go live against ${env.name}` : undefined}
191
+ className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2.5 py-1.5 text-left"
192
+ >
193
+ <CheckIcon
194
+ className={cn(
195
+ 'size-3.5',
196
+ env.name === selected && !runnerMock ? 'opacity-100' : 'opacity-0',
197
+ )}
198
+ />
199
+ {env.protected && <span className="size-1.5 rounded-full bg-destructive" />}
200
+ <span className="text-[12.5px] font-medium">{env.name}</span>
201
+ {env.protected && (
202
+ <span className="ml-auto text-[10.5px] uppercase tracking-wide text-destructive-foreground/80">
203
+ protected
204
+ </span>
205
+ )}
206
+ </button>
207
+ <button
208
+ onClick={() => {
209
+ setManageEnv(env.name);
210
+ setOpen(false);
211
+ }}
212
+ title={`Manage ${env.name} — secrets & auth`}
213
+ className="mr-1.5 cursor-pointer rounded-sm p-1 text-muted-foreground opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100"
214
+ >
215
+ <Settings2Icon className="size-3.5" />
216
+ </button>
217
+ </div>
218
+ ))}
219
+ </div>
220
+ )}
221
+ <div className="my-1 h-px bg-border" />
222
+ <button
223
+ onClick={toggleSafe}
224
+ className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-left transition-colors hover:bg-accent"
225
+ >
226
+ {safeMode ? (
227
+ <ShieldIcon className="size-3.5 text-success" />
228
+ ) : (
229
+ <ShieldOffIcon className="size-3.5 text-destructive-foreground" />
230
+ )}
231
+ <span className="text-[12.5px] font-medium">Safe mode</span>
232
+ <span
233
+ className={cn(
234
+ 'ml-auto text-[11px] font-medium',
235
+ safeMode ? 'text-success' : 'text-destructive-foreground',
236
+ )}
237
+ >
238
+ {safeMode ? 'on' : 'off'}
239
+ </span>
240
+ </button>
241
+ {runnerOnline === true && !offline && (
242
+ <>
243
+ <div className="my-1 h-px bg-border" />
244
+ <button
245
+ onClick={openEnvironments}
246
+ title="See every environment, its secrets and auth — or describe changes to the agent."
247
+ className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-left text-[12.5px] text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
248
+ >
249
+ <Settings2Icon className="size-3.5" />
250
+ Manage environments…
251
+ </button>
252
+ </>
253
+ )}
254
+ </PopoverContent>
255
+ </Popover>
256
+
257
+ <Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
258
+ <DialogContent className="max-w-md">
259
+ <DialogTitle>Disable safe mode on {selected}?</DialogTitle>
260
+ <DialogDescription>
261
+ Writes will execute for real against <span className="font-medium text-foreground">{selected}</span> —
262
+ database mutations, emails and other side effects. Type{' '}
263
+ <span className="font-mono text-foreground">{selected}</span> to confirm.
264
+ </DialogDescription>
265
+ <Input
266
+ value={confirmText}
267
+ onChange={(e) => setConfirmText(e.target.value)}
268
+ placeholder={selected}
269
+ autoFocus
270
+ spellCheck={false}
271
+ onKeyDown={(e) => {
272
+ if (e.key === 'Enter' && confirmText.trim() === selected) confirmDisable();
273
+ }}
274
+ />
275
+ <div className="flex justify-end gap-2">
276
+ <Button variant="ghost" size="sm" onClick={() => setConfirmOpen(false)}>
277
+ Cancel
278
+ </Button>
279
+ <Button
280
+ variant="destructive"
281
+ size="sm"
282
+ disabled={confirmText.trim() !== selected}
283
+ onClick={confirmDisable}
284
+ >
285
+ Disable safe mode
286
+ </Button>
287
+ </div>
288
+ </DialogContent>
289
+ </Dialog>
290
+
291
+ <Dialog open={liveConfirm !== null} onOpenChange={(o) => !o && setLiveConfirm(null)}>
292
+ <DialogContent className="max-w-md">
293
+ {liveConfirm?.blocked ? (
294
+ <>
295
+ <DialogTitle>The served environment is protected</DialogTitle>
296
+ <DialogDescription>
297
+ Mounted endpoints execute against the runner's default environment
298
+ (<span className="font-medium text-foreground">{environmentsDefault}</span>),
299
+ which is protected — and served traffic always runs for real, with no dry-run.
300
+ The studio's safe mode does not apply to it. Change the default environment in{' '}
301
+ <span className="font-mono text-foreground">emberflow.environments.json</span>{' '}
302
+ before leaving Mock.
303
+ </DialogDescription>
304
+ <div className="flex justify-end">
305
+ <Button variant="ghost" size="sm" onClick={() => setLiveConfirm(null)}>
306
+ Close
307
+ </Button>
308
+ </div>
309
+ </>
310
+ ) : (
311
+ <>
312
+ <DialogTitle>Go live?</DialogTitle>
313
+ <DialogDescription>
314
+ Runs will execute real nodes against{' '}
315
+ <span className="font-medium text-foreground">{liveConfirm?.env}</span>, and
316
+ mounted endpoints will stop answering from scenario examples and execute for
317
+ real against the{' '}
318
+ <span className="font-medium text-foreground">{environmentsDefault}</span>{' '}
319
+ environment — real side effects included. Served traffic ignores the studio's
320
+ safe mode.
321
+ </DialogDescription>
322
+ {liveError && <div className="text-[12px] text-destructive">{liveError}</div>}
323
+ <div className="flex justify-end gap-2">
324
+ <Button variant="ghost" size="sm" onClick={() => setLiveConfirm(null)}>
325
+ Cancel
326
+ </Button>
327
+ <Button size="sm" onClick={confirmLive}>
328
+ Go live
329
+ </Button>
330
+ </div>
331
+ </>
332
+ )}
333
+ </DialogContent>
334
+ </Dialog>
335
+
336
+ <EnvironmentDialog
337
+ env={environments.find((e) => e.name === manageEnv) ?? null}
338
+ open={manageEnv !== null}
339
+ onOpenChange={(o) => {
340
+ if (!o) setManageEnv(null);
341
+ }}
342
+ />
343
+
344
+ <EnvironmentsDialog open={envsOpen} onOpenChange={setEnvsOpen} />
345
+ </>
346
+ );
347
+ }
348
+
349
+ /** Poll the runner health + environments on an interval. Mounted once (in the toolbar). */
350
+ export function useRunnerPolling(): void {
351
+ const checkRunner = useBuilderStore((s) => s.checkRunner);
352
+ useEffect(() => {
353
+ void checkRunner();
354
+ const interval = setInterval(() => void checkRunner(), 10_000);
355
+ return () => clearInterval(interval);
356
+ }, [checkRunner]);
357
+ }
@@ -0,0 +1,144 @@
1
+ import { useState } from 'react';
2
+ import { ArrowLeftIcon, KeyRoundIcon, Settings2Icon, SparklesIcon } from 'lucide-react';
3
+ import { cn } from '@/lib/utils';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogTitle,
10
+ } from '@/components/ui/dialog';
11
+ import { EnvironmentDialog } from './EnvironmentDialog';
12
+ import { useBuilderStore } from '../store/builderStore';
13
+
14
+ /**
15
+ * The Environments overview modal — one place to SEE what exists. Each row
16
+ * opens the per-environment Manage dialog (secrets + auth); creating or
17
+ * reshaping environments is agent work: the call-to-action hands off to the
18
+ * agent panel in environment-setup mode, where the user describes what they
19
+ * want in prose and can keep refining it conversationally.
20
+ */
21
+ export function EnvironmentsDialog({
22
+ open,
23
+ onOpenChange,
24
+ onBack,
25
+ }: {
26
+ open: boolean;
27
+ onOpenChange: (open: boolean) => void;
28
+ /** When set (opened from the Welcome checklist), a back arrow beside the
29
+ * title returns there instead of just closing. */
30
+ onBack?: () => void;
31
+ }) {
32
+ const environments = useBuilderStore((s) => s.environments);
33
+ const environmentsDefault = useBuilderStore((s) => s.environmentsDefault);
34
+ const beginEnvironmentSetup = useBuilderStore((s) => s.beginEnvironmentSetup);
35
+ const agentRunning = useBuilderStore((s) => s.agentRun?.status === 'running');
36
+ const [manageEnv, setManageEnv] = useState<string | null>(null);
37
+
38
+ const setupWithAi = () => {
39
+ onOpenChange(false);
40
+ beginEnvironmentSetup();
41
+ };
42
+
43
+ return (
44
+ <>
45
+ <Dialog open={open} onOpenChange={onOpenChange}>
46
+ <DialogContent className="max-w-lg">
47
+ <div className="flex items-center gap-2">
48
+ {onBack && (
49
+ <Button
50
+ variant="ghost"
51
+ size="icon"
52
+ onClick={onBack}
53
+ aria-label="Back to setup checklist"
54
+ className="-ml-1.5 size-6 text-muted-foreground hover:text-foreground"
55
+ >
56
+ <ArrowLeftIcon className="size-4" />
57
+ </Button>
58
+ )}
59
+ <DialogTitle>Environments</DialogTitle>
60
+ </div>
61
+ {environments.length === 0 ? (
62
+ <>
63
+ <DialogDescription>
64
+ No environments yet — you're in Mock, where nothing real is touched. Environments
65
+ are the real places runs can point at (dev? prod? test?); create them to go live.
66
+ </DialogDescription>
67
+ <div className="flex justify-end">
68
+ <Button onClick={setupWithAi} disabled={agentRunning}>
69
+ <SparklesIcon className="size-3.5" />
70
+ Set up with AI
71
+ </Button>
72
+ </div>
73
+ </>
74
+ ) : (
75
+ <>
76
+ <DialogDescription>
77
+ Where runs can point. Secrets and auth live per environment — open one to manage
78
+ them. To add or reshape environments, describe the change to the agent.
79
+ </DialogDescription>
80
+ <div className="flex flex-col gap-1">
81
+ {environments.map((env) => (
82
+ <button
83
+ key={env.name}
84
+ onClick={() => setManageEnv(env.name)}
85
+ title={`Manage ${env.name} — secrets & auth`}
86
+ className="group flex w-full cursor-pointer items-center gap-2.5 rounded-md border border-border/70 px-3 py-2 text-left transition-colors hover:bg-accent"
87
+ >
88
+ <span
89
+ className={cn(
90
+ 'size-2 shrink-0 rounded-full',
91
+ env.protected ? 'bg-destructive' : 'bg-success/70',
92
+ )}
93
+ />
94
+ <span className="text-[13px] font-medium">{env.name}</span>
95
+ {env.name === environmentsDefault && (
96
+ <span className="rounded bg-secondary px-1.5 py-0.5 text-[9.5px] uppercase tracking-wide text-muted-foreground">
97
+ default
98
+ </span>
99
+ )}
100
+ {env.protected && (
101
+ <span className="text-[10px] uppercase tracking-wide text-destructive-foreground/80">
102
+ protected
103
+ </span>
104
+ )}
105
+ <span className="ml-auto flex items-center gap-2 text-[11px] text-muted-foreground">
106
+ {env.varKeys.length > 0 && <span>{env.varKeys.length} vars</span>}
107
+ {env.auth?.configured && (
108
+ <span
109
+ className="flex items-center gap-1"
110
+ title={env.auth.authenticated ? 'Auth configured — logged in' : 'Auth configured — not logged in'}
111
+ >
112
+ <KeyRoundIcon className="size-3" />
113
+ {env.auth.authenticated ? 'auth ✓' : 'auth'}
114
+ </span>
115
+ )}
116
+ <Settings2Icon className="size-3.5 opacity-0 transition-opacity group-hover:opacity-100" />
117
+ </span>
118
+ </button>
119
+ ))}
120
+ </div>
121
+ <div className="flex justify-end gap-2">
122
+ <Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>
123
+ Close
124
+ </Button>
125
+ <Button variant="secondary" size="sm" onClick={setupWithAi} disabled={agentRunning}>
126
+ <SparklesIcon className="size-3.5" />
127
+ Change with AI
128
+ </Button>
129
+ </div>
130
+ </>
131
+ )}
132
+ </DialogContent>
133
+ </Dialog>
134
+
135
+ <EnvironmentDialog
136
+ env={environments.find((e) => e.name === manageEnv) ?? null}
137
+ open={manageEnv !== null}
138
+ onOpenChange={(o) => {
139
+ if (!o) setManageEnv(null);
140
+ }}
141
+ />
142
+ </>
143
+ );
144
+ }
@@ -0,0 +1,67 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
3
+ import { Button } from '@/components/ui/button';
4
+ import { cn } from '@/lib/utils';
5
+ import type { ExecutionRecord } from '../engine';
6
+
7
+ const statusDot: Record<string, string> = {
8
+ succeeded: 'bg-success',
9
+ failed: 'bg-destructive',
10
+ };
11
+
12
+ /**
13
+ * Browsable header + body over a loop-body node's `ExecutionRecord[]`.
14
+ * Selection is uncontrolled and defaults to (and snaps back to) the last
15
+ * record whenever the records array grows — i.e. a new run/iteration.
16
+ */
17
+ export function ExecutionPager({
18
+ executions,
19
+ children,
20
+ className,
21
+ }: {
22
+ executions: ExecutionRecord[];
23
+ children: (record: ExecutionRecord, index: number) => React.ReactNode;
24
+ className?: string;
25
+ }) {
26
+ const [index, setIndex] = useState(executions.length - 1);
27
+
28
+ useEffect(() => {
29
+ setIndex(executions.length - 1);
30
+ }, [executions.length]);
31
+
32
+ const safeIndex = Math.min(Math.max(index, 0), executions.length - 1);
33
+ const record = executions[safeIndex];
34
+ if (!record) return null;
35
+
36
+ return (
37
+ <div className={className}>
38
+ <div className="mb-2 flex items-center gap-1">
39
+ <Button
40
+ variant="ghost"
41
+ size="icon"
42
+ className="size-5 shrink-0 text-muted-foreground hover:text-foreground"
43
+ disabled={safeIndex === 0}
44
+ onClick={() => setIndex(safeIndex - 1)}
45
+ aria-label="Previous execution"
46
+ >
47
+ <ChevronLeftIcon className="size-3.5" />
48
+ </Button>
49
+ <span className={cn('size-1.5 shrink-0 rounded-full', statusDot[record.status])} />
50
+ <span className="font-mono text-[11px] text-muted-foreground">
51
+ {safeIndex + 1}/{executions.length}
52
+ </span>
53
+ <Button
54
+ variant="ghost"
55
+ size="icon"
56
+ className="size-5 shrink-0 text-muted-foreground hover:text-foreground"
57
+ disabled={safeIndex === executions.length - 1}
58
+ onClick={() => setIndex(safeIndex + 1)}
59
+ aria-label="Next execution"
60
+ >
61
+ <ChevronRightIcon className="size-3.5" />
62
+ </Button>
63
+ </div>
64
+ {children(record, safeIndex)}
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { InfraPanel } from './InfraPanel';
4
+ import type { InfrastructureManifest, InfrastructureResponse } from '../store/infraClient';
5
+
6
+ const MANIFEST: InfrastructureManifest = {
7
+ version: 1,
8
+ scannedAt: '2026-07-12T00:00:00Z',
9
+ greenfield: false,
10
+ summary: 'Express app with Postgres (Prisma), Stripe, and SendGrid.',
11
+ items: [
12
+ {
13
+ id: 'postgres-main',
14
+ kind: 'database',
15
+ name: 'Postgres (Prisma)',
16
+ evidence: [{ file: 'prisma/schema.prisma', note: 'datasource db provider=postgresql' }],
17
+ suggestedSecretRefs: ['DATABASE_URL'],
18
+ suggestedVars: [],
19
+ notes: 'Schema defines User, Order, Invoice models.',
20
+ },
21
+ {
22
+ id: 'stripe',
23
+ kind: 'http-api',
24
+ name: 'Stripe',
25
+ evidence: [{ file: 'package.json', note: 'stripe dependency' }],
26
+ suggestedSecretRefs: ['STRIPE_SECRET_KEY'],
27
+ suggestedVars: [],
28
+ },
29
+ ],
30
+ };
31
+
32
+ const html = (data: InfrastructureResponse | null): string =>
33
+ renderToStaticMarkup(<InfraPanel data={data} />);
34
+
35
+ describe('InfraPanel', () => {
36
+ it('renders the summary, each item card, kind, evidence, secretRefs and notes', () => {
37
+ const out = html({ present: true, manifest: MANIFEST });
38
+ expect(out).toContain('Express app with Postgres');
39
+ // Item names + kinds.
40
+ expect(out).toContain('Postgres (Prisma)');
41
+ expect(out).toContain('database');
42
+ expect(out).toContain('Stripe');
43
+ expect(out).toContain('http-api');
44
+ // Evidence file + note.
45
+ expect(out).toContain('prisma/schema.prisma');
46
+ expect(out).toContain('datasource db provider=postgresql');
47
+ // Secret ref chips (NAMES only).
48
+ expect(out).toContain('DATABASE_URL');
49
+ expect(out).toContain('STRIPE_SECRET_KEY');
50
+ // Notes.
51
+ expect(out).toContain('User, Order, Invoice');
52
+ });
53
+
54
+ it('renders the not-scouted empty state with the scout button when absent', () => {
55
+ const out = renderToStaticMarkup(<InfraPanel data={{ present: false }} onScout={() => {}} />);
56
+ expect(out).toContain('Not scouted yet');
57
+ expect(out).toContain('emberflow/infrastructure.json');
58
+ expect(out).toContain('Scout infrastructure');
59
+ });
60
+
61
+ it('treats a null (unreachable) response like not-scouted', () => {
62
+ const out = html(null);
63
+ expect(out).toContain('Not scouted yet');
64
+ });
65
+
66
+ it('renders a distinct greenfield state', () => {
67
+ const out = html({
68
+ present: true,
69
+ manifest: { version: 1, greenfield: true, summary: 'Clean slate — no infrastructure yet.', items: [] },
70
+ });
71
+ expect(out).toContain('Greenfield project');
72
+ expect(out).toContain('Clean slate');
73
+ // No item cards.
74
+ expect(out).not.toContain('data-kind');
75
+ });
76
+
77
+ it('disables the scout button with the reason tooltip when canScout is false (no agent CLI)', () => {
78
+ const out = renderToStaticMarkup(
79
+ <InfraPanel
80
+ data={{ present: false }}
81
+ onScout={() => {}}
82
+ canScout={false}
83
+ canScoutReason="Detect a coding agent first"
84
+ />,
85
+ );
86
+ expect(out).toContain('disabled');
87
+ expect(out).toContain('Detect a coding agent first');
88
+ });
89
+
90
+ it('shows the scouting state on the button when a scout run is active', () => {
91
+ const out = renderToStaticMarkup(
92
+ <InfraPanel data={{ present: false }} onScout={() => {}} scouting />,
93
+ );
94
+ expect(out).toContain('Scouting…');
95
+ });
96
+
97
+ it('never renders a secret VALUE — only names appear', () => {
98
+ const out = html({ present: true, manifest: MANIFEST });
99
+ // The chip carries the NAME; there is no value anywhere in the manifest to leak.
100
+ expect(out).toContain('DATABASE_URL');
101
+ expect(out).not.toContain('postgres://');
102
+ });
103
+ });