@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,38 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { decideRuntime, resolveProjectDir, TSX_MISSING_MESSAGE } from './runtime.mjs';
5
+
6
+ // Decide the runtime BEFORE importing anything TypeScript: a JS consumer runs on
7
+ // plain Node (no tsx), a source checkout / `.ts` config runs under tsx.
8
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9
+ const argv = process.argv.slice(2);
10
+ const projectDir = resolveProjectDir(argv, process.env, process.cwd());
11
+ const decision = decideRuntime({ packageRoot, projectDir, env: process.env });
12
+
13
+ if (process.env.EMBERFLOW_DEBUG_RUNTIME) {
14
+ console.error(`[emberflow] runtime ${JSON.stringify(decision)}`);
15
+ }
16
+
17
+ if (decision.needsTsx) {
18
+ try {
19
+ const { register } = await import('tsx/esm/api');
20
+ register();
21
+ } catch {
22
+ console.error(TSX_MISSING_MESSAGE);
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ // Compiled commands for JS consumers (plain node); TypeScript source under tsx
28
+ // for the repo dev loop. The dynamic specifier is a string literal in each
29
+ // branch so Node's ESM loader resolves it directly.
30
+ const { parseArgs, runCommand } = decision.useDist
31
+ ? await import('../dist/bin/commands.js')
32
+ : await import('./commands.ts');
33
+
34
+ const code = await runCommand(parseArgs(argv), {
35
+ runnerMode: decision.runnerMode,
36
+ packageRoot,
37
+ });
38
+ process.exit(code);
@@ -0,0 +1,165 @@
1
+ // bin/init.test.ts
2
+ import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { afterEach, describe, expect, it, vi } from 'vitest';
6
+ import { runInit, tsxResolvable } from './init';
7
+
8
+ const dirs: string[] = [];
9
+ function scratch(): string { const d = mkdtempSync(join(tmpdir(), 'ef-init-')); dirs.push(d); return d; }
10
+ afterEach(() => { for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }); });
11
+
12
+ describe('runInit', () => {
13
+ it('scaffolds config, an example apis/ operation, and its scenario sidecar', async () => {
14
+ const d = scratch();
15
+ writeFileSync(join(d, 'package.json'), JSON.stringify({ name: 'consumer', scripts: {} }));
16
+ const code = await runInit(d);
17
+ expect(code).toBe(0);
18
+ expect(existsSync(join(d, 'emberflow.config.mjs'))).toBe(true);
19
+ // No legacy flows/ tree — the current model is apis/ only.
20
+ expect(existsSync(join(d, 'emberflow/flows'))).toBe(false);
21
+ expect(existsSync(join(d, 'emberflow/apis/default/hello.json'))).toBe(true);
22
+ expect(existsSync(join(d, 'emberflow/apis/default/hello.scenarios.json'))).toBe(true);
23
+ const pkg = JSON.parse(readFileSync(join(d, 'package.json'), 'utf8'));
24
+ expect(pkg.scripts.emberflow).toBe('emberflow dev');
25
+ const op = JSON.parse(readFileSync(join(d, 'emberflow/apis/default/hello.json'), 'utf8'));
26
+ expect(op.id).toBe('default/hello');
27
+ expect(op.http).toEqual({ method: 'GET', path: '/hello' });
28
+ expect(op.nodes.length).toBeGreaterThanOrEqual(2);
29
+ });
30
+
31
+ it('defaults to a javascript scaffold with language field and JSDoc typing', async () => {
32
+ const d = scratch();
33
+ await runInit(d, { skills: false });
34
+ const config = readFileSync(join(d, 'emberflow.config.mjs'), 'utf8');
35
+ expect(config).toContain("language: 'javascript'");
36
+ expect(config).toContain("@param {import('@xdelivered/emberflow').NodeRegistry} registry");
37
+ expect(existsSync(join(d, 'emberflow.config.ts'))).toBe(false);
38
+ expect(existsSync(join(d, 'tsconfig.json'))).toBe(false);
39
+ });
40
+
41
+ it('scaffolds a typescript config, tsconfig.json, and a typed registerNodes when language: typescript', async () => {
42
+ const d = scratch();
43
+ await runInit(d, { skills: false, language: 'typescript' });
44
+ expect(existsSync(join(d, 'emberflow.config.mjs'))).toBe(false);
45
+ const config = readFileSync(join(d, 'emberflow.config.ts'), 'utf8');
46
+ expect(config).toContain("language: 'typescript'");
47
+ expect(config).toContain("import type { NodeRegistry } from '@xdelivered/emberflow'");
48
+ expect(config).toContain('registerNodes(registry: NodeRegistry)');
49
+ expect(existsSync(join(d, 'tsconfig.json'))).toBe(true);
50
+ const tsconfig = JSON.parse(readFileSync(join(d, 'tsconfig.json'), 'utf8'));
51
+ expect(tsconfig.compilerOptions.strict).toBe(true);
52
+ expect(tsconfig.include).toContain('emberflow.config.ts');
53
+ });
54
+
55
+ it('does not overwrite an existing tsconfig.json on re-init', async () => {
56
+ const d = scratch();
57
+ writeFileSync(join(d, 'tsconfig.json'), '{"custom": true}\n');
58
+ await runInit(d, { skills: false, language: 'typescript' });
59
+ expect(readFileSync(join(d, 'tsconfig.json'), 'utf8')).toContain('custom');
60
+ });
61
+
62
+ it('refuses to scaffold a second config in the OTHER language (dead-config guard)', async () => {
63
+ const d = scratch();
64
+ await runInit(d, { skills: false, language: 'javascript' });
65
+ const code = await runInit(d, { skills: false, language: 'typescript' });
66
+ expect(code).toBe(1);
67
+ expect(existsSync(join(d, 'emberflow.config.ts'))).toBe(false);
68
+ // And the mirror direction: existing .ts config blocks a javascript init.
69
+ const d2 = scratch();
70
+ await runInit(d2, { skills: false, language: 'typescript' });
71
+ const code2 = await runInit(d2, { skills: false, language: 'javascript' });
72
+ expect(code2).toBe(1);
73
+ expect(existsSync(join(d2, 'emberflow.config.mjs'))).toBe(false);
74
+ });
75
+
76
+ it('does not clobber an existing config or emberflow script', async () => {
77
+ const d = scratch();
78
+ writeFileSync(join(d, 'emberflow.config.mjs'), 'export default { custom: true };\n');
79
+ writeFileSync(join(d, 'package.json'), JSON.stringify({ scripts: { emberflow: 'mine' } }));
80
+ await runInit(d);
81
+ expect(readFileSync(join(d, 'emberflow.config.mjs'), 'utf8')).toContain('custom: true');
82
+ expect(JSON.parse(readFileSync(join(d, 'package.json'), 'utf8')).scripts.emberflow).toBe('mine');
83
+ });
84
+
85
+ it('copies skills into .claude/skills by default (repo scope)', async () => {
86
+ const d = scratch();
87
+ await runInit(d, { skills: { scope: 'repo', home: d } });
88
+ expect(existsSync(join(d, '.claude/skills/emberflow-basics/SKILL.md'))).toBe(true);
89
+ });
90
+
91
+ it('skips skills entirely when skills: false', async () => {
92
+ const d = scratch();
93
+ await runInit(d, { skills: false });
94
+ expect(existsSync(join(d, '.claude'))).toBe(false);
95
+ });
96
+
97
+ it('creates .gitignore for emberflow infra, keeping apis + config committed', async () => {
98
+ const d = scratch();
99
+ await runInit(d, { skills: false });
100
+ const gi = readFileSync(join(d, '.gitignore'), 'utf8');
101
+ // infra ignored
102
+ for (const line of ['node_modules/', 'studio-dist/', 'emberflow.secrets.json', 'emberflow.environments.json']) {
103
+ expect(gi).toContain(line);
104
+ }
105
+ // the value stays committed — no ignore LINE for config or flows (the header
106
+ // comment may name them, but they must not appear as active patterns)
107
+ const patterns = gi.split('\n').filter((l) => l.trim() && !l.trim().startsWith('#'));
108
+ expect(patterns).not.toContain('emberflow.config.mjs');
109
+ expect(patterns).not.toContain('emberflow/');
110
+ expect(patterns).not.toContain('emberflow/flows/');
111
+ });
112
+
113
+ it('scaffolds emberflow/apis/default so the API host is ready out of the box', async () => {
114
+ const d = scratch();
115
+ await runInit(d, { skills: false });
116
+ expect(existsSync(join(d, 'emberflow', 'apis', 'default'))).toBe(true);
117
+ });
118
+
119
+ it('appends to an existing .gitignore without duplicating on re-run', async () => {
120
+ const d = scratch();
121
+ writeFileSync(join(d, '.gitignore'), 'bin/\nobj/\n');
122
+ await runInit(d, { skills: false });
123
+ await runInit(d, { skills: false });
124
+ const gi = readFileSync(join(d, '.gitignore'), 'utf8');
125
+ expect(gi).toContain('bin/'); // pre-existing preserved
126
+ expect((gi.match(/emberflow\.secrets\.json/g) ?? []).length).toBe(1);
127
+ });
128
+
129
+ it('warns when stamping "type": "module" on an existing package.json without a type field', async () => {
130
+ const d = scratch();
131
+ writeFileSync(join(d, 'package.json'), JSON.stringify({ name: 'consumer', scripts: {} }));
132
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
133
+ try {
134
+ await runInit(d, { skills: false, language: 'typescript' });
135
+ expect(JSON.parse(readFileSync(join(d, 'package.json'), 'utf8')).type).toBe('module');
136
+ expect(warnSpy.mock.calls.flat().join(' ')).toContain('set "type": "module" in package.json');
137
+ } finally {
138
+ warnSpy.mockRestore();
139
+ }
140
+ });
141
+
142
+ it('does not warn about stamping type:module when it is already set', async () => {
143
+ const d = scratch();
144
+ writeFileSync(join(d, 'package.json'), JSON.stringify({ name: 'consumer', type: 'module', scripts: {} }));
145
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
146
+ try {
147
+ await runInit(d, { skills: false, language: 'typescript' });
148
+ expect(warnSpy.mock.calls.flat().join(' ')).not.toContain('set "type": "module" in package.json');
149
+ } finally {
150
+ warnSpy.mockRestore();
151
+ }
152
+ });
153
+ });
154
+
155
+ describe('tsxResolvable', () => {
156
+ it('returns true when tsx resolves from a project (this repo has it as a devDependency)', () => {
157
+ expect(tsxResolvable(process.cwd())).toBe(true);
158
+ });
159
+
160
+ it('returns false in an isolated scratch dir with no node_modules', () => {
161
+ const d = scratch();
162
+ writeFileSync(join(d, 'package.json'), JSON.stringify({ name: 'isolated' }));
163
+ expect(tsxResolvable(d)).toBe(false);
164
+ });
165
+ });
package/bin/init.ts ADDED
@@ -0,0 +1,297 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, copyFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { detectHarnesses, resolveSkillDirs } from './skillTargets';
7
+
8
+ const MCP_SNIPPET = {
9
+ mcpServers: {
10
+ emberflow: { command: 'npx', args: ['emberflow', 'mcp'] },
11
+ },
12
+ };
13
+
14
+ function copySkillsRecursive(src: string, dest: string, log: string[]): void {
15
+ mkdirSync(dest, { recursive: true });
16
+ for (const entry of readdirSync(src)) {
17
+ const srcPath = join(src, entry);
18
+ const destPath = join(dest, entry);
19
+ if (statSync(srcPath).isDirectory()) {
20
+ copySkillsRecursive(srcPath, destPath, log);
21
+ } else if (!existsSync(destPath)) {
22
+ copyFileSync(srcPath, destPath);
23
+ log.push(destPath);
24
+ }
25
+ }
26
+ }
27
+
28
+ const JS_CONFIG_CONTENT =
29
+ "import { defineConfig } from '@xdelivered/emberflow';\n\n" +
30
+ 'export default defineConfig({\n' +
31
+ " language: 'javascript',\n" +
32
+ ' // Anchor only: this path is never created and holds no files. It just tells\n' +
33
+ ' // Emberflow where your project root is — operations always live in the\n' +
34
+ ' // SIBLING apis/ tree next to it:\n' +
35
+ ' // emberflow/apis/<api>/<folder…>/<op>.json — each op id = its path under apis/.\n' +
36
+ " flowsDir: 'emberflow/flows',\n" +
37
+ ' // Author your nodes here: registry.register(definition, async (ctx) => output).\n' +
38
+ " /** @param {import('@xdelivered/emberflow').NodeRegistry} registry */\n" +
39
+ ' registerNodes(registry) {\n' +
40
+ ' /* register your nodes */\n' +
41
+ ' },\n' +
42
+ '});\n';
43
+
44
+ const TS_CONFIG_CONTENT =
45
+ "import { defineConfig } from '@xdelivered/emberflow';\n" +
46
+ "import type { NodeRegistry } from '@xdelivered/emberflow';\n\n" +
47
+ 'export default defineConfig({\n' +
48
+ " language: 'typescript',\n" +
49
+ ' // Anchor only: this path is never created and holds no files. It just tells\n' +
50
+ ' // Emberflow where your project root is — operations always live in the\n' +
51
+ ' // SIBLING apis/ tree next to it:\n' +
52
+ ' // emberflow/apis/<api>/<folder…>/<op>.json — each op id = its path under apis/.\n' +
53
+ " flowsDir: 'emberflow/flows',\n" +
54
+ ' // Author your nodes here: registry.register(definition, async (ctx) => output).\n' +
55
+ ' registerNodes(registry: NodeRegistry) {\n' +
56
+ ' /* register your nodes */\n' +
57
+ ' },\n' +
58
+ '});\n';
59
+
60
+ // Minimal — the consumer only needs their config + nodes typechecked, not a full
61
+ // project tsconfig. `noEmit` because emberflow.config.ts runs via tsx, never tsc.
62
+ const TSCONFIG_CONTENT =
63
+ JSON.stringify(
64
+ {
65
+ compilerOptions: {
66
+ target: 'es2022',
67
+ module: 'nodenext',
68
+ moduleResolution: 'nodenext',
69
+ strict: true,
70
+ skipLibCheck: true,
71
+ esModuleInterop: true,
72
+ resolveJsonModule: true,
73
+ noEmit: true,
74
+ },
75
+ include: ['emberflow.config.ts', 'emberflow/**/*'],
76
+ },
77
+ null,
78
+ 2
79
+ ) + '\n';
80
+
81
+ /** Whether `tsx` resolves from the project's own node_modules (peer dep — optional).
82
+ * Exported so bin/commands.ts can reuse it when deciding whether `init --ts` can
83
+ * launch dev immediately after scaffolding, or must defer to the user. */
84
+ export function tsxResolvable(cwd: string): boolean {
85
+ try {
86
+ createRequire(join(cwd, 'package.json')).resolve('tsx');
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ // The example HTTP operation: GET /hello, Input({params,query,body,headers}) →
94
+ // Response({status,body}). Its `id` equals its path under the apis dir.
95
+ const HELLO_OP = {
96
+ id: 'default/hello',
97
+ name: 'Hello',
98
+ version: 1,
99
+ http: { method: 'GET', path: '/hello' },
100
+ nodes: [
101
+ {
102
+ id: 'input',
103
+ type: 'Input',
104
+ label: 'Request',
105
+ position: { x: 0, y: 0 },
106
+ config: {
107
+ fields: [
108
+ { name: 'params', type: 'object' },
109
+ { name: 'query', type: 'object' },
110
+ { name: 'body', type: 'object' },
111
+ { name: 'headers', type: 'object' },
112
+ ],
113
+ defaults: { query: { name: 'world' } },
114
+ },
115
+ },
116
+ {
117
+ id: 'response',
118
+ type: 'Response',
119
+ label: 'Response',
120
+ position: { x: 300, y: 0 },
121
+ config: {},
122
+ inputMap: {
123
+ body: { sourceNodeId: 'input', sourceField: 'query' },
124
+ },
125
+ },
126
+ ],
127
+ edges: [{ id: 'e0', source: 'input', target: 'response', targetHandle: 'body' }],
128
+ createdAt: '2026-07-05T00:00:00Z',
129
+ updatedAt: '2026-07-05T00:00:00Z',
130
+ };
131
+
132
+ const HELLO_OP_SCENARIOS = [{ id: 's-hi', name: 'greet', input: { query: { name: 'Ember' } } }];
133
+
134
+ // Emberflow infra that is machine-local, generated, or secret — never committed.
135
+ // NOTE: emberflow.config.mjs and emberflow/ (apis + scenarios) are deliberately
136
+ // NOT here — those are the value and SHOULD be checked in.
137
+ const GITIGNORE_HEADER = '# Emberflow (local infra — emberflow.config.mjs and emberflow/apis ARE committed)';
138
+ const GITIGNORE_LINES = [
139
+ 'node_modules/',
140
+ 'studio-dist/',
141
+ 'emberflow.secrets.json',
142
+ 'emberflow.environments.json',
143
+ ];
144
+
145
+ function writeFileIfAbsent(path: string, content: string, log: string[]): void {
146
+ if (existsSync(path)) return;
147
+ writeFileSync(path, content);
148
+ log.push(path);
149
+ }
150
+
151
+ export async function runInit(
152
+ cwd: string,
153
+ opts?: {
154
+ skills?: false | { scope: 'repo' | 'global'; home: string };
155
+ /** Real package root, passed by the bin. Falls back to this module's own
156
+ * location — which is wrong when compiled to dist/bin/init.js (its `..` is
157
+ * dist/, not the package root that holds templates/), so the bin supplies it. */
158
+ packageRoot?: string;
159
+ /** Which language to scaffold the config/nodes in. Defaults to javascript
160
+ * (matches the non-TTY prompt default in bin/commands.ts). */
161
+ language?: 'javascript' | 'typescript';
162
+ }
163
+ ): Promise<number> {
164
+ const written: string[] = [];
165
+ const language = opts?.language ?? 'javascript';
166
+
167
+ // Refuse to scaffold a SECOND config in another extension: the loader probes
168
+ // .mjs before .js before .ts, so a new config of a different flavor would sit
169
+ // dead on disk while init claims success. Language switching is a manual
170
+ // migration (rename/port the existing config), not a re-init.
171
+ const existingOther = ['emberflow.config.mjs', 'emberflow.config.js', 'emberflow.config.ts']
172
+ .filter((name) => (language === 'typescript' ? !name.endsWith('.ts') : name.endsWith('.ts')))
173
+ .find((name) => existsSync(join(cwd, name)));
174
+ if (existingOther) {
175
+ console.error(
176
+ `[emberflow] this project already has ${existingOther} — refusing to scaffold a ${language} config alongside it. ` +
177
+ `To switch languages, port ${existingOther} manually and delete it.`,
178
+ );
179
+ return 1;
180
+ }
181
+
182
+ if (language === 'typescript') {
183
+ writeFileIfAbsent(join(cwd, 'emberflow.config.ts'), TS_CONFIG_CONTENT, written);
184
+ writeFileIfAbsent(join(cwd, 'tsconfig.json'), TSCONFIG_CONTENT, written);
185
+ if (!tsxResolvable(cwd)) {
186
+ console.log('[emberflow] TypeScript config needs tsx to run this project.');
187
+ console.log(' npm i -D tsx typescript');
188
+ }
189
+ } else {
190
+ writeFileIfAbsent(join(cwd, 'emberflow.config.mjs'), JS_CONFIG_CONTENT, written);
191
+ }
192
+
193
+ // Scaffold the apis/ tree with an example HTTP operation so the API host has a
194
+ // routed endpoint the moment the project boots. The operation's `id`
195
+ // ("default/hello") equals its path under the apis dir.
196
+ const apisDefaultDir = join(cwd, 'emberflow', 'apis', 'default');
197
+ const apisDefaultDirExisted = existsSync(apisDefaultDir);
198
+ mkdirSync(apisDefaultDir, { recursive: true });
199
+ if (!apisDefaultDirExisted) written.push(apisDefaultDir + '/');
200
+
201
+ writeFileIfAbsent(
202
+ join(apisDefaultDir, 'hello.json'),
203
+ JSON.stringify(HELLO_OP, null, 2) + '\n',
204
+ written
205
+ );
206
+ writeFileIfAbsent(
207
+ join(apisDefaultDir, 'hello.scenarios.json'),
208
+ JSON.stringify(HELLO_OP_SCENARIOS, null, 2) + '\n',
209
+ written
210
+ );
211
+
212
+ const pkgPath = join(cwd, 'package.json');
213
+ if (existsSync(pkgPath)) {
214
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {
215
+ scripts?: Record<string, string>;
216
+ type?: string;
217
+ [key: string]: unknown;
218
+ };
219
+ let pkgDirty = false;
220
+ if (!pkg.scripts) pkg.scripts = {};
221
+ if (!pkg.scripts.emberflow) {
222
+ pkg.scripts.emberflow = 'emberflow dev';
223
+ pkgDirty = true;
224
+ }
225
+ // A TypeScript project MUST load as ESM: `emberflow` is an ESM-only package,
226
+ // and under a CommonJS package.json tsx transpiles emberflow.config.ts to CJS
227
+ // and then require()s the ESM `emberflow` — which Node rejects with
228
+ // ERR_REQUIRE_CYCLE_MODULE. Stamp "type":"module" when the project hasn't
229
+ // declared one. (JS scaffolds a .mjs config, which is ESM regardless.)
230
+ if (language === 'typescript') {
231
+ if (pkg.type === undefined) {
232
+ pkg.type = 'module';
233
+ pkgDirty = true;
234
+ console.warn(
235
+ '[emberflow] set "type": "module" in package.json — required for emberflow.config.ts; ' +
236
+ 'if this project has existing CommonJS .js files, audit them (they are now parsed as ESM)',
237
+ );
238
+ } else if (pkg.type !== 'module') {
239
+ console.warn(
240
+ `[emberflow] warning: package.json has "type": "${pkg.type}", but a TypeScript ` +
241
+ 'emberflow.config.ts must load as ESM. Set "type": "module" or the runner will fail ' +
242
+ 'to load your config.',
243
+ );
244
+ }
245
+ }
246
+ if (pkgDirty) {
247
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
248
+ written.push(pkgPath);
249
+ }
250
+ }
251
+
252
+ const gitignorePath = join(cwd, '.gitignore');
253
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
254
+ const missing = GITIGNORE_LINES.filter((line) => !existing.includes(line));
255
+ if (missing.length > 0) {
256
+ // Create .gitignore if absent, or append only the lines not already present.
257
+ const header = existing.includes(GITIGNORE_HEADER) ? '' : `${GITIGNORE_HEADER}\n`;
258
+ const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
259
+ const block = (existing.length > 0 ? '\n' : '') + header + missing.join('\n') + '\n';
260
+ writeFileSync(gitignorePath, existing + sep + block);
261
+ written.push(gitignorePath);
262
+ }
263
+
264
+ for (const path of written) {
265
+ console.log(`[emberflow] created ${path}`);
266
+ }
267
+
268
+ const skillsOpt = opts?.skills === undefined ? { scope: 'repo' as const, home: homedir() } : opts.skills;
269
+ if (skillsOpt !== false) {
270
+ const pkgRoot = opts?.packageRoot ?? join(dirname(fileURLToPath(import.meta.url)), '..');
271
+ const skillsSrc = join(pkgRoot, 'templates', 'skills');
272
+ if (existsSync(skillsSrc)) {
273
+ const presence = detectHarnesses(cwd, skillsOpt.home);
274
+ const skillDirs = resolveSkillDirs(presence, skillsOpt.scope, cwd, skillsOpt.home);
275
+ const skillsWritten: string[] = [];
276
+ for (const dir of skillDirs) {
277
+ copySkillsRecursive(skillsSrc, dir, skillsWritten);
278
+ }
279
+ for (const path of skillsWritten) {
280
+ console.log(`[emberflow] created ${path}`);
281
+ }
282
+ }
283
+ console.log('');
284
+ console.log(
285
+ '[emberflow] To let your agent drive Emberflow directly, register the emberflow MCP server:'
286
+ );
287
+ console.log(
288
+ " - Claude Code: add the snippet below to .mcp.json at your project root."
289
+ );
290
+ console.log(
291
+ " - Other agents: add it to your agent's MCP server config (name/location varies by tool)."
292
+ );
293
+ console.log(JSON.stringify(MCP_SNIPPET, null, 2));
294
+ }
295
+
296
+ return 0;
297
+ }
@@ -0,0 +1,105 @@
1
+ // Runtime-mode decision for the Emberflow bin.
2
+ //
3
+ // The bin must decide — BEFORE loading any TypeScript — whether this invocation
4
+ // needs the tsx loader (source checkout, a `.ts` project config, or an explicit
5
+ // override) or can run on plain Node against the compiled `dist/`. A JS consumer
6
+ // with a `.mjs`/`.js` config runs entirely on `node` with NO tsx: no tsx child
7
+ // IPC, so the agent in-process CLI works inside the codex sandbox.
8
+ //
9
+ // This module is plain JS on purpose — it imports NO server/TS code, so probing
10
+ // it never triggers the tsx requirement it is trying to decide about. Everything
11
+ // here is pure and injectable (fsExists/env) so the decision matrix is unit
12
+ // testable without touching a real filesystem.
13
+
14
+ import { existsSync } from 'node:fs';
15
+ import { isAbsolute, join, resolve, sep } from 'node:path';
16
+
17
+ // Mirrors server/projectConfig.ts CONFIG_BASENAMES. Kept as a tiny local copy
18
+ // (not imported) because that module is TypeScript and pulls in server code.
19
+ const CONFIG_BASENAMES = ['emberflow.config.mjs', 'emberflow.config.js', 'emberflow.config.ts'];
20
+
21
+ /** Actionable message shown when a `.ts` config / source mode needs tsx but it
22
+ * isn't installed. Exported so both the bin and its tests reference one string. */
23
+ export const TSX_MISSING_MESSAGE =
24
+ '[emberflow] This project needs tsx to run, but tsx is not installed.\n' +
25
+ ' Either a TypeScript config (emberflow.config.ts) or source mode requires the tsx loader.\n' +
26
+ ' Fix: install it as a dev dependency — npm i -D tsx\n' +
27
+ ' Or: rename emberflow.config.ts to emberflow.config.mjs (plain JS) so Emberflow runs on plain Node.';
28
+
29
+ /** The emberflow.config.* basename present in `dir`, or undefined. Local probe
30
+ * of the same basenames server/projectConfig.ts#configPathFor checks. */
31
+ export function configBasenameFor(dir, fsExists = existsSync) {
32
+ const root = resolve(dir);
33
+ return CONFIG_BASENAMES.find((b) => fsExists(join(root, b)));
34
+ }
35
+
36
+ /** Resolve the project directory the same way the commands do, in the binding
37
+ * order EMBERFLOW_PROJECT → --project → cwd. Values are resolved against cwd. */
38
+ export function resolveProjectDir(argv, env, cwd) {
39
+ const fromEnv = env.EMBERFLOW_PROJECT;
40
+ if (fromEnv) return isAbsolute(fromEnv) ? fromEnv : resolve(cwd, fromEnv);
41
+ const i = argv.indexOf('--project');
42
+ if (i !== -1 && argv[i + 1]) return resolve(cwd, argv[i + 1]);
43
+ return cwd;
44
+ }
45
+
46
+ /**
47
+ * Decide how to run.
48
+ *
49
+ * @param packageRoot absolute path to the package root (dir above bin/).
50
+ * @param projectDir resolved project dir (see resolveProjectDir).
51
+ * @param env process.env-shaped object.
52
+ * @param fsExists existsSync-shaped probe (injectable for tests).
53
+ *
54
+ * Returns:
55
+ * needsTsx – register tsx and spawn the runner under tsx.
56
+ * runnerMode – 'node' (spawn `node dist/server/*.js`) or 'tsx' (spawn tsx source).
57
+ * useDist – import the compiled `dist/bin/commands.js` rather than source.
58
+ * sourceMode – repo checkout (dev loop) rather than an installed package.
59
+ * configBasename / reason – for the EMBERFLOW_DEBUG_RUNTIME trace.
60
+ */
61
+ export function decideRuntime({ packageRoot, projectDir, env = {}, fsExists = existsSync }) {
62
+ const hasDist = fsExists(join(packageRoot, 'dist'));
63
+ const hasSrc = fsExists(join(packageRoot, 'src'));
64
+ const underNodeModules = packageRoot.split(sep).includes('node_modules');
65
+
66
+ // source-mode = repo checkout, where we always run the TypeScript sources under
67
+ // tsx (the dev loop). `dist/` may EXIST in the repo after `build:lib`, so its
68
+ // presence can't be the signal. Heuristic: src/ exists AND we're not installed
69
+ // under node_modules. EMBERFLOW_SOURCE is an explicit escape hatch either way.
70
+ let sourceMode;
71
+ if (env.EMBERFLOW_SOURCE === '1') sourceMode = true;
72
+ else if (env.EMBERFLOW_SOURCE === '0') sourceMode = false;
73
+ else sourceMode = hasSrc && !underNodeModules;
74
+
75
+ const configBasename = configBasenameFor(projectDir, fsExists);
76
+ const tsConfig = configBasename?.endsWith('.ts') ?? false;
77
+ const forced = env.EMBERFLOW_FORCE_TSX === '1';
78
+
79
+ // Import compiled commands only when we have a dist AND we're not in the source
80
+ // dev loop. Otherwise fall back to the TypeScript source (which requires tsx —
81
+ // and needsTsx is already true whenever sourceMode is).
82
+ const useDist = hasDist && !sourceMode;
83
+
84
+ // INVARIANT: importing TypeScript source requires tsx. When there is no dist
85
+ // to fall back on (`!useDist`), the launcher will import ./commands.ts, so tsx
86
+ // is required even if nothing else demanded it — otherwise plain node dies
87
+ // with a cryptic ERR_UNKNOWN_FILE_EXTENSION instead of TSX_MISSING_MESSAGE.
88
+ const needsTsx = sourceMode || forced || tsConfig || !useDist;
89
+
90
+ // A node-mode runner needs the compiled server; if there's no dist we can't run
91
+ // plain node, so fall back to tsx even when nothing else demanded it.
92
+ const runnerMode = !needsTsx && hasDist ? 'node' : 'tsx';
93
+
94
+ const reason = sourceMode
95
+ ? 'source-mode (repo checkout)'
96
+ : forced
97
+ ? 'EMBERFLOW_FORCE_TSX=1'
98
+ : tsConfig
99
+ ? `ts-config (${configBasename})`
100
+ : runnerMode === 'node'
101
+ ? 'js-consumer (plain node)'
102
+ : 'no-dist fallback';
103
+
104
+ return { needsTsx, runnerMode, useDist, sourceMode, hasDist, configBasename, reason };
105
+ }