@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick McCurley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # Emberflow
2
+
3
+ Emberflow gives your project a visual studio for **API operations modelled as
4
+ node graphs** — build an endpoint by wiring nodes, run it, test it, and ship it
5
+ from one place. Every operation runs in two modes from day one: **real**
6
+ (against your live environments) and **mock** (design-first, driven by scenario
7
+ data, nothing real touched). It is **agent-native** — Codex and Claude Code can
8
+ author flows, scenarios, nodes, and environments for you through installed
9
+ skills, and a scout that reads your codebase so agents reuse the infrastructure
10
+ you already have.
11
+
12
+ A consumer should be running in about five minutes. Read on, or jump to the
13
+ [CLI cheatsheet](#cli-cheatsheet).
14
+
15
+ ## Quick start
16
+
17
+ > **⚠ Publish pending.** The package is scoped as `@xdelivered/emberflow`
18
+ > (the bare name `emberflow` on npm belongs to an unrelated package — don't
19
+ > `npm i emberflow`). Until the scoped package is published, install from
20
+ > **git** or a **packed tarball** (both fully supported, both ship or build
21
+ > the studio + runner). **Full instructions: [INSTALL.md](INSTALL.md).**
22
+
23
+ ```sh
24
+ # add Emberflow to a project — git install (runs a guarded `prepare` build):
25
+ npm i -D 'git+https://github.com/pmccurley87/emberflow.git'
26
+
27
+ # …or from a packed tarball (built from a checkout of this repo):
28
+ npm pack # in the emberflow checkout → xdelivered-emberflow-0.2.0.tgz
29
+ npm i -D ../path/to/xdelivered-emberflow-0.2.0.tgz
30
+
31
+ # then, in your project:
32
+ npx emberflow init # asks JS or TS, scaffolds config + a hello op + agent skills
33
+ ```
34
+
35
+ Once published, this becomes `npm i -D @xdelivered/emberflow` /
36
+ `npm create @xdelivered/emberflow` — the scaffolding is already wired for it.
37
+
38
+ `init` asks one question — **JavaScript or TypeScript?** (`--js` / `--ts` to
39
+ skip the prompt) — and that decides your runtime:
40
+
41
+ - **`--js`** (default) scaffolds `emberflow.config.mjs` with JSDoc types. Runs
42
+ on **plain Node** against Emberflow's compiled `dist/` — **no `tsx`**.
43
+ - **`--ts`** scaffolds `emberflow.config.ts` + a `tsconfig.json` and asks you to
44
+ `npm i -D tsx typescript`. The runner boots your TypeScript under `tsx`.
45
+
46
+ `init` scaffolds: the config file, `emberflow/apis/default/hello` (a working
47
+ example operation + its scenarios), and the four Emberflow agent skills into
48
+ whatever harness it detects (`.claude/` and/or `.codex/`, global or local).
49
+
50
+ ## First run — the welcome checklist
51
+
52
+ The studio opens on a **welcome checklist** for a fresh project (reach it any
53
+ time from the toolbar's **Setup** item). Each row is one setup step with a
54
+ status glyph and a button that deep-links to the surface that fixes it:
55
+
56
+ - **Coding agent detected** — shows which of Codex / Claude Code is on your
57
+ `PATH`, with the detected **version** (a stale CLI that rejects modern models
58
+ is surfaced honestly, not hidden).
59
+ - **Environments configured** — Emberflow splits config from secrets across two
60
+ files:
61
+ - `emberflow.environments.json` — **committed-safe**: environment names,
62
+ non-secret `vars` (URLs, ids, flags), `protected` flags, and the *names* of
63
+ the secrets each environment needs. Never any secret value.
64
+ - `emberflow.secrets.json` — **git-ignored, `chmod 600`**: the actual values,
65
+ keyed by environment. Agents never read it. A value of `"$ENV:VAR_NAME"`
66
+ resolves from `process.env` at load.
67
+ - **Secrets present** for the default environment.
68
+ - **Agent skills installed** (re-run `npx emberflow init` to add them).
69
+ - **Infrastructure scouted** — dispatches the scout (below).
70
+ - **First operation** — open the hello op or ask the agent to build one.
71
+
72
+ A brand-new project **starts in Mock**: with no environments there is nothing
73
+ real to serve against, so everything previews from scenarios until you
74
+ deliberately create an environment. The environment dropdown (top-right) is the
75
+ single mode control — **Mock** is its first row, real environments follow, and
76
+ switching to a real one crosses a "Go live?" confirmation.
77
+
78
+ ## The infrastructure scout
79
+
80
+ Point an agent at a brownfield repo and the **scout** reads it — dependencies,
81
+ lockfiles, ORM schemas, config files, env-var references, HTTP/SDK usage — and
82
+ writes a **committed, secrets-free manifest** at `emberflow/infrastructure.json`
83
+ describing what your project already uses. The studio's **Infra** dock tab
84
+ renders it, and agents read it before authoring operations so they **reuse**
85
+ your real databases/APIs/providers instead of inventing parallel config.
86
+
87
+ ```json
88
+ {
89
+ "version": 1,
90
+ "greenfield": false,
91
+ "summary": "Express app with Postgres (Prisma), Stripe, and SendGrid.",
92
+ "items": [
93
+ {
94
+ "id": "postgres-main",
95
+ "kind": "database",
96
+ "name": "Postgres (Prisma)",
97
+ "evidence": [{ "file": "prisma/schema.prisma", "note": "datasource provider=postgresql" }],
98
+ "suggestedSecretRefs": ["DATABASE_URL"],
99
+ "notes": "Schema defines User, Order, Invoice models."
100
+ }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ Names and env-var **names** only — never values. Run it from the welcome
106
+ checklist or the agent panel; it needs a coding agent on your `PATH`.
107
+
108
+ ## Building APIs
109
+
110
+ You build operations visually (drag nodes, wire them, set HTTP metadata), and
111
+ your own code plugs in as **custom nodes** registered in the config's
112
+ `registerNodes(registry)` — they run server-side next to your app, so they reach
113
+ your DB and services. Or hand it to an agent: the installed **skills** teach it
114
+ Emberflow's file layout, node mechanics, and review rubric, and it consults the
115
+ **scout manifest** first so it reuses your infrastructure. Ask in the agent
116
+ panel ("build a checkout endpoint"), and it authors the flow, scenarios, and any
117
+ new nodes, then runs `doctor` to clean up before finishing.
118
+
119
+ **Operational commit posture** — an operation runs at one of three levels:
120
+
121
+ - **Mock** — infrastructure nodes return canned scenario outputs; nothing real
122
+ is ever touched, no auth attached. Design-time default.
123
+ - **Dry-run** — real environment, but mutation nodes with `config.commit`
124
+ unset (or a protected env's safe-mode on) perform reads and log what they
125
+ *would* write, without doing it.
126
+ - **Commit** — `config.commit === true` on a real, unprotected env: mutation
127
+ nodes perform the real side effect. Faking success on the commit path is
128
+ forbidden and `doctor` flags it.
129
+
130
+ ## CLI cheatsheet
131
+
132
+ | Command | What it does |
133
+ |---|---|
134
+ | `emberflow init [--js\|--ts] [--global\|--local] [--no-skills] [--no-launch]` | scaffold config, example op, agent skills |
135
+ | `emberflow dev [--port N] [--project DIR]` | studio + runner in one process, opens the browser |
136
+ | `emberflow serve [--port N] [--mock]` | run the API host headless (no studio) |
137
+ | `emberflow test [opId] [--environment NAME] [--json]` | assert scenario `expect` blocks in-process (CI-ready) |
138
+ | `emberflow doctor [opId] [--fix]` | report/repair operation invariants in-process |
139
+ | `emberflow run <opId> [--scenario NAME]` | run an operation against a live runner |
140
+ | `emberflow list-environments` / `login-environment <name>` | inspect / authenticate an environment |
141
+ | `emberflow mcp` | serve the MCP tools (list/get/save/validate/run workflows) |
142
+
143
+ ## Docs
144
+
145
+ Depth on each area lives under [`docs/superpowers/specs/`](docs/superpowers/specs/):
146
+
147
+ - [Consuming Emberflow](docs/superpowers/specs/2026-07-05-consuming-emberflow.md) — config, custom nodes, runtime matrix.
148
+ - [Agent-native usage](docs/superpowers/specs/2026-07-06-agent-native-usage.md) — how the studio dispatches agents.
149
+ - [HTTP API endpoints](docs/superpowers/specs/2026-07-07-api-endpoints-usage.md) — auth policies, Response nodes, mock serving.
150
+ - [Onboarding & scout design](docs/superpowers/specs/2026-07-12-onboarding-infra-scout-design.md) — welcome checklist + infrastructure scout.
151
+
152
+ Working on Emberflow itself? See [CONTRIBUTING.md](CONTRIBUTING.md).
153
+ </content>
@@ -0,0 +1,185 @@
1
+ import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ // Spy on spawn so runDev/runServe/launchServer never actually spawn a child
7
+ // process during these tests — we only care about what commands.ts decided
8
+ // to launch (or not).
9
+ const spawnCalls: Array<{ cmd: string; args: string[]; env: Record<string, string | undefined> }> = [];
10
+ vi.mock('node:child_process', () => ({
11
+ spawn: (cmd: string, args: string[], opts: { env: Record<string, string | undefined> }) => {
12
+ spawnCalls.push({ cmd, args, env: opts.env });
13
+ const listeners: Record<string, ((code: number) => void)[]> = {};
14
+ queueMicrotask(() => listeners.exit?.forEach((cb) => cb(0)));
15
+ return { on: (event: string, cb: (code: number) => void) => {
16
+ (listeners[event] ??= []).push(cb);
17
+ } };
18
+ },
19
+ }));
20
+
21
+ const { parseArgs, runCommand } = await import('./commands');
22
+
23
+ describe('parseArgs', () => {
24
+ it('parses dev with flags', () => {
25
+ expect(parseArgs(['dev', '--port', '9000', '--project', './x'])).toEqual({
26
+ command: 'dev', port: 9000, project: './x', scenario: undefined, rest: [],
27
+ });
28
+ });
29
+ it('defaults to help when no command', () => {
30
+ expect(parseArgs([]).command).toBe('help');
31
+ });
32
+ it('parses run with a flow and scenario', () => {
33
+ const p = parseArgs(['run', 'order-triage', '--scenario', 'vip']);
34
+ expect(p.command).toBe('run');
35
+ expect(p.rest).toEqual(['order-triage']);
36
+ expect(p.scenario).toBe('vip');
37
+ });
38
+ it('parses init with --no-skills', () => {
39
+ const p = parseArgs(['init', '--no-skills']);
40
+ expect(p.command).toBe('init');
41
+ expect(p.noSkills).toBe(true);
42
+ });
43
+ it('defaults noSkills to undefined when absent', () => {
44
+ expect(parseArgs(['init']).noSkills).toBeUndefined();
45
+ });
46
+ it('parses --global as scope global', () => {
47
+ expect(parseArgs(['init', '--global']).scope).toBe('global');
48
+ });
49
+ it('parses --local as scope repo', () => {
50
+ expect(parseArgs(['init', '--local']).scope).toBe('repo');
51
+ });
52
+ it('parses --no-launch', () => {
53
+ expect(parseArgs(['init', '--no-launch']).noLaunch).toBe(true);
54
+ });
55
+ it('parses the serve command with flags', () => {
56
+ expect(parseArgs(['serve', '--port', '9001', '--project', './p'])).toEqual({
57
+ command: 'serve', port: 9001, project: './p', scenario: undefined, rest: [],
58
+ });
59
+ });
60
+ it('parses --js', () => {
61
+ expect(parseArgs(['init', '--js']).js).toBe(true);
62
+ });
63
+ it('parses --ts', () => {
64
+ expect(parseArgs(['init', '--ts']).ts).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe('runCommand init --js/--ts', () => {
69
+ it('errors when both --js and --ts are passed', async () => {
70
+ const errors: unknown[] = [];
71
+ const origError = console.error;
72
+ console.error = (...args: unknown[]) => { errors.push(args); };
73
+ try {
74
+ const code = await runCommand(parseArgs(['init', '--js', '--ts', '--no-skills', '--no-launch']));
75
+ expect(code).toBe(1);
76
+ expect(errors.join(' ')).toContain('mutually exclusive');
77
+ } finally {
78
+ console.error = origError;
79
+ }
80
+ });
81
+ });
82
+
83
+ // Repo root — a nested scratch dir here still resolves `tsx` by walking up to
84
+ // this repo's own node_modules, which is what a "tsx IS resolvable" fixture
85
+ // needs without faking a whole install.
86
+ const REPO_ROOT = process.cwd();
87
+
88
+ function withCwd<T>(dir: string, fn: () => Promise<T>): Promise<T> {
89
+ const prev = process.cwd();
90
+ process.chdir(dir);
91
+ return fn().finally(() => process.chdir(prev));
92
+ }
93
+
94
+ function silenceLog<T>(fn: () => Promise<T>): Promise<T> {
95
+ const orig = console.log;
96
+ console.log = () => {};
97
+ return fn().finally(() => { console.log = orig; });
98
+ }
99
+
100
+ describe('runCommand init --ts auto-launch (stale vs fresh runtime decision)', () => {
101
+ const dirs: string[] = [];
102
+ afterEach(() => {
103
+ for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
104
+ spawnCalls.length = 0;
105
+ });
106
+
107
+ it('skips the launch and prints guidance when tsx is not resolvable from the project', async () => {
108
+ const d = mkdtempSync(join(tmpdir(), 'ef-cmd-notsx-'));
109
+ dirs.push(d);
110
+ const logs: string[] = [];
111
+ const origLog = console.log;
112
+ console.log = (...args: unknown[]) => { logs.push(args.join(' ')); };
113
+ try {
114
+ const code = await withCwd(d, () => runCommand(parseArgs(['init', '--ts', '--no-skills'])));
115
+ expect(code).toBe(0);
116
+ expect(spawnCalls).toEqual([]); // no launch attempt at all
117
+ const out = logs.join('\n');
118
+ expect(out).toContain('npm i -D tsx typescript');
119
+ expect(out).toContain('npx emberflow dev');
120
+ } finally {
121
+ console.log = origLog;
122
+ }
123
+ });
124
+
125
+ it('re-decides the runtime against the scaffolded project when tsx IS resolvable, instead of trusting the stale bin-startup context', async () => {
126
+ // A "consumer install" context decided BEFORE scaffolding: dist/ present,
127
+ // no src/, decided runnerMode 'node' (no config existed yet at bin startup).
128
+ const consumerRoot = mkdtempSync(join(tmpdir(), 'ef-cmd-consumer-'));
129
+ mkdirSync(join(consumerRoot, 'dist'));
130
+ dirs.push(consumerRoot);
131
+ // Project dir nested under THIS repo so tsx resolves via the repo's own
132
+ // node_modules (see REPO_ROOT comment above).
133
+ const projectDir = mkdtempSync(join(REPO_ROOT, '.tmp-ef-cmd-project-'));
134
+ dirs.push(projectDir);
135
+ const staleCtx = { runnerMode: 'node' as const, packageRoot: consumerRoot };
136
+
137
+ await silenceLog(async () => {
138
+ const code = await withCwd(projectDir, () =>
139
+ runCommand(parseArgs(['init', '--ts', '--no-skills']), staleCtx)
140
+ );
141
+ expect(code).toBe(0);
142
+ });
143
+
144
+ // The launched server process is spawned via `npx tsx …` (fresh decision),
145
+ // NOT plain `node …` (the stale decision passed in as staleCtx.runnerMode).
146
+ const serverLaunch = spawnCalls.find((c) =>
147
+ c.args.some((a) => a.endsWith('index.ts') || a.endsWith('index.js'))
148
+ );
149
+ expect(serverLaunch?.cmd).toBe('npx');
150
+ expect(serverLaunch?.args).toContain('tsx');
151
+ });
152
+ });
153
+
154
+ describe('runDev/runServe project-dir precedence (EMBERFLOW_PROJECT env first)', () => {
155
+ afterEach(() => { spawnCalls.length = 0; delete process.env.EMBERFLOW_PROJECT; });
156
+
157
+ it('dev: an already-set EMBERFLOW_PROJECT wins over cwd when --project is absent', async () => {
158
+ process.env.EMBERFLOW_PROJECT = '/env/project';
159
+ await runCommand(parseArgs(['dev']));
160
+ expect(spawnCalls[0]?.env.EMBERFLOW_PROJECT).toBe('/env/project');
161
+ });
162
+
163
+ it('dev: --project wins over cwd when EMBERFLOW_PROJECT is unset', async () => {
164
+ const code = await runCommand(parseArgs(['dev', '--project', './somewhere']));
165
+ void code;
166
+ expect(spawnCalls[0]?.env.EMBERFLOW_PROJECT).toBe(join(process.cwd(), 'somewhere'));
167
+ });
168
+
169
+ it('dev: EMBERFLOW_PROJECT wins over --project when both are set', async () => {
170
+ process.env.EMBERFLOW_PROJECT = '/env/project';
171
+ await runCommand(parseArgs(['dev', '--project', './somewhere']));
172
+ expect(spawnCalls[0]?.env.EMBERFLOW_PROJECT).toBe('/env/project');
173
+ });
174
+
175
+ it('dev: falls back to cwd when neither env nor --project is set', async () => {
176
+ await runCommand(parseArgs(['dev']));
177
+ expect(spawnCalls[0]?.env.EMBERFLOW_PROJECT).toBe(process.cwd());
178
+ });
179
+
180
+ it('serve: same precedence as dev', async () => {
181
+ process.env.EMBERFLOW_PROJECT = '/env/project';
182
+ await runCommand(parseArgs(['serve', '--project', './somewhere']));
183
+ expect(spawnCalls[0]?.env.EMBERFLOW_PROJECT).toBe('/env/project');
184
+ });
185
+ });
@@ -0,0 +1,306 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { createInterface } from 'node:readline';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join, resolve } from 'node:path';
7
+ import { decideRuntime, resolveProjectDir } from './runtime.mjs';
8
+
9
+ export interface ParsedArgs {
10
+ command: string;
11
+ port?: number;
12
+ project?: string;
13
+ scenario?: string;
14
+ noSkills?: boolean;
15
+ scope?: 'global' | 'repo';
16
+ noLaunch?: boolean;
17
+ mock?: boolean;
18
+ js?: boolean;
19
+ ts?: boolean;
20
+ rest: string[];
21
+ }
22
+
23
+ /** How the bin decided to run — passed down from bin/emberflow.mjs (see
24
+ * bin/runtime.mjs). `runnerMode` selects `node dist/server/*.js` vs a tsx spawn;
25
+ * `packageRoot` is the real package root (bin/emberflow.mjs is never in dist, so
26
+ * it always resolves this correctly — unlike this module's own location, which
27
+ * is dist/bin/commands.js when compiled). */
28
+ export interface RuntimeContext {
29
+ runnerMode: 'node' | 'tsx';
30
+ packageRoot: string;
31
+ }
32
+
33
+ export function parseArgs(argv: string[]): ParsedArgs {
34
+ const [command = 'help', ...tail] = argv;
35
+ const out: ParsedArgs = { command, rest: [] };
36
+ for (let i = 0; i < tail.length; i++) {
37
+ const a = tail[i];
38
+ if (a === '--port') out.port = Number(tail[++i]);
39
+ else if (a === '--project') out.project = tail[++i];
40
+ else if (a === '--scenario') out.scenario = tail[++i];
41
+ else if (a === '--no-skills') out.noSkills = true;
42
+ else if (a === '--global') out.scope = 'global';
43
+ else if (a === '--local') out.scope = 'repo';
44
+ else if (a === '--no-launch') out.noLaunch = true;
45
+ else if (a === '--mock') out.mock = true;
46
+ else if (a === '--js') out.js = true;
47
+ else if (a === '--ts') out.ts = true;
48
+ else out.rest.push(a);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** Ask the user whether to install skills globally or per-repo. Guarded on TTY so
54
+ * it never hangs a non-interactive run (tests, CI, piped input). */
55
+ function promptScope(): Promise<'global' | 'repo'> {
56
+ if (!process.stdin.isTTY) return Promise.resolve('repo');
57
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
58
+ return new Promise((res) => {
59
+ rl.question(
60
+ 'Install Emberflow skills for [g]lobal (all projects) or [r]epo (this project)? [r] ',
61
+ (answer) => {
62
+ rl.close();
63
+ res(answer.trim().toLowerCase().startsWith('g') ? 'global' : 'repo');
64
+ }
65
+ );
66
+ });
67
+ }
68
+
69
+ /** Ask the user which language to author their APIs/nodes in. Same TTY guard as
70
+ * promptScope — never hangs a non-interactive run (tests, CI, piped input),
71
+ * defaulting to javascript. */
72
+ function promptLanguage(): Promise<'javascript' | 'typescript'> {
73
+ if (!process.stdin.isTTY) return Promise.resolve('javascript');
74
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
75
+ return new Promise((res) => {
76
+ rl.question(
77
+ 'Author your APIs in [j]avascript or [t]ypescript? [j] ',
78
+ (answer) => {
79
+ rl.close();
80
+ res(answer.trim().toLowerCase().startsWith('t') ? 'typescript' : 'javascript');
81
+ }
82
+ );
83
+ });
84
+ }
85
+
86
+ /** Fallback package root used when runCommand is called without a RuntimeContext
87
+ * (e.g. a direct unit test). In normal use bin/emberflow.mjs passes the real
88
+ * root — important because this module compiles to dist/bin/commands.js, whose
89
+ * own `..` is dist/, not the package root. */
90
+ const selfPkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
91
+
92
+ const defaultCtx: RuntimeContext = { runnerMode: 'tsx', packageRoot: selfPkgRoot };
93
+
94
+ /** Spawn `tsx <entry>` inheriting stdio, with extra env. Returns the child's exit code. */
95
+ function runTsx(
96
+ ctx: RuntimeContext,
97
+ entry: string,
98
+ env: Record<string, string>,
99
+ args: string[] = [],
100
+ ): Promise<number> {
101
+ return new Promise((res) => {
102
+ const child = spawn('npx', ['tsx', join(ctx.packageRoot, entry), ...args], {
103
+ stdio: 'inherit',
104
+ env: { ...process.env, ...env },
105
+ });
106
+ child.on('exit', (code) => res(code ?? 0));
107
+ });
108
+ }
109
+
110
+ /** Spawn `node <entry>` inheriting stdio, with extra env. Used for JS consumers,
111
+ * where the compiled dist/server runs on plain Node (no tsx child — so it works
112
+ * inside the codex sandbox). */
113
+ function runNode(entry: string, env: Record<string, string>, args: string[] = []): Promise<number> {
114
+ return new Promise((res) => {
115
+ const child = spawn(process.execPath, [entry, ...args], {
116
+ stdio: 'inherit',
117
+ env: { ...process.env, ...env },
118
+ });
119
+ child.on('exit', (code) => res(code ?? 0));
120
+ });
121
+ }
122
+
123
+ /** Launch a server entry ('index' or 'mcp') in the mode the bin decided: plain
124
+ * `node dist/server/<name>.js` for JS consumers, `tsx server/<name>.ts` otherwise. */
125
+ function launchServer(
126
+ ctx: RuntimeContext,
127
+ name: 'index' | 'mcp',
128
+ env: Record<string, string>,
129
+ args: string[] = [],
130
+ ): Promise<number> {
131
+ if (ctx.runnerMode === 'node') {
132
+ return runNode(join(ctx.packageRoot, 'dist', 'server', `${name}.js`), env, args);
133
+ }
134
+ return runTsx(ctx, join('server', `${name}.ts`), env, args);
135
+ }
136
+
137
+ /** Build (if needed) and launch the studio dev server. Shared by `dev` and by
138
+ * `init` (which launches after scaffolding unless --no-launch is passed). */
139
+ async function runDev(p: ParsedArgs, ctx: RuntimeContext): Promise<number> {
140
+ // Same precedence as bin/runtime.mjs's decideRuntime (EMBERFLOW_PROJECT env →
141
+ // --project flag → cwd) so the launched process boots the same project the
142
+ // runtime decision was made against — a bare --project flag no longer
143
+ // silently overwrites an already-set EMBERFLOW_PROJECT.
144
+ const project = resolveProjectDir(p.project ? ['--project', p.project] : [], process.env, process.cwd());
145
+ // From a consumer install the shipped studio-dist exists; from source it may not.
146
+ if (!existsSync(join(ctx.packageRoot, 'studio-dist'))) {
147
+ console.log('[emberflow] building studio…');
148
+ await runTsx(ctx, 'node_modules/.bin/vite', {}, ['build']).catch(() => 0);
149
+ }
150
+ return launchServer(ctx, 'index', {
151
+ EMBERFLOW_SERVE_STUDIO: '1',
152
+ EMBERFLOW_PROJECT: project,
153
+ // `dev` runs a single (non-watch) process, so opening the browser once
154
+ // is the intended UX. (The server default is off so this repo's watch-mode
155
+ // `npm run server` doesn't spawn a tab per restart — see server/index.ts.)
156
+ EMBERFLOW_OPEN_BROWSER: '1',
157
+ ...(p.port ? { EMBERFLOW_RUNNER_PORT: String(p.port) } : {}),
158
+ ...(p.mock ? { EMBERFLOW_MOCK: '1' } : {}),
159
+ });
160
+ }
161
+
162
+ /** Run the runner headless: API + operation routes, NO studio, NO browser.
163
+ * This is the production-ish API host — `dev` additionally serves the studio
164
+ * and opens a browser tab. */
165
+ function runServe(p: ParsedArgs, ctx: RuntimeContext): Promise<number> {
166
+ // See runDev's comment: same EMBERFLOW_PROJECT-first precedence as
167
+ // bin/runtime.mjs's resolveProjectDir.
168
+ const project = resolveProjectDir(p.project ? ['--project', p.project] : [], process.env, process.cwd());
169
+ return launchServer(ctx, 'index', {
170
+ EMBERFLOW_PROJECT: project,
171
+ ...(p.port ? { EMBERFLOW_RUNNER_PORT: String(p.port) } : {}),
172
+ ...(p.mock ? { EMBERFLOW_MOCK: '1' } : {}),
173
+ });
174
+ }
175
+
176
+ export async function runCommand(p: ParsedArgs, ctx: RuntimeContext = defaultCtx): Promise<number> {
177
+ const pkgRoot = ctx.packageRoot;
178
+ switch (p.command) {
179
+ case 'dev':
180
+ return runDev(p, ctx);
181
+ case 'serve':
182
+ return runServe(p, ctx);
183
+ case 'mcp':
184
+ return launchServer(ctx, 'mcp', {});
185
+ case 'run': {
186
+ // In-process via tsx's register() loader — NO `tsx` child spawn, so it
187
+ // works inside the codex sandbox (tsx's IPC pipe is blocked there).
188
+ const args = ['run', ...p.rest, ...(p.scenario ? ['--scenario', p.scenario] : [])];
189
+ return (await import('../server/cli')).runCli(args);
190
+ }
191
+ case 'list-nodes':
192
+ case 'node-schema':
193
+ case 'list-workflows':
194
+ case 'get-workflow':
195
+ case 'list-environments':
196
+ case 'login-environment':
197
+ case 'set-environment-auth':
198
+ case 'serving':
199
+ case 'validate':
200
+ case 'publish':
201
+ case 'save':
202
+ case 'samples':
203
+ case 'create':
204
+ case 'delete':
205
+ case 'rename': {
206
+ // Rich CLI operation commands, also in-process (no `tsx` child spawn).
207
+ return (await import('../server/cli')).runCli([p.command, ...p.rest]);
208
+ }
209
+ case 'test': {
210
+ // In-process scenario suite runner — NO runner process. Needs the
211
+ // project dir (server/cli.ts's `test` case reads EMBERFLOW_PROJECT),
212
+ // same convention runDev/runServe use for --project.
213
+ if (p.project) process.env.EMBERFLOW_PROJECT = resolve(process.cwd(), p.project);
214
+ // --mock is parsed globally (dev/serve read it as EMBERFLOW_MOCK), so it
215
+ // never lands in p.rest — thread it through explicitly here.
216
+ return (await import('../server/cli')).runCli(['test', ...p.rest, ...(p.mock ? ['--mock'] : [])]);
217
+ }
218
+ case 'doctor': {
219
+ // In-process operation diagnostics — NO runner process, same
220
+ // EMBERFLOW_PROJECT convention as `test`.
221
+ if (p.project) process.env.EMBERFLOW_PROJECT = resolve(process.cwd(), p.project);
222
+ return (await import('../server/cli')).runCli(['doctor', ...p.rest]);
223
+ }
224
+ case 'init': {
225
+ // bin/init compiles alongside this module (dist/bin/init.js when built),
226
+ // so resolve the guard against THIS module's own location + extension —
227
+ // init.ts under tsx source, init.js under plain-node dist.
228
+ const initExt = import.meta.url.endsWith('.js') ? '.js' : '.ts';
229
+ const initEntry = fileURLToPath(new URL(`./init${initExt}`, import.meta.url));
230
+ if (!existsSync(initEntry)) {
231
+ console.log('emberflow init lands in a later step');
232
+ return 0;
233
+ }
234
+ if (p.js && p.ts) {
235
+ console.error('[emberflow] --js and --ts are mutually exclusive');
236
+ return 1;
237
+ }
238
+ try {
239
+ const mod = (await import('./init')) as {
240
+ runInit: (
241
+ cwd: string,
242
+ opts?: {
243
+ skills?: false | { scope: 'repo' | 'global'; home: string };
244
+ packageRoot?: string;
245
+ language?: 'javascript' | 'typescript';
246
+ }
247
+ ) => Promise<number>;
248
+ tsxResolvable: (cwd: string) => boolean;
249
+ };
250
+ const scope = p.scope ?? (await promptScope());
251
+ const language = p.js ? 'javascript' : p.ts ? 'typescript' : await promptLanguage();
252
+ const code = await mod.runInit(
253
+ process.cwd(),
254
+ p.noSkills
255
+ ? { skills: false, packageRoot: pkgRoot, language }
256
+ : { skills: { scope, home: homedir() }, packageRoot: pkgRoot, language }
257
+ );
258
+ if (code !== 0) return code;
259
+
260
+ console.log('');
261
+ console.log('[emberflow] next steps:');
262
+ console.log(' - run `npx emberflow dev` any time to relaunch the studio');
263
+ console.log(
264
+ p.noSkills
265
+ ? ' - skills were not installed (--no-skills)'
266
+ : ` - skills installed at ${scope === 'global' ? '~' : '.'}/.claude (and/or .codex) /skills`
267
+ );
268
+ console.log(' - your operations live in emberflow/apis (open the studio to build them)');
269
+ console.log('');
270
+
271
+ if (p.noLaunch) return 0;
272
+
273
+ // `ctx` reflects the runtime decision made at bin startup — BEFORE
274
+ // scaffolding existed. For a fresh TS project that decision was made with
275
+ // no config on disk (→ node mode), which then dies launching against the
276
+ // just-scaffolded emberflow.config.ts. Re-decide against the now-real
277
+ // project instead of trusting the stale context.
278
+ if (language === 'typescript') {
279
+ if (!mod.tsxResolvable(process.cwd())) {
280
+ console.log('');
281
+ console.log('[emberflow] tsx is not installed — skipping auto-launch.');
282
+ console.log(' npm i -D tsx typescript');
283
+ console.log(' npx emberflow dev');
284
+ return 0;
285
+ }
286
+ const decision = decideRuntime({ packageRoot: pkgRoot, projectDir: process.cwd(), env: process.env });
287
+ console.log('[emberflow] Starting Emberflow…');
288
+ return runDev(p, { runnerMode: decision.runnerMode as 'node' | 'tsx', packageRoot: pkgRoot });
289
+ }
290
+
291
+ console.log('[emberflow] Starting Emberflow…');
292
+ return runDev(p, ctx);
293
+ } catch (err) {
294
+ console.error('[emberflow] init failed:', err instanceof Error ? err.message : err);
295
+ return 1;
296
+ }
297
+ }
298
+ default:
299
+ console.log(
300
+ 'Usage: emberflow <dev|serve|mcp|init|run|test|doctor|list-nodes|node-schema|list-workflows|get-workflow|list-environments|login-environment|set-environment-auth|serving|validate|publish|save|create|delete|rename|samples> [--port N] [--project DIR] [--scenario NAME] [--no-skills] [--global|--local] [--no-launch] [--mock] [--js|--ts]\n' +
301
+ ' test [opId] [--environment NAME] [--json] Run scenario expectations in-process (no runner) — exit 0/1/2\n' +
302
+ ' doctor [opId] [--fix] Diagnose operation(s) in-process (no runner); --fix seeds param defaults — exit 0/1/2'
303
+ );
304
+ return 0;
305
+ }
306
+ }