@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,804 @@
1
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { findScenario, isArtifact, publishFlow, scenarioNames, } from '../src/engine/index.js';
5
+ import { createDefaultRegistry } from '../src/nodes/index.js';
6
+ import { parsePathParams } from '../src/lib/pathParams.js';
7
+ import { diagnoseOperation } from '../src/engine/diagnostics.js';
8
+ import { seedParamDefaults } from './normalizeFlow.js';
9
+ import { loadProjectConfig } from './projectConfig.js';
10
+ import { loadEnvironments } from './environments.js';
11
+ import { buildApiStore, buildRegistries, requireProjectWhenExplicit } from './projectMode.js';
12
+ import * as client from './client.js';
13
+ import { RunnerUnreachableError } from './client.js';
14
+ import { formatScenarioLine, formatSummary, runScenarioSuite, ScenarioTestUsageError, } from './testRunner.js';
15
+ const USAGE = `Usage: emberflow-cli <command> [args]
16
+
17
+ Commands:
18
+ list-nodes List all registered node types
19
+ node-schema <type> Print the full NodeDefinition for a node type
20
+ list-workflows List workflows known to the runner
21
+ get-workflow <id> Print a workflow's full flow JSON
22
+ get-node <opId> <nodeId> Print one node's wiring: its instance
23
+ (config, inputMap, retry/optional), its inbound
24
+ + outbound edges, and — when the type is
25
+ registered — a definition summary (input/output
26
+ field names, traceKind, effects). Runner-
27
+ preferred; falls back to an in-process offline
28
+ load when the runner is unreachable. Unknown op
29
+ or node exits 1.
30
+ list-environments List configured environments (names + key lists, no values;
31
+ includes per-env auth: {configured, authenticated} when set).
32
+ Prefers the runner; if it's unreachable, falls back to an
33
+ in-process offline load of the same files (NO runner
34
+ required) — prints "source": "offline" and, since login
35
+ state lives runner-side, auth.authenticated: "unknown".
36
+ login-environment <name> Perform the environment's configured login and store the
37
+ captured credential runner-side; prints {environment,
38
+ authenticated, secretRef} (never the secret value)
39
+ set-environment-auth <name> --json '<EnvAuth JSON>'
40
+ Set (or, with --json 'null', clear) an environment's auth
41
+ config; prints {environment, configured: true}. Carries no
42
+ secret values — only refs/names.
43
+ serving <real|mock> Switch whether mounted HTTP endpoints execute for real or
44
+ answer from scenario expectations (mock); prints
45
+ {serving: mode}.
46
+ validate <file|id> Validate a flow (file path or workflow id)
47
+ publish <id|file> [--out <path>] Publish a flow to a sealed artifact JSON file
48
+ run <file|id> [--input '<json>' | --input-file <path> | --scenario <name>]
49
+ [--env <name>] [--safe | --unsafe] [--confirm <name>] [--full]
50
+ Run a flow to completion. Prints a CONCISE result by
51
+ default (per-node status + truncated I/O); --full for raw.
52
+ save <file> Save a flow file to the runner
53
+ create <id> --method <M> --path </route> [--name "<display>"]
54
+ Create a new operation shell (Input → Response + http
55
+ trigger) at the given apis id. Then edit it to add nodes.
56
+ delete <id> Delete an operation/workflow from the runner
57
+ rename <old-id> <new-id> [--name "<display>"]
58
+ Rename an operation (updates id + display name + http route)
59
+ samples <nodeId> Print recorded execution samples for a node
60
+ test [opId] [--environment <name>] [--json] [--mock]
61
+ Run every scenario with an \`expect\` assertion
62
+ in-process (NO runner required) and report
63
+ pass/fail/skip. [opId] limits to one operation.
64
+ Scenarios without \`expect\` (or an empty \`{}\`)
65
+ are skipped. --json prints a machine-readable
66
+ array instead of the default text summary.
67
+ --mock runs each scenario as a Mock run
68
+ (mockRun: true): infrastructure nodes
69
+ (traceKind db/http/llm) return their sidecar
70
+ mock instead of touching real infra — an
71
+ op-level \`flow.mocks\` overlaid by the
72
+ scenario's own \`mocks\` (scenario wins per
73
+ nodeId) — and fail loudly if unmocked. Off by
74
+ default (real runs). The hermetic way to prove
75
+ branch coverage for infra-heavy ops without
76
+ live Postgres/Weld/OpenRouter.
77
+ Exit 0 all pass, 1 any fail, 2 usage (unknown
78
+ opId/environment).
79
+ doctor [opId] [--fix] Diagnose operation(s) in-process (NO runner
80
+ required): missing path-param defaults, params
81
+ no scenario exercises, scenarios with no
82
+ expect. [opId] limits to one operation.
83
+ --fix seeds '' param defaults in-place (never
84
+ overwrites an existing value) and re-diagnoses
85
+ so the report reflects post-fix state.
86
+ Exit 0 no error-severity diagnostics, 1 at
87
+ least one error-severity diagnostic, 2 usage
88
+ (unknown opId / unknown flag).
89
+
90
+ Env:
91
+ EMBERFLOW_RUNNER_URL Runner base URL (default http://127.0.0.1:8092)
92
+
93
+ Notes:
94
+ run --step is not supported by the CLI; step mode is interactive-only.
95
+ run --input, --input-file, and --scenario are mutually exclusive.
96
+ run --scenario <name> looks up a named test input embedded in the flow
97
+ (scenario name or id); artifacts carry no scenarios.
98
+ run accepts either a flow file/id or a published artifact file ($artifact) —
99
+ artifacts always execute under production semantics.
100
+ run --env <name> selects an environment from emberflow.environments.json
101
+ (omitted → the runner's default environment).
102
+ run --safe / --unsafe overrides safe mode (omitted → the environment's
103
+ default: protected environments default to safe).
104
+ run --unsafe on a protected environment additionally requires
105
+ --confirm <name> matching --env exactly, or the runner rejects the run.
106
+ `;
107
+ function printJson(value) {
108
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
109
+ }
110
+ /** Value after a `--flag` in argv, or undefined. */
111
+ function flagValue(args, flag) {
112
+ const i = args.indexOf(flag);
113
+ return i !== -1 ? args[i + 1] : undefined;
114
+ }
115
+ /** kebab/snake slug → Title Case display name (e.g. "latest-results" → "Latest Results"). */
116
+ function titleCaseSlug(slug) {
117
+ return slug
118
+ .split(/[-_\s]+/)
119
+ .filter(Boolean)
120
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
121
+ .join(' ');
122
+ }
123
+ /** Shrink a large value to a keys+preview stub so a `run` summary stays readable.
124
+ * Small values pass through untouched; anything over `cap` chars of JSON is
125
+ * replaced by its shape (keys/length) + a short preview. */
126
+ function summarizeValue(v, cap = 4000) {
127
+ if (v === undefined || v === null)
128
+ return v;
129
+ const s = JSON.stringify(v);
130
+ if (s.length <= cap)
131
+ return v;
132
+ const stub = { __truncated: true, chars: s.length };
133
+ if (Array.isArray(v)) {
134
+ stub.arrayLength = v.length;
135
+ stub.first = summarizeValue(v[0], 300);
136
+ }
137
+ else if (typeof v === 'object') {
138
+ stub.keys = Object.keys(v);
139
+ }
140
+ stub.preview = s.slice(0, 300);
141
+ return stub;
142
+ }
143
+ /** A concise per-node view of a run: status + error + truncated input/output —
144
+ * the default `run` output, so a flow whose fetch nodes dump 70KB of raw API
145
+ * JSON doesn't blow up the agent's context on every run-inspect cycle. */
146
+ function summarizeNodeStates(states) {
147
+ const out = {};
148
+ for (const [id, raw] of Object.entries(states)) {
149
+ const st = (raw ?? {});
150
+ out[id] = {
151
+ status: st.status,
152
+ ...(st.error ? { error: st.error } : {}),
153
+ ...(st.output !== undefined ? { output: summarizeValue(st.output) } : {}),
154
+ ...(st.input !== undefined ? { input: summarizeValue(st.input) } : {}),
155
+ };
156
+ }
157
+ return out;
158
+ }
159
+ /** Field names of a node schema, tolerant of the loose `unknown` shape the
160
+ * runner's /nodes payload carries (vs. the typed offline registry). */
161
+ function schemaFieldNames(schema) {
162
+ const fields = schema?.fields;
163
+ if (!Array.isArray(fields))
164
+ return [];
165
+ return fields.map((f) => f?.name).filter((n) => typeof n === 'string');
166
+ }
167
+ /** Compact `definition` summary for `get-node`: input/output field names, trace
168
+ * kind, effects (absent = 'read' per NodeDefinition). Accepts either a runner
169
+ * NodeMeta or an offline NodeDefinition — both carry these fields. */
170
+ function summarizeNodeDefinition(def) {
171
+ return {
172
+ type: def.type,
173
+ label: def.label,
174
+ traceKind: def.traceKind,
175
+ effects: def.effects ?? 'read',
176
+ inputFields: schemaFieldNames(def.inputSchema),
177
+ outputFields: schemaFieldNames(def.outputSchema),
178
+ };
179
+ }
180
+ /** Thrown by `fail` to unwind to `runCli`, which turns it into a return code.
181
+ * Lets the parse helpers keep their `never`-returning `fail(...)` call sites
182
+ * while runCli stays in-process (no process.exit) so it's usable as a library. */
183
+ class CliExit extends Error {
184
+ code;
185
+ constructor(code) {
186
+ super(`CliExit(${code})`);
187
+ this.code = code;
188
+ }
189
+ }
190
+ /** Prints {error} JSON to stderr and unwinds with the given code. Never returns. */
191
+ function fail(message, code) {
192
+ process.stderr.write(`${JSON.stringify({ error: message })}\n`);
193
+ throw new CliExit(code);
194
+ }
195
+ /** File path if it exists on disk, else fetched by id from the runner. */
196
+ async function resolveFlow(arg) {
197
+ if (existsSync(arg)) {
198
+ return JSON.parse(readFileSync(arg, 'utf8'));
199
+ }
200
+ const flow = await client.getWorkflow(arg);
201
+ if (!flow) {
202
+ throw new Error(`No flow file at "${arg}" and no workflow with id "${arg}" on the runner`);
203
+ }
204
+ return flow;
205
+ }
206
+ /** Like resolveFlow, but recognizes a file whose JSON is a published artifact. */
207
+ async function resolveFlowOrArtifact(arg) {
208
+ if (existsSync(arg)) {
209
+ const parsed = JSON.parse(readFileSync(arg, 'utf8'));
210
+ return isArtifact(parsed) ? parsed : parsed;
211
+ }
212
+ const flow = await client.getWorkflow(arg);
213
+ if (!flow) {
214
+ throw new Error(`No flow file at "${arg}" and no workflow with id "${arg}" on the runner`);
215
+ }
216
+ return flow;
217
+ }
218
+ /** Parses --env/--safe/--unsafe/--confirm flags. --safe and --unsafe are mutually exclusive. */
219
+ function parseEnvFlags(args) {
220
+ const envIdx = args.indexOf('--env');
221
+ let environment;
222
+ if (envIdx !== -1) {
223
+ environment = args[envIdx + 1];
224
+ if (!environment)
225
+ fail('Usage: run <file|id> --env <name>', 2);
226
+ }
227
+ const hasSafe = args.includes('--safe');
228
+ const hasUnsafe = args.includes('--unsafe');
229
+ if (hasSafe && hasUnsafe) {
230
+ fail('--safe and --unsafe are mutually exclusive', 2);
231
+ }
232
+ const safeMode = hasSafe ? true : hasUnsafe ? false : undefined;
233
+ const confirmIdx = args.indexOf('--confirm');
234
+ let confirm;
235
+ if (confirmIdx !== -1) {
236
+ confirm = args[confirmIdx + 1];
237
+ if (!confirm)
238
+ fail('Usage: run <file|id> --confirm <name>', 2);
239
+ }
240
+ return { environment, safeMode, confirm };
241
+ }
242
+ /** Parses --input/--input-file flags. Mutually exclusive; parse errors exit 2. */
243
+ function parseInputFlag(args) {
244
+ const inputIdx = args.indexOf('--input');
245
+ const inputFileIdx = args.indexOf('--input-file');
246
+ if (inputIdx !== -1 && inputFileIdx !== -1) {
247
+ fail('--input and --input-file are mutually exclusive', 2);
248
+ }
249
+ if (inputIdx !== -1) {
250
+ const raw = args[inputIdx + 1];
251
+ if (!raw)
252
+ fail("Usage: run <file|id> --input '<json>'", 2);
253
+ try {
254
+ return JSON.parse(raw);
255
+ }
256
+ catch (err) {
257
+ fail(`Invalid --input JSON: ${err instanceof Error ? err.message : String(err)}`, 2);
258
+ }
259
+ }
260
+ if (inputFileIdx !== -1) {
261
+ const path = args[inputFileIdx + 1];
262
+ if (!path)
263
+ fail('Usage: run <file|id> --input-file <path>', 2);
264
+ if (!existsSync(path))
265
+ fail(`No such --input-file: ${path}`, 2);
266
+ try {
267
+ return JSON.parse(readFileSync(path, 'utf8'));
268
+ }
269
+ catch (err) {
270
+ fail(`Invalid JSON in --input-file ${path}: ${err instanceof Error ? err.message : String(err)}`, 2);
271
+ }
272
+ }
273
+ return undefined;
274
+ }
275
+ /**
276
+ * Runs a single CLI command in-process and resolves to a process exit code
277
+ * (0 success, non-zero on error) — never calls process.exit, so it's safe to
278
+ * drive from the register-API bin inside a sandbox (no `tsx` child spawn).
279
+ * `argv` is `[command, ...rest]`.
280
+ */
281
+ export async function runCli(argv) {
282
+ const [command, ...args] = argv;
283
+ try {
284
+ return await dispatch(command, args);
285
+ }
286
+ catch (err) {
287
+ if (err instanceof CliExit)
288
+ return err.code;
289
+ if (err instanceof RunnerUnreachableError) {
290
+ process.stderr.write(`${JSON.stringify({ error: err.message })}\n`);
291
+ return 2;
292
+ }
293
+ process.stderr.write(`${JSON.stringify({ error: err instanceof Error ? err.message : String(err) })}\n`);
294
+ return 1;
295
+ }
296
+ }
297
+ async function dispatch(command, args) {
298
+ switch (command) {
299
+ case 'list-nodes': {
300
+ // The RUNNER's live registry — built-ins + the project's registerNodes —
301
+ // NOT a local createDefaultRegistry(), which is blind to project nodes.
302
+ const nodes = await client.listNodes();
303
+ printJson(nodes.map((d) => ({
304
+ type: d.type,
305
+ label: d.label,
306
+ category: d.category,
307
+ description: d.description,
308
+ })));
309
+ return 0;
310
+ }
311
+ case 'node-schema': {
312
+ const type = args[0];
313
+ if (!type)
314
+ fail('Usage: node-schema <type>', 2);
315
+ const nodes = await client.listNodes();
316
+ const node = nodes.find((n) => n.type === type);
317
+ if (!node) {
318
+ fail(`Unknown node type: ${type}. (Run list-nodes to see registered types.)`, 2);
319
+ }
320
+ printJson(node);
321
+ return 0;
322
+ }
323
+ case 'list-workflows': {
324
+ const flows = await client.listWorkflows();
325
+ printJson(flows.map((f) => ({ id: f.id, name: f.name, folder: f.folder, nodes: f.nodes.length })));
326
+ return 0;
327
+ }
328
+ case 'get-workflow': {
329
+ const id = args[0];
330
+ if (!id)
331
+ fail('Usage: get-workflow <id>', 2);
332
+ const flow = await client.getWorkflow(id);
333
+ if (!flow)
334
+ fail(`Unknown workflow: ${id}`, 1);
335
+ printJson(flow);
336
+ return 0;
337
+ }
338
+ case 'get-node': {
339
+ const opId = args[0];
340
+ const nodeId = args[1];
341
+ if (!opId || !nodeId)
342
+ fail('Usage: get-node <opId> <nodeId>', 2);
343
+ // Resolve the flow + a node-type definition lookup. Runner-preferred
344
+ // (mirrors get-workflow / node-schema); falls back to an in-process
345
+ // offline load of the project files when the runner is unreachable, so
346
+ // node inspection works with no server up.
347
+ let flow;
348
+ let lookupDef;
349
+ try {
350
+ flow = await client.getWorkflow(opId);
351
+ const nodes = await client.listNodes();
352
+ lookupDef = (type) => {
353
+ const def = nodes.find((n) => n.type === type);
354
+ return def ? summarizeNodeDefinition(def) : undefined;
355
+ };
356
+ }
357
+ catch (err) {
358
+ if (!(err instanceof RunnerUnreachableError))
359
+ throw err;
360
+ const projectDir = process.env.EMBERFLOW_PROJECT
361
+ ? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
362
+ : process.cwd();
363
+ const project = requireProjectWhenExplicit(await loadProjectConfig(projectDir), process.env.EMBERFLOW_PROJECT, projectDir);
364
+ const apiStore = buildApiStore(project);
365
+ flow = apiStore.load(opId);
366
+ const { validation } = buildRegistries(project);
367
+ lookupDef = (type) => validation.has(type) ? summarizeNodeDefinition(validation.get(type).definition) : undefined;
368
+ }
369
+ if (!flow)
370
+ fail(`Unknown operation: ${opId}`, 1);
371
+ const node = flow.nodes.find((n) => n.id === nodeId);
372
+ if (!node)
373
+ fail(`Unknown node "${nodeId}" in operation "${opId}"`, 1);
374
+ const edges = flow.edges ?? [];
375
+ const inbound = edges
376
+ .filter((e) => e.target === nodeId)
377
+ .map((e) => ({ source: e.source, sourceHandle: e.sourceHandle, targetHandle: e.targetHandle }));
378
+ const outbound = edges
379
+ .filter((e) => e.source === nodeId)
380
+ .map((e) => ({ target: e.target, sourceHandle: e.sourceHandle, targetHandle: e.targetHandle }));
381
+ printJson({
382
+ operation: opId,
383
+ node: {
384
+ id: node.id,
385
+ type: node.type,
386
+ label: node.label,
387
+ config: node.config,
388
+ inputMap: node.inputMap,
389
+ ...(node.retry ? { retry: node.retry } : {}),
390
+ ...(node.optional !== undefined ? { optional: node.optional } : {}),
391
+ },
392
+ inbound,
393
+ outbound,
394
+ definition: lookupDef(node.type) ?? null,
395
+ });
396
+ return 0;
397
+ }
398
+ case 'list-environments': {
399
+ try {
400
+ printJson({ source: 'runner', ...(await client.listEnvironments()) });
401
+ }
402
+ catch (err) {
403
+ if (!(err instanceof RunnerUnreachableError))
404
+ throw err;
405
+ // Runner unreachable — fall back to an in-process offline load of the
406
+ // same emberflow.environments.json/emberflow.secrets.json the runner
407
+ // would read, so intake (which the skills tell agents to run before a
408
+ // runner is up) still works. Auth *status* (logged-in state) lives
409
+ // runner-side (it's set by a live login run) — offline we can only
410
+ // report whether auth is *configured*, not whether it's authenticated.
411
+ const projectDir = process.env.EMBERFLOW_PROJECT
412
+ ? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
413
+ : process.cwd();
414
+ const envFile = loadEnvironments(projectDir);
415
+ printJson({
416
+ source: 'offline',
417
+ defaultEnvironment: envFile.defaultEnvironment,
418
+ environments: Object.entries(envFile.environments).map(([name, env]) => ({
419
+ name,
420
+ protected: !!env.protected,
421
+ varKeys: Object.keys(env.vars),
422
+ secretKeys: Object.keys(env.secrets),
423
+ ...(env.auth ? { auth: { configured: true, authenticated: 'unknown' } } : {}),
424
+ })),
425
+ });
426
+ }
427
+ return 0;
428
+ }
429
+ case 'login-environment': {
430
+ const name = args[0];
431
+ if (!name)
432
+ fail('Usage: login-environment <name>', 2);
433
+ // Errors (unknown env 404, no auth.login 400, upstream login failure 502)
434
+ // bubble to runCli's catch-all, which prints {error} and returns 1.
435
+ const { authenticated, secretRef } = await client.loginEnvironment(name);
436
+ printJson({ environment: name, authenticated, secretRef });
437
+ return 0;
438
+ }
439
+ case 'set-environment-auth': {
440
+ const name = args[0];
441
+ if (!name)
442
+ fail("Usage: set-environment-auth <name> --json '<EnvAuth JSON>'", 2);
443
+ const raw = flagValue(args, '--json');
444
+ if (!raw)
445
+ fail("Usage: set-environment-auth <name> --json '<EnvAuth JSON>'", 2);
446
+ let auth;
447
+ try {
448
+ auth = JSON.parse(raw);
449
+ }
450
+ catch (err) {
451
+ fail(`Invalid --json: ${err instanceof Error ? err.message : String(err)}`, 2);
452
+ }
453
+ // Errors (unknown env 404, invalid auth shape 400) bubble to runCli's
454
+ // catch-all, which prints {error} and returns 1.
455
+ await client.setEnvironmentAuth(name, auth);
456
+ printJson({ environment: name, configured: true });
457
+ return 0;
458
+ }
459
+ case 'serving': {
460
+ const mode = args[0];
461
+ if (mode !== 'real' && mode !== 'mock') {
462
+ fail('Usage: serving <real|mock>', 2);
463
+ }
464
+ // Errors (e.g. runner rejects the mode) bubble to runCli's catch-all,
465
+ // which prints {error} and returns 1.
466
+ await client.setServingMode(mode);
467
+ printJson({ serving: mode });
468
+ return 0;
469
+ }
470
+ case 'validate': {
471
+ const arg = args[0];
472
+ if (!arg)
473
+ fail('Usage: validate <file|id>', 2);
474
+ const flow = await resolveFlow(arg);
475
+ // Validate against the runner's live registry (built-ins + project nodes),
476
+ // not a project-blind local createDefaultRegistry().
477
+ const { valid, issues } = await client.validateOperation(flow);
478
+ printJson({ valid, issues });
479
+ return valid ? 0 : 1;
480
+ }
481
+ case 'run': {
482
+ if (args.includes('--step')) {
483
+ fail('run --step is not supported by the CLI; step mode is interactive-only', 2);
484
+ }
485
+ const arg = args[0];
486
+ if (!arg) {
487
+ fail("Usage: run <file|id> [--input '<json>' | --input-file <path> | --scenario <name>]", 2);
488
+ }
489
+ const scenarioIdx = args.indexOf('--scenario');
490
+ if (scenarioIdx !== -1 && (args.includes('--input') || args.includes('--input-file'))) {
491
+ fail('--scenario and --input/--input-file are mutually exclusive', 2);
492
+ }
493
+ let scenarioName;
494
+ if (scenarioIdx !== -1) {
495
+ scenarioName = args[scenarioIdx + 1];
496
+ if (!scenarioName)
497
+ fail('Usage: run <file|id> --scenario <name>', 2);
498
+ }
499
+ let input = parseInputFlag(args);
500
+ const { environment, safeMode, confirm } = parseEnvFlags(args);
501
+ const resolved = await resolveFlowOrArtifact(arg);
502
+ if (scenarioName !== undefined) {
503
+ if (isArtifact(resolved)) {
504
+ fail('Artifacts carry no scenarios; pass --input instead', 2);
505
+ }
506
+ const scenario = findScenario(resolved, scenarioName);
507
+ if (!scenario) {
508
+ const names = scenarioNames(resolved);
509
+ fail(`Unknown scenario: "${scenarioName}". Available: ${names.length ? names.join(', ') : 'none'}`, 1);
510
+ }
511
+ input = scenario.input;
512
+ }
513
+ const runId = isArtifact(resolved)
514
+ ? await client.startRun({ artifact: resolved, input, environment, safeMode, confirm })
515
+ : await client.startRun({ flow: resolved, input, environment, safeMode, confirm });
516
+ const { run: finished, logs } = await client.waitForRun(runId);
517
+ const full = args.includes('--full');
518
+ printJson({
519
+ status: finished.status,
520
+ environment: finished.environment,
521
+ safeMode: finished.safeMode,
522
+ nodeStates: full ? finished.nodeStates : summarizeNodeStates(finished.nodeStates),
523
+ logs,
524
+ ...(full ? {} : { note: 'node inputs/outputs over 4000 chars are truncated (keys + preview shown); pass --full for the raw nodeStates.' }),
525
+ });
526
+ return finished.status === 'succeeded' ? 0 : 1;
527
+ }
528
+ case 'publish': {
529
+ const arg = args[0];
530
+ if (!arg)
531
+ fail('Usage: publish <id|file> [--out <path>]', 2);
532
+ const outIdx = args.indexOf('--out');
533
+ const outPathArg = outIdx !== -1 ? args[outIdx + 1] : undefined;
534
+ if (outIdx !== -1 && !outPathArg)
535
+ fail('Usage: publish <id|file> [--out <path>]', 2);
536
+ const flow = await resolveFlow(arg);
537
+ const registry = createDefaultRegistry();
538
+ const artifact = await publishFlow(flow, registry);
539
+ const outPath = outPathArg ?? `artifacts/${flow.id}.json`;
540
+ mkdirSync(dirname(outPath), { recursive: true });
541
+ writeFileSync(outPath, `${JSON.stringify(artifact, null, 2)}\n`);
542
+ printJson({ ok: true, path: outPath, nodeTypes: Object.keys(artifact.nodeHashes).length });
543
+ return 0;
544
+ }
545
+ case 'save': {
546
+ const file = args[0];
547
+ if (!file)
548
+ fail('Usage: save <file>', 2);
549
+ if (!existsSync(file))
550
+ fail(`No such file: ${file}`, 2);
551
+ const flow = JSON.parse(readFileSync(file, 'utf8'));
552
+ await client.saveWorkflow(flow);
553
+ printJson({ ok: true, id: flow.id });
554
+ return 0;
555
+ }
556
+ case 'delete': {
557
+ const id = args[0];
558
+ if (!id)
559
+ fail('Usage: delete <id>', 2);
560
+ const result = await client.deleteOperation(id);
561
+ printJson(result);
562
+ return result.ok ? 0 : 1;
563
+ }
564
+ case 'create': {
565
+ const id = args.filter((a) => !a.startsWith('--'))[0];
566
+ if (!id) {
567
+ fail('Usage: create <id> --method <GET|POST|PATCH|PUT|DELETE> --path </route> [--name "<display>"]', 2);
568
+ }
569
+ const method = flagValue(args, '--method')?.toUpperCase();
570
+ const path = flagValue(args, '--path');
571
+ const name = flagValue(args, '--name') ?? titleCaseSlug(id.split('/').filter(Boolean).pop() ?? id);
572
+ const now = new Date().toISOString();
573
+ // Minimal valid shell: Input → terminus. HTTP endpoints get a Response
574
+ // node + http trigger (method decided up front); non-HTTP ops get a Result.
575
+ // A path with :params (e.g. /channels/:id) needs a matching Input default
576
+ // so a plain "Run" (no scenario) doesn't crash the first node reading
577
+ // ctx.input.params.<name> — seed empty-string placeholders up front.
578
+ const pathParams = path ? parsePathParams(path) : [];
579
+ const inputConfig = pathParams.length > 0
580
+ ? { defaults: { params: Object.fromEntries(pathParams.map((p) => [p, ''])) } }
581
+ : {};
582
+ const nodes = [{ id: 'input', type: 'Input', label: 'Input', position: { x: 0, y: 0 }, config: inputConfig }];
583
+ const edges = [];
584
+ const terminus = method ? 'response' : 'result';
585
+ nodes.push({ id: terminus, type: method ? 'Response' : 'Result', label: method ? 'Response' : 'Result', position: { x: 320, y: 0 }, config: {} });
586
+ edges.push({ id: 'input-' + terminus, source: 'input', target: terminus });
587
+ const flow = {
588
+ id,
589
+ name,
590
+ version: 1,
591
+ nodes,
592
+ edges,
593
+ createdAt: now,
594
+ updatedAt: now,
595
+ ...(method && path ? { http: { method, path } } : {}),
596
+ };
597
+ const result = await client.createOperation(flow, id);
598
+ printJson(result.ok ? { ok: true, id, name, ...(method && path ? { http: { method, path } } : {}) } : { error: result.error });
599
+ return result.ok ? 0 : 1;
600
+ }
601
+ case 'rename': {
602
+ const positional = args.filter((a) => !a.startsWith('--'));
603
+ const [oldId, newId] = positional;
604
+ if (!oldId || !newId)
605
+ fail('Usage: rename <old-id> <new-id> [--name "<display>"]', 2);
606
+ const nameIdx = args.indexOf('--name');
607
+ const customName = nameIdx !== -1 ? args[nameIdx + 1] : undefined;
608
+ const flow = (await client.getWorkflow(oldId));
609
+ if (!flow)
610
+ fail(`Unknown operation: ${oldId}`, 1);
611
+ // The op id IS its apis path. Rename it AND bring the human-facing bits
612
+ // along: the display `name` (so the sidebar/title stops showing a vague
613
+ // auto-generated label) and the http path when it mirrored the old id.
614
+ const leaf = newId.split('/').filter(Boolean).pop() ?? newId;
615
+ const displayName = customName ?? titleCaseSlug(leaf);
616
+ const next = { ...flow, id: newId, name: displayName };
617
+ if (next.http && next.http.path === `/${oldId}`) {
618
+ next.http = { ...next.http, path: `/${newId}` };
619
+ }
620
+ const created = await client.createOperation(next, newId);
621
+ if (!created.ok) {
622
+ printJson({ error: `rename failed at create: ${created.error}` });
623
+ return 1;
624
+ }
625
+ const removed = await client.deleteOperation(oldId);
626
+ printJson({ ok: true, from: oldId, to: newId, name: displayName, httpPath: next.http?.path, oldRemoved: removed.ok });
627
+ return 0;
628
+ }
629
+ case 'samples': {
630
+ const nodeId = args[0];
631
+ if (!nodeId)
632
+ fail('Usage: samples <nodeId>', 2);
633
+ printJson(await client.samples(nodeId));
634
+ return 0;
635
+ }
636
+ case 'test': {
637
+ let opId;
638
+ let environmentName;
639
+ let json = false;
640
+ let mock = false;
641
+ for (let i = 0; i < args.length; i++) {
642
+ const a = args[i];
643
+ if (a === '--environment') {
644
+ environmentName = args[++i];
645
+ }
646
+ else if (a === '--json') {
647
+ json = true;
648
+ }
649
+ else if (a === '--mock') {
650
+ mock = true;
651
+ }
652
+ else if (opId === undefined) {
653
+ opId = a;
654
+ }
655
+ }
656
+ const projectDir = process.env.EMBERFLOW_PROJECT
657
+ ? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
658
+ : process.cwd();
659
+ let report;
660
+ try {
661
+ report = await runScenarioSuite({ projectDir, opId, environmentName, mock });
662
+ }
663
+ catch (err) {
664
+ if (err instanceof ScenarioTestUsageError)
665
+ fail(err.message, 2);
666
+ throw err;
667
+ }
668
+ if (json) {
669
+ printJson(report.results);
670
+ }
671
+ else {
672
+ for (const r of report.results) {
673
+ const line = formatScenarioLine(r);
674
+ if (line)
675
+ process.stdout.write(`${line}\n`);
676
+ }
677
+ process.stdout.write(`${formatSummary(report)}\n`);
678
+ }
679
+ // CI foot-gun guard: a suite that asserted NOTHING still exits 0, but
680
+ // that green is vacuous — say so on stderr (stdout stays parseable).
681
+ if (report.passed + report.failed === 0) {
682
+ process.stderr.write(`warning: no scenarios asserted (${report.skipped} skipped)\n`);
683
+ }
684
+ return report.failed > 0 ? 1 : 0;
685
+ }
686
+ case 'doctor': {
687
+ let opId;
688
+ let fix = false;
689
+ for (const a of args) {
690
+ if (a === '--fix') {
691
+ fix = true;
692
+ }
693
+ else if (a.startsWith('--')) {
694
+ fail(`Unknown flag: ${a}. Usage: doctor [opId] [--fix]`, 2);
695
+ }
696
+ else if (opId === undefined) {
697
+ opId = a;
698
+ }
699
+ else {
700
+ fail('Usage: doctor [opId] [--fix]', 2);
701
+ }
702
+ }
703
+ const projectDir = process.env.EMBERFLOW_PROJECT
704
+ ? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
705
+ : process.cwd();
706
+ let project;
707
+ try {
708
+ project = requireProjectWhenExplicit(await loadProjectConfig(projectDir), process.env.EMBERFLOW_PROJECT, projectDir);
709
+ }
710
+ catch (err) {
711
+ // Unreadable project (EMBERFLOW_PROJECT points at a dir with no
712
+ // emberflow.config.*) is a usage-shaped failure for `doctor`, not an
713
+ // internal error — exit 2 per the documented contract, not the
714
+ // generic-catch 1 in runCli.
715
+ fail(err instanceof Error ? err.message : String(err), 2);
716
+ }
717
+ const apiStore = buildApiStore(project);
718
+ const { validation: doctorRegistry } = buildRegistries(project);
719
+ const allOps = apiStore.list();
720
+ let targetOps = allOps;
721
+ if (opId !== undefined) {
722
+ const op = allOps.find((o) => o.id === opId);
723
+ if (!op)
724
+ fail(`Unknown operation: '${opId}'`, 2);
725
+ targetOps = [op];
726
+ }
727
+ let errors = 0;
728
+ let warnings = 0;
729
+ let info = 0;
730
+ const lines = [];
731
+ for (const op of targetOps) {
732
+ let flow = op;
733
+ lines.push(op.id);
734
+ if (fix) {
735
+ const relPath = apiStore.pathOf(op.id);
736
+ const { flow: seededFlow, seeded } = seedParamDefaults(op);
737
+ if (seeded.length > 0 && relPath) {
738
+ apiStore.save(seededFlow, relPath);
739
+ flow = apiStore.load(op.id) ?? seededFlow;
740
+ lines.push(`fixed ${op.id}: seeded params — ${seeded.join(', ')}`);
741
+ }
742
+ else {
743
+ flow = seededFlow;
744
+ }
745
+ }
746
+ const infraNodes = [];
747
+ const outputFieldsByNode = {};
748
+ const mutationSourcesByNode = {};
749
+ for (const n of flow.nodes) {
750
+ if (!doctorRegistry.has(n.type))
751
+ continue;
752
+ const { definition, implementation } = doctorRegistry.get(n.type);
753
+ const { traceKind } = definition;
754
+ if (traceKind === 'db' || traceKind === 'http' || traceKind === 'llm') {
755
+ infraNodes.push({ id: n.id, traceKind });
756
+ }
757
+ // Declared output fields for inputmap-schema-mismatch — only when the
758
+ // node's outputSchema actually declares fields (empty/absent → skip, so
759
+ // dynamic-output nodes like Input/Subflow never false-positive).
760
+ const fields = definition.outputSchema?.fields;
761
+ if (fields && fields.length > 0) {
762
+ outputFieldsByNode[n.id] = fields.map((f) => f.name);
763
+ }
764
+ // Implementation source for simulated-commit — only mutation nodes,
765
+ // and only when the implementation is a function (String() is always
766
+ // safe, but a native/bound stub's source never carries the marker).
767
+ if (definition.effects === 'mutation' && typeof implementation === 'function') {
768
+ mutationSourcesByNode[n.id] = String(implementation);
769
+ }
770
+ }
771
+ const diagnostics = diagnoseOperation(flow, flow.scenarios, {
772
+ infraNodes,
773
+ outputFieldsByNode,
774
+ mutationSourcesByNode,
775
+ languageDrift: project?.languageDrift,
776
+ });
777
+ for (const d of diagnostics) {
778
+ lines.push(` ${d.severity} ${d.code}: ${d.message}`);
779
+ if (d.severity === 'error')
780
+ errors++;
781
+ else if (d.severity === 'warning')
782
+ warnings++;
783
+ else
784
+ info++;
785
+ }
786
+ }
787
+ lines.push(`${errors} errors, ${warnings} warnings, ${info} info across ${targetOps.length} operations`);
788
+ process.stdout.write(`${lines.join('\n')}\n`);
789
+ return errors > 0 ? 1 : 0;
790
+ }
791
+ default: {
792
+ process.stderr.write(USAGE);
793
+ return 2;
794
+ }
795
+ }
796
+ }
797
+ /** True when this module is the process entry point (run directly, not imported). */
798
+ const invokedDirectly = process.argv[1] !== undefined &&
799
+ realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
800
+ if (invokedDirectly) {
801
+ void runCli(process.argv.slice(2)).then((code) => {
802
+ process.exitCode = code;
803
+ });
804
+ }