@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,119 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { spawn, type ChildProcess } from 'node:child_process';
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ // Verifies POST /runs auto-attaches the environment's credential (Task 4).
8
+ // Boots the actual runner subprocess (server/index.ts) against a scratch
9
+ // project dir, mirroring the harness in apiMount.test.ts.
10
+
11
+ let proc: ChildProcess;
12
+ const PORT = 8129;
13
+ const base = `http://127.0.0.1:${PORT}`;
14
+ let projectDir: string;
15
+
16
+ async function waitHealthy(url: string, tries = 40): Promise<void> {
17
+ for (let i = 0; i < tries; i++) {
18
+ try {
19
+ if ((await fetch(url)).ok) return;
20
+ } catch {
21
+ /* not up yet */
22
+ }
23
+ await new Promise((r) => setTimeout(r, 150));
24
+ }
25
+ throw new Error('runner did not become healthy');
26
+ }
27
+
28
+ function bootRunner(port: number, env: Record<string, string | undefined>): ChildProcess {
29
+ return spawn('npx', ['tsx', 'server/index.ts'], {
30
+ env: { ...process.env, EMBERFLOW_RUNNER_PORT: String(port), ...env },
31
+ stdio: 'ignore',
32
+ });
33
+ }
34
+
35
+ // Single-node flow: an Input node that just echoes whatever input the run
36
+ // was invoked with (including `headers`) — a "trivial flow whose Input
37
+ // echoes headers" per the brief.
38
+ const echoFlow = {
39
+ id: 'echo-input',
40
+ name: 'Echo Input',
41
+ version: 1,
42
+ createdAt: '2026-01-01T00:00:00Z',
43
+ updatedAt: '2026-01-01T00:00:00Z',
44
+ nodes: [
45
+ {
46
+ id: 'input',
47
+ type: 'Input',
48
+ label: 'Input',
49
+ position: { x: 0, y: 0 },
50
+ config: { fields: [], defaults: {} },
51
+ },
52
+ ],
53
+ edges: [],
54
+ };
55
+
56
+ /** POSTs a step-mode run, steps it once, then reads back the Input node's recorded sample output. */
57
+ async function runAndGetInputOutput(input: Record<string, unknown>): Promise<any> {
58
+ const createRes = await fetch(`${base}/api/runs`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ flow: echoFlow, mode: 'step', input }),
62
+ });
63
+ expect(createRes.status).toBe(201);
64
+ const { runId } = await createRes.json();
65
+
66
+ const stepRes = await fetch(`${base}/api/runs/${runId}/step`, { method: 'POST' });
67
+ expect(stepRes.status).toBe(200);
68
+
69
+ const samplesRes = await fetch(`${base}/api/samples?nodeId=input`);
70
+ expect(samplesRes.status).toBe(200);
71
+ const { samples } = await samplesRes.json();
72
+ const sample = samples.find((s: { runId: string }) => s.runId === runId);
73
+ expect(sample).toBeDefined();
74
+ return sample.output;
75
+ }
76
+
77
+ beforeAll(async () => {
78
+ projectDir = mkdtempSync(join(tmpdir(), 'runsauth-'));
79
+ mkdirSync(join(projectDir, 'emberflow', 'apis', 'default'), { recursive: true });
80
+ writeFileSync(join(projectDir, 'emberflow.config.mjs'), 'export default {};\n');
81
+ writeFileSync(
82
+ join(projectDir, 'emberflow.environments.json'),
83
+ JSON.stringify({
84
+ defaultEnvironment: 'local',
85
+ environments: {
86
+ local: {
87
+ vars: {},
88
+ secrets: { sessionCookie: 'supersecretcookievalue' },
89
+ auth: { attach: { as: 'cookie', name: 'session', secretRef: 'sessionCookie' } },
90
+ },
91
+ },
92
+ }),
93
+ );
94
+
95
+ proc = bootRunner(PORT, { EMBERFLOW_PROJECT: projectDir });
96
+ await waitHealthy(`${base}/healthz`);
97
+ }, 20_000);
98
+
99
+ afterAll(() => {
100
+ proc?.kill();
101
+ if (projectDir) rmSync(projectDir, { recursive: true, force: true });
102
+ });
103
+
104
+ describe('POST /runs auto-attaches the environment credential', () => {
105
+ it('attaches the session cookie from the environment secret when the run omits headers', async () => {
106
+ // The flow receives the raw secret internally (real attachment happened);
107
+ // only the studio-facing sample output (via GET /samples) is redacted.
108
+ // The name= prefix on the redaction token proves attachment occurred.
109
+ const output = await runAndGetInputOutput({});
110
+ expect(output.headers.cookie).toContain('session=«secret:sessionCookie»');
111
+ });
112
+
113
+ it('does not overwrite an explicit value for the same cookie name (non-destructive)', async () => {
114
+ // The run already sets the target cookie name ("session") explicitly —
115
+ // attachCredential must leave it exactly as the caller set it.
116
+ const output = await runAndGetInputOutput({ headers: { cookie: 'session=mine' } });
117
+ expect(output.headers.cookie).toBe('session=mine');
118
+ });
119
+ });
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler, Router } from 'express';
2
+
3
+ /** A single routed operation's method + path. */
4
+ export interface RegisteredOp {
5
+ method: string;
6
+ path: string;
7
+ }
8
+
9
+ /**
10
+ * The seam between the runner and whatever HTTP runtime serves it. Express is
11
+ * the only implementation today; Deno/Bun adapters can implement this same
12
+ * interface later without touching the runner.
13
+ */
14
+ export interface RuntimeAdapter {
15
+ /** Register a routed operation's handler at `{method, path}`. Throws on a
16
+ * duplicate method+path registration. */
17
+ registerOperation(op: RegisteredOp, handler: RequestHandler): void;
18
+ /** Mount a sub-router at a base path (used for `/api` + the studio). */
19
+ mountRouter(basePath: string, router: Router): void;
20
+ /** Start listening; resolves once the server is up. */
21
+ listen(port: number, host: string): Promise<void>;
22
+ /** Stop the server; resolves once fully closed. */
23
+ close(): Promise<void>;
24
+ }
@@ -0,0 +1,38 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { ExpressAdapter } from './expressAdapter';
3
+
4
+ describe('ExpressAdapter', () => {
5
+ let adapter: ExpressAdapter | undefined;
6
+
7
+ afterEach(async () => {
8
+ await adapter?.close();
9
+ adapter = undefined;
10
+ });
11
+
12
+ it('registers an operation route and serves it', async () => {
13
+ adapter = new ExpressAdapter();
14
+ adapter.registerOperation({ method: 'GET', path: '/ping' }, (_req, res) => res.json({ pong: true }));
15
+ await adapter.listen(0, '127.0.0.1');
16
+ const port = adapter.address?.port;
17
+ expect(port).toBeTypeOf('number');
18
+
19
+ const res = await fetch(`http://127.0.0.1:${port}/ping`);
20
+ expect(res.status).toBe(200);
21
+ expect(await res.json()).toEqual({ pong: true });
22
+ });
23
+
24
+ it('throws on a duplicate method+path', () => {
25
+ adapter = new ExpressAdapter();
26
+ const h = (_req: never, res: { end: () => void }) => res.end();
27
+ adapter.registerOperation({ method: 'POST', path: '/x' }, h as never);
28
+ expect(() => adapter?.registerOperation({ method: 'POST', path: '/x' }, h as never)).toThrow(/POST \/x/);
29
+ });
30
+
31
+ it('resolves listen/close cleanly', async () => {
32
+ adapter = new ExpressAdapter();
33
+ await adapter.listen(0, '127.0.0.1');
34
+ expect(adapter.address).not.toBeNull();
35
+ await adapter.close();
36
+ expect(adapter.address).toBeNull();
37
+ });
38
+ });
@@ -0,0 +1,70 @@
1
+ import express, { type Express, type RequestHandler, type Router } from 'express';
2
+ import type { AddressInfo } from 'node:net';
3
+ import type { Server } from 'node:http';
4
+ import type { RegisteredOp, RuntimeAdapter } from './RuntimeAdapter';
5
+
6
+ const METHODS = ['get', 'post', 'put', 'delete', 'patch'] as const;
7
+ type Method = (typeof METHODS)[number];
8
+
9
+ function isMethod(m: string): m is Method {
10
+ return (METHODS as readonly string[]).includes(m);
11
+ }
12
+
13
+ /** Express implementation of the `RuntimeAdapter` seam. Keep it thin — this is
14
+ * the seam other runtimes (Deno/Bun) implement later. */
15
+ export class ExpressAdapter implements RuntimeAdapter {
16
+ private readonly _app: Express;
17
+ private server?: Server;
18
+ private readonly seen = new Set<string>();
19
+
20
+ constructor(app?: Express) {
21
+ this._app = app ?? express();
22
+ }
23
+
24
+ get app(): Express {
25
+ return this._app;
26
+ }
27
+
28
+ /** The bound address once `listen()` resolves (useful in tests to find an
29
+ * ephemeral port). `null` before listening / after close. */
30
+ get address(): AddressInfo | null {
31
+ const addr = this.server?.address();
32
+ return addr && typeof addr === 'object' ? addr : null;
33
+ }
34
+
35
+ registerOperation(op: RegisteredOp, handler: RequestHandler): void {
36
+ const method = op.method.toUpperCase();
37
+ const key = `${method} ${op.path}`;
38
+ if (this.seen.has(key)) {
39
+ throw new Error(`Duplicate operation route: ${key}`);
40
+ }
41
+ const lower = method.toLowerCase();
42
+ if (!isMethod(lower)) {
43
+ throw new Error(`Unsupported HTTP method for operation route: ${method} ${op.path}`);
44
+ }
45
+ this.seen.add(key);
46
+ this._app[lower](op.path, handler);
47
+ }
48
+
49
+ mountRouter(basePath: string, router: Router): void {
50
+ this._app.use(basePath, router);
51
+ }
52
+
53
+ listen(port: number, host: string): Promise<void> {
54
+ return new Promise((resolve) => {
55
+ this.server = this._app.listen(port, host, () => resolve());
56
+ });
57
+ }
58
+
59
+ close(): Promise<void> {
60
+ return new Promise((resolve, reject) => {
61
+ const server = this.server;
62
+ if (!server) {
63
+ resolve();
64
+ return;
65
+ }
66
+ this.server = undefined;
67
+ server.close((err) => (err ? reject(err) : resolve()));
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,148 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { deepSubsetMatch, evaluateExpectation } from './scenarioTest';
3
+ import type { WorkflowDefinition, WorkflowRun } from '../src/engine';
4
+
5
+ describe('deepSubsetMatch', () => {
6
+ it('passes on equal primitives', () => {
7
+ expect(deepSubsetMatch(1, 1)).toEqual([]);
8
+ expect(deepSubsetMatch('a', 'a')).toEqual([]);
9
+ expect(deepSubsetMatch(true, true)).toEqual([]);
10
+ });
11
+
12
+ it('fails on unequal primitives', () => {
13
+ expect(deepSubsetMatch(1, 2)).toEqual(['expected 1, got 2']);
14
+ expect(deepSubsetMatch('a', 'b')).toEqual(['expected "a", got "b"']);
15
+ });
16
+
17
+ it('passes when actual is a superset of a nested object', () => {
18
+ const failures = deepSubsetMatch(
19
+ { user: { name: 'Ada' } },
20
+ { user: { name: 'Ada', age: 30 }, extra: true },
21
+ );
22
+ expect(failures).toEqual([]);
23
+ });
24
+
25
+ it('reports a dotted path for a missing/mismatched nested key', () => {
26
+ const failures = deepSubsetMatch({ user: { name: 'Ada' } }, { user: { name: 'Bob' } });
27
+ expect(failures).toEqual(['user.name: expected "Ada", got "Bob"']);
28
+ });
29
+
30
+ it('reports missing actual key', () => {
31
+ const failures = deepSubsetMatch({ user: { name: 'Ada' } }, { user: {} });
32
+ expect(failures).toEqual(['user.name: expected "Ada", got undefined']);
33
+ });
34
+
35
+ it('matches arrays index-wise on the expected prefix', () => {
36
+ expect(deepSubsetMatch([1, 2], [1, 2, 3])).toEqual([]);
37
+ expect(deepSubsetMatch([1, 2], [1])).toEqual(['[1]: expected 2, got undefined']);
38
+ expect(deepSubsetMatch([1, 2], [1, 9])).toEqual(['[1]: expected 2, got 9']);
39
+ });
40
+
41
+ it('allows extra actual keys anywhere', () => {
42
+ expect(deepSubsetMatch({ a: 1 }, { a: 1, b: 2, c: 3 })).toEqual([]);
43
+ });
44
+
45
+ it('null expected matches only null', () => {
46
+ expect(deepSubsetMatch(null, null)).toEqual([]);
47
+ expect(deepSubsetMatch(null, undefined)).toEqual(['expected null, got undefined']);
48
+ expect(deepSubsetMatch(null, 0)).toEqual(['expected null, got 0']);
49
+ });
50
+ });
51
+
52
+ function makeFlow(overrides: Partial<WorkflowDefinition> = {}): WorkflowDefinition {
53
+ return {
54
+ id: 'flow-1',
55
+ name: 'Flow 1',
56
+ version: 1,
57
+ nodes: [
58
+ { id: 'response-200', type: 'Response', label: 'OK', position: { x: 0, y: 0 }, config: {} },
59
+ { id: 'response-401', type: 'Response', label: 'Unauthorized', position: { x: 0, y: 0 }, config: {} },
60
+ ],
61
+ edges: [],
62
+ createdAt: new Date().toISOString(),
63
+ updatedAt: new Date().toISOString(),
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ function makeRun(overrides: Partial<WorkflowRun> = {}): WorkflowRun {
69
+ return {
70
+ id: 'run-1',
71
+ workflowId: 'flow-1',
72
+ status: 'succeeded',
73
+ startedAt: new Date().toISOString(),
74
+ nodeStates: {},
75
+ ...overrides,
76
+ };
77
+ }
78
+
79
+ describe('evaluateExpectation', () => {
80
+ it('passes when status matches', () => {
81
+ const flow = makeFlow();
82
+ const run = makeRun({
83
+ nodeStates: {
84
+ 'response-200': { status: 'succeeded', output: { status: 200, body: { ok: true } } },
85
+ },
86
+ });
87
+ const result = evaluateExpectation(run, flow, { status: 200 });
88
+ expect(result).toEqual({ ok: true, failures: [] });
89
+ });
90
+
91
+ it('fails when status mismatches', () => {
92
+ const flow = makeFlow();
93
+ const run = makeRun({
94
+ nodeStates: {
95
+ 'response-200': { status: 'succeeded', output: { status: 200, body: {} } },
96
+ },
97
+ });
98
+ const result = evaluateExpectation(run, flow, { status: 401 });
99
+ expect(result.ok).toBe(false);
100
+ expect(result.failures).toEqual(['status: expected 401, got 200']);
101
+ });
102
+
103
+ it('fails and lists the dotted path when body is not a subset match', () => {
104
+ const flow = makeFlow();
105
+ const run = makeRun({
106
+ nodeStates: {
107
+ 'response-200': {
108
+ status: 'succeeded',
109
+ output: { status: 200, body: { user: { name: 'Bob' } } },
110
+ },
111
+ },
112
+ });
113
+ const result = evaluateExpectation(run, flow, { body: { user: { name: 'Ada' } } });
114
+ expect(result.ok).toBe(false);
115
+ expect(result.failures).toEqual(['body.user.name: expected "Ada", got "Bob"']);
116
+ });
117
+
118
+ it('checks executedNodes against nodeStates, reporting skipped nodes', () => {
119
+ const flow = makeFlow();
120
+ const run = makeRun({
121
+ nodeStates: {
122
+ 'response-200': { status: 'succeeded', output: { status: 200, body: {} } },
123
+ 'response-401': { status: 'skipped' },
124
+ },
125
+ });
126
+ const result = evaluateExpectation(run, flow, { executedNodes: ['response-200', 'response-401'] });
127
+ expect(result.ok).toBe(false);
128
+ expect(result.failures).toEqual(['executedNodes: response-401 did not execute (status: skipped)']);
129
+ });
130
+
131
+ it('passes when status, body, and executedNodes all match', () => {
132
+ const flow = makeFlow();
133
+ const run = makeRun({
134
+ nodeStates: {
135
+ 'response-200': {
136
+ status: 'succeeded',
137
+ output: { status: 200, body: { user: { name: 'Ada' } } },
138
+ },
139
+ },
140
+ });
141
+ const result = evaluateExpectation(run, flow, {
142
+ status: 200,
143
+ body: { user: { name: 'Ada' } },
144
+ executedNodes: ['response-200'],
145
+ });
146
+ expect(result).toEqual({ ok: true, failures: [] });
147
+ });
148
+ });
@@ -0,0 +1,88 @@
1
+ import type { ScenarioExpectation, WorkflowDefinition, WorkflowRun } from '../src/engine';
2
+ import { extractResponse } from './operationResult';
3
+
4
+ function fmt(value: unknown): string {
5
+ if (value === undefined) return 'undefined';
6
+ return JSON.stringify(value);
7
+ }
8
+
9
+ /** Deep-subset match: every key/index present in `expected` must match the
10
+ * corresponding key/index in `actual`, recursively. Extra keys/indices in
11
+ * `actual` are ignored. `null` matches only `null`. Returns human-readable
12
+ * failure strings with dotted (and `[i]`) paths; empty array = match. */
13
+ export function deepSubsetMatch(expected: unknown, actual: unknown, path = ''): string[] {
14
+ const label = path === '' ? '' : `${path}: `;
15
+
16
+ if (expected === null) {
17
+ return actual === null ? [] : [`${label}expected null, got ${fmt(actual)}`];
18
+ }
19
+
20
+ if (Array.isArray(expected)) {
21
+ if (!Array.isArray(actual)) {
22
+ return [`${label}expected array, got ${fmt(actual)}`];
23
+ }
24
+ const failures: string[] = [];
25
+ for (let i = 0; i < expected.length; i++) {
26
+ failures.push(...deepSubsetMatch(expected[i], actual[i], `${path}[${i}]`));
27
+ }
28
+ return failures;
29
+ }
30
+
31
+ if (typeof expected === 'object') {
32
+ if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {
33
+ return [`${label}expected object, got ${fmt(actual)}`];
34
+ }
35
+ const failures: string[] = [];
36
+ for (const key of Object.keys(expected as Record<string, unknown>)) {
37
+ const childPath = path === '' ? key : `${path}.${key}`;
38
+ failures.push(
39
+ ...deepSubsetMatch(
40
+ (expected as Record<string, unknown>)[key],
41
+ (actual as Record<string, unknown>)[key],
42
+ childPath,
43
+ ),
44
+ );
45
+ }
46
+ return failures;
47
+ }
48
+
49
+ // Primitive.
50
+ return expected === actual ? [] : [`${label}expected ${fmt(expected)}, got ${fmt(actual)}`];
51
+ }
52
+
53
+ /** Check a scenario's finished run against its `expect` assertion. `status`/
54
+ * `body` come from `extractResponse` (the flow's Response-node mapping,
55
+ * defaulting to status 200); `executedNodes` checks each id's node state is
56
+ * `succeeded`. */
57
+ export function evaluateExpectation(
58
+ run: WorkflowRun,
59
+ flow: WorkflowDefinition,
60
+ expect: ScenarioExpectation,
61
+ ): { ok: boolean; failures: string[] } {
62
+ const failures: string[] = [];
63
+
64
+ if (expect.status !== undefined || expect.body !== undefined) {
65
+ const { status, body } = extractResponse(run, flow);
66
+
67
+ if (expect.status !== undefined && expect.status !== status) {
68
+ failures.push(`status: expected ${expect.status}, got ${status}`);
69
+ }
70
+
71
+ if (expect.body !== undefined) {
72
+ for (const failure of deepSubsetMatch(expect.body, body)) {
73
+ failures.push(`body.${failure}`);
74
+ }
75
+ }
76
+ }
77
+
78
+ if (expect.executedNodes) {
79
+ for (const id of expect.executedNodes) {
80
+ const state = run.nodeStates[id];
81
+ if (state?.status !== 'succeeded') {
82
+ failures.push(`executedNodes: ${id} did not execute (status: ${state?.status ?? 'not run'})`);
83
+ }
84
+ }
85
+ }
86
+
87
+ return { ok: failures.length === 0, failures };
88
+ }
@@ -0,0 +1,127 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { spawn, type ChildProcess } from 'node:child_process';
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ // Boots a real runner subprocess over a temp project (the agentRoute.test.ts
8
+ // pattern) and drives GET /setup-status. Because the route re-reads
9
+ // environments per request, one runner covers both a fresh, unconfigured
10
+ // project AND a project that has just gained an environments file: we assert
11
+ // the fresh shape, then write emberflow.environments.json and re-fetch.
12
+
13
+ const PORT = 8153;
14
+ const base = `http://127.0.0.1:${PORT}`;
15
+
16
+ async function waitHealthy(url: string, tries = 40): Promise<void> {
17
+ for (let i = 0; i < tries; i++) {
18
+ try {
19
+ if ((await fetch(url)).ok) return;
20
+ } catch {
21
+ /* not up yet */
22
+ }
23
+ await new Promise((r) => setTimeout(r, 150));
24
+ }
25
+ throw new Error('runner did not become healthy');
26
+ }
27
+
28
+ let proc: ChildProcess;
29
+ let projectDir: string;
30
+
31
+ beforeAll(async () => {
32
+ projectDir = mkdtempSync(join(tmpdir(), 'setupstatus-'));
33
+ mkdirSync(join(projectDir, 'emberflow', 'apis', 'default'), { recursive: true });
34
+ writeFileSync(join(projectDir, 'emberflow.config.mjs'), 'export default {};\n');
35
+ // A pristine project ships exactly the single default/hello example op.
36
+ writeFileSync(
37
+ join(projectDir, 'emberflow', 'apis', 'default', 'hello.json'),
38
+ JSON.stringify({
39
+ id: 'default/hello',
40
+ name: 'Hello',
41
+ version: 1,
42
+ nodes: [
43
+ { id: 'in', type: 'Input', label: 'Input', position: { x: 0, y: 0 }, config: {} },
44
+ { id: 'out', type: 'Result', label: 'Result', position: { x: 300, y: 0 }, config: {}, inputMap: { value: { sourceNodeId: 'in', sourceField: '$' } } },
45
+ ],
46
+ edges: [{ id: 'e1', source: 'in', target: 'out' }],
47
+ createdAt: '2026-07-12T00:00:00Z',
48
+ updatedAt: '2026-07-12T00:00:00Z',
49
+ }),
50
+ );
51
+
52
+ proc = spawn('npx', ['tsx', 'server/index.ts'], {
53
+ env: {
54
+ ...process.env,
55
+ EMBERFLOW_RUNNER_PORT: String(PORT),
56
+ EMBERFLOW_PROJECT: projectDir,
57
+ // The skills check also looks in the user's home dir (`init --global`);
58
+ // point homedir() at the temp project so a dev machine with real global
59
+ // skills doesn't flip the fresh-project assertion.
60
+ HOME: projectDir,
61
+ USERPROFILE: projectDir,
62
+ },
63
+ stdio: 'ignore',
64
+ });
65
+
66
+ await waitHealthy(`${base}/healthz`);
67
+ }, 20_000);
68
+
69
+ afterAll(() => {
70
+ proc?.kill();
71
+ if (projectDir) rmSync(projectDir, { recursive: true, force: true });
72
+ });
73
+
74
+ describe('GET /setup-status', () => {
75
+ it('reports the fresh, unconfigured shape for an init-like project', async () => {
76
+ const res = await fetch(`${base}/setup-status`);
77
+ expect(res.status).toBe(200);
78
+ const body = await res.json();
79
+
80
+ expect(Array.isArray(body.agents)).toBe(true);
81
+ // A fresh project has no environments file → the synthesized fallback is
82
+ // reported as unconfigured with zero real environments.
83
+ expect(body.environments).toMatchObject({
84
+ configured: false,
85
+ count: 0,
86
+ protectedCount: 0,
87
+ anyAuthConfigured: false,
88
+ });
89
+ expect(body.skills).toEqual({ claude: false, codex: false });
90
+ expect(body.ops).toEqual({ count: 1, onlyHello: true });
91
+ // Inferred from the .mjs config extension (project.language).
92
+ expect(body.language).toBe('javascript');
93
+ // Unconfigured projects default to mock serving.
94
+ expect(body.servingMode).toBe('mock');
95
+ expect(body.infrastructure).toEqual({ present: false });
96
+ });
97
+
98
+ it('reflects an environments file written after boot (re-read per request)', async () => {
99
+ writeFileSync(
100
+ join(projectDir, 'emberflow.environments.json'),
101
+ JSON.stringify({
102
+ defaultEnvironment: 'dev',
103
+ environments: {
104
+ dev: { vars: {}, secrets: [] },
105
+ prod: {
106
+ vars: {},
107
+ secrets: ['API_KEY'],
108
+ protected: true,
109
+ auth: { attach: { as: 'header', name: 'Authorization', secretRef: 'API_KEY' } },
110
+ },
111
+ },
112
+ }),
113
+ );
114
+
115
+ const res = await fetch(`${base}/setup-status`);
116
+ expect(res.status).toBe(200);
117
+ const body = await res.json();
118
+ expect(body.environments).toEqual({
119
+ configured: true,
120
+ count: 2,
121
+ protectedCount: 1,
122
+ anyAuthConfigured: true,
123
+ });
124
+ // ops are unchanged — still just the hello example.
125
+ expect(body.ops).toEqual({ count: 1, onlyHello: true });
126
+ });
127
+ });