@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,241 @@
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.js';
7
+ const MCP_SNIPPET = {
8
+ mcpServers: {
9
+ emberflow: { command: 'npx', args: ['emberflow', 'mcp'] },
10
+ },
11
+ };
12
+ function copySkillsRecursive(src, dest, log) {
13
+ mkdirSync(dest, { recursive: true });
14
+ for (const entry of readdirSync(src)) {
15
+ const srcPath = join(src, entry);
16
+ const destPath = join(dest, entry);
17
+ if (statSync(srcPath).isDirectory()) {
18
+ copySkillsRecursive(srcPath, destPath, log);
19
+ }
20
+ else if (!existsSync(destPath)) {
21
+ copyFileSync(srcPath, destPath);
22
+ log.push(destPath);
23
+ }
24
+ }
25
+ }
26
+ const JS_CONFIG_CONTENT = "import { defineConfig } from '@xdelivered/emberflow';\n\n" +
27
+ 'export default defineConfig({\n' +
28
+ " language: 'javascript',\n" +
29
+ ' // Anchor only: this path is never created and holds no files. It just tells\n' +
30
+ ' // Emberflow where your project root is — operations always live in the\n' +
31
+ ' // SIBLING apis/ tree next to it:\n' +
32
+ ' // emberflow/apis/<api>/<folder…>/<op>.json — each op id = its path under apis/.\n' +
33
+ " flowsDir: 'emberflow/flows',\n" +
34
+ ' // Author your nodes here: registry.register(definition, async (ctx) => output).\n' +
35
+ " /** @param {import('@xdelivered/emberflow').NodeRegistry} registry */\n" +
36
+ ' registerNodes(registry) {\n' +
37
+ ' /* register your nodes */\n' +
38
+ ' },\n' +
39
+ '});\n';
40
+ const TS_CONFIG_CONTENT = "import { defineConfig } from '@xdelivered/emberflow';\n" +
41
+ "import type { NodeRegistry } from '@xdelivered/emberflow';\n\n" +
42
+ 'export default defineConfig({\n' +
43
+ " language: 'typescript',\n" +
44
+ ' // Anchor only: this path is never created and holds no files. It just tells\n' +
45
+ ' // Emberflow where your project root is — operations always live in the\n' +
46
+ ' // SIBLING apis/ tree next to it:\n' +
47
+ ' // emberflow/apis/<api>/<folder…>/<op>.json — each op id = its path under apis/.\n' +
48
+ " flowsDir: 'emberflow/flows',\n" +
49
+ ' // Author your nodes here: registry.register(definition, async (ctx) => output).\n' +
50
+ ' registerNodes(registry: NodeRegistry) {\n' +
51
+ ' /* register your nodes */\n' +
52
+ ' },\n' +
53
+ '});\n';
54
+ // Minimal — the consumer only needs their config + nodes typechecked, not a full
55
+ // project tsconfig. `noEmit` because emberflow.config.ts runs via tsx, never tsc.
56
+ const TSCONFIG_CONTENT = JSON.stringify({
57
+ compilerOptions: {
58
+ target: 'es2022',
59
+ module: 'nodenext',
60
+ moduleResolution: 'nodenext',
61
+ strict: true,
62
+ skipLibCheck: true,
63
+ esModuleInterop: true,
64
+ resolveJsonModule: true,
65
+ noEmit: true,
66
+ },
67
+ include: ['emberflow.config.ts', 'emberflow/**/*'],
68
+ }, null, 2) + '\n';
69
+ /** Whether `tsx` resolves from the project's own node_modules (peer dep — optional).
70
+ * Exported so bin/commands.ts can reuse it when deciding whether `init --ts` can
71
+ * launch dev immediately after scaffolding, or must defer to the user. */
72
+ export function tsxResolvable(cwd) {
73
+ try {
74
+ createRequire(join(cwd, 'package.json')).resolve('tsx');
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ // The example HTTP operation: GET /hello, Input({params,query,body,headers}) →
82
+ // Response({status,body}). Its `id` equals its path under the apis dir.
83
+ const HELLO_OP = {
84
+ id: 'default/hello',
85
+ name: 'Hello',
86
+ version: 1,
87
+ http: { method: 'GET', path: '/hello' },
88
+ nodes: [
89
+ {
90
+ id: 'input',
91
+ type: 'Input',
92
+ label: 'Request',
93
+ position: { x: 0, y: 0 },
94
+ config: {
95
+ fields: [
96
+ { name: 'params', type: 'object' },
97
+ { name: 'query', type: 'object' },
98
+ { name: 'body', type: 'object' },
99
+ { name: 'headers', type: 'object' },
100
+ ],
101
+ defaults: { query: { name: 'world' } },
102
+ },
103
+ },
104
+ {
105
+ id: 'response',
106
+ type: 'Response',
107
+ label: 'Response',
108
+ position: { x: 300, y: 0 },
109
+ config: {},
110
+ inputMap: {
111
+ body: { sourceNodeId: 'input', sourceField: 'query' },
112
+ },
113
+ },
114
+ ],
115
+ edges: [{ id: 'e0', source: 'input', target: 'response', targetHandle: 'body' }],
116
+ createdAt: '2026-07-05T00:00:00Z',
117
+ updatedAt: '2026-07-05T00:00:00Z',
118
+ };
119
+ const HELLO_OP_SCENARIOS = [{ id: 's-hi', name: 'greet', input: { query: { name: 'Ember' } } }];
120
+ // Emberflow infra that is machine-local, generated, or secret — never committed.
121
+ // NOTE: emberflow.config.mjs and emberflow/ (apis + scenarios) are deliberately
122
+ // NOT here — those are the value and SHOULD be checked in.
123
+ const GITIGNORE_HEADER = '# Emberflow (local infra — emberflow.config.mjs and emberflow/apis ARE committed)';
124
+ const GITIGNORE_LINES = [
125
+ 'node_modules/',
126
+ 'studio-dist/',
127
+ 'emberflow.secrets.json',
128
+ 'emberflow.environments.json',
129
+ ];
130
+ function writeFileIfAbsent(path, content, log) {
131
+ if (existsSync(path))
132
+ return;
133
+ writeFileSync(path, content);
134
+ log.push(path);
135
+ }
136
+ export async function runInit(cwd, opts) {
137
+ const written = [];
138
+ const language = opts?.language ?? 'javascript';
139
+ // Refuse to scaffold a SECOND config in another extension: the loader probes
140
+ // .mjs before .js before .ts, so a new config of a different flavor would sit
141
+ // dead on disk while init claims success. Language switching is a manual
142
+ // migration (rename/port the existing config), not a re-init.
143
+ const existingOther = ['emberflow.config.mjs', 'emberflow.config.js', 'emberflow.config.ts']
144
+ .filter((name) => (language === 'typescript' ? !name.endsWith('.ts') : name.endsWith('.ts')))
145
+ .find((name) => existsSync(join(cwd, name)));
146
+ if (existingOther) {
147
+ console.error(`[emberflow] this project already has ${existingOther} — refusing to scaffold a ${language} config alongside it. ` +
148
+ `To switch languages, port ${existingOther} manually and delete it.`);
149
+ return 1;
150
+ }
151
+ if (language === 'typescript') {
152
+ writeFileIfAbsent(join(cwd, 'emberflow.config.ts'), TS_CONFIG_CONTENT, written);
153
+ writeFileIfAbsent(join(cwd, 'tsconfig.json'), TSCONFIG_CONTENT, written);
154
+ if (!tsxResolvable(cwd)) {
155
+ console.log('[emberflow] TypeScript config needs tsx to run this project.');
156
+ console.log(' npm i -D tsx typescript');
157
+ }
158
+ }
159
+ else {
160
+ writeFileIfAbsent(join(cwd, 'emberflow.config.mjs'), JS_CONFIG_CONTENT, written);
161
+ }
162
+ // Scaffold the apis/ tree with an example HTTP operation so the API host has a
163
+ // routed endpoint the moment the project boots. The operation's `id`
164
+ // ("default/hello") equals its path under the apis dir.
165
+ const apisDefaultDir = join(cwd, 'emberflow', 'apis', 'default');
166
+ const apisDefaultDirExisted = existsSync(apisDefaultDir);
167
+ mkdirSync(apisDefaultDir, { recursive: true });
168
+ if (!apisDefaultDirExisted)
169
+ written.push(apisDefaultDir + '/');
170
+ writeFileIfAbsent(join(apisDefaultDir, 'hello.json'), JSON.stringify(HELLO_OP, null, 2) + '\n', written);
171
+ writeFileIfAbsent(join(apisDefaultDir, 'hello.scenarios.json'), JSON.stringify(HELLO_OP_SCENARIOS, null, 2) + '\n', written);
172
+ const pkgPath = join(cwd, 'package.json');
173
+ if (existsSync(pkgPath)) {
174
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
175
+ let pkgDirty = false;
176
+ if (!pkg.scripts)
177
+ pkg.scripts = {};
178
+ if (!pkg.scripts.emberflow) {
179
+ pkg.scripts.emberflow = 'emberflow dev';
180
+ pkgDirty = true;
181
+ }
182
+ // A TypeScript project MUST load as ESM: `emberflow` is an ESM-only package,
183
+ // and under a CommonJS package.json tsx transpiles emberflow.config.ts to CJS
184
+ // and then require()s the ESM `emberflow` — which Node rejects with
185
+ // ERR_REQUIRE_CYCLE_MODULE. Stamp "type":"module" when the project hasn't
186
+ // declared one. (JS scaffolds a .mjs config, which is ESM regardless.)
187
+ if (language === 'typescript') {
188
+ if (pkg.type === undefined) {
189
+ pkg.type = 'module';
190
+ pkgDirty = true;
191
+ console.warn('[emberflow] set "type": "module" in package.json — required for emberflow.config.ts; ' +
192
+ 'if this project has existing CommonJS .js files, audit them (they are now parsed as ESM)');
193
+ }
194
+ else if (pkg.type !== 'module') {
195
+ console.warn(`[emberflow] warning: package.json has "type": "${pkg.type}", but a TypeScript ` +
196
+ 'emberflow.config.ts must load as ESM. Set "type": "module" or the runner will fail ' +
197
+ 'to load your config.');
198
+ }
199
+ }
200
+ if (pkgDirty) {
201
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
202
+ written.push(pkgPath);
203
+ }
204
+ }
205
+ const gitignorePath = join(cwd, '.gitignore');
206
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
207
+ const missing = GITIGNORE_LINES.filter((line) => !existing.includes(line));
208
+ if (missing.length > 0) {
209
+ // Create .gitignore if absent, or append only the lines not already present.
210
+ const header = existing.includes(GITIGNORE_HEADER) ? '' : `${GITIGNORE_HEADER}\n`;
211
+ const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
212
+ const block = (existing.length > 0 ? '\n' : '') + header + missing.join('\n') + '\n';
213
+ writeFileSync(gitignorePath, existing + sep + block);
214
+ written.push(gitignorePath);
215
+ }
216
+ for (const path of written) {
217
+ console.log(`[emberflow] created ${path}`);
218
+ }
219
+ const skillsOpt = opts?.skills === undefined ? { scope: 'repo', home: homedir() } : opts.skills;
220
+ if (skillsOpt !== false) {
221
+ const pkgRoot = opts?.packageRoot ?? join(dirname(fileURLToPath(import.meta.url)), '..');
222
+ const skillsSrc = join(pkgRoot, 'templates', 'skills');
223
+ if (existsSync(skillsSrc)) {
224
+ const presence = detectHarnesses(cwd, skillsOpt.home);
225
+ const skillDirs = resolveSkillDirs(presence, skillsOpt.scope, cwd, skillsOpt.home);
226
+ const skillsWritten = [];
227
+ for (const dir of skillDirs) {
228
+ copySkillsRecursive(skillsSrc, dir, skillsWritten);
229
+ }
230
+ for (const path of skillsWritten) {
231
+ console.log(`[emberflow] created ${path}`);
232
+ }
233
+ }
234
+ console.log('');
235
+ console.log('[emberflow] To let your agent drive Emberflow directly, register the emberflow MCP server:');
236
+ console.log(" - Claude Code: add the snippet below to .mcp.json at your project root.");
237
+ console.log(" - Other agents: add it to your agent's MCP server config (name/location varies by tool).");
238
+ console.log(JSON.stringify(MCP_SNIPPET, null, 2));
239
+ }
240
+ return 0;
241
+ }
@@ -0,0 +1,39 @@
1
+ /** The emberflow.config.* basename present in `dir`, or undefined. Local probe
2
+ * of the same basenames server/projectConfig.ts#configPathFor checks. */
3
+ export function configBasenameFor(dir: any, fsExists?: typeof existsSync): string | undefined;
4
+ /** Resolve the project directory the same way the commands do, in the binding
5
+ * order EMBERFLOW_PROJECT → --project → cwd. Values are resolved against cwd. */
6
+ export function resolveProjectDir(argv: any, env: any, cwd: any): any;
7
+ /**
8
+ * Decide how to run.
9
+ *
10
+ * @param packageRoot absolute path to the package root (dir above bin/).
11
+ * @param projectDir resolved project dir (see resolveProjectDir).
12
+ * @param env process.env-shaped object.
13
+ * @param fsExists existsSync-shaped probe (injectable for tests).
14
+ *
15
+ * Returns:
16
+ * needsTsx – register tsx and spawn the runner under tsx.
17
+ * runnerMode – 'node' (spawn `node dist/server/*.js`) or 'tsx' (spawn tsx source).
18
+ * useDist – import the compiled `dist/bin/commands.js` rather than source.
19
+ * sourceMode – repo checkout (dev loop) rather than an installed package.
20
+ * configBasename / reason – for the EMBERFLOW_DEBUG_RUNTIME trace.
21
+ */
22
+ export function decideRuntime({ packageRoot, projectDir, env, fsExists }: {
23
+ packageRoot: any;
24
+ projectDir: any;
25
+ env?: {} | undefined;
26
+ fsExists?: typeof existsSync | undefined;
27
+ }): {
28
+ needsTsx: boolean;
29
+ runnerMode: string;
30
+ useDist: boolean;
31
+ sourceMode: boolean;
32
+ hasDist: boolean;
33
+ configBasename: string | undefined;
34
+ reason: string;
35
+ };
36
+ /** Actionable message shown when a `.ts` config / source mode needs tsx but it
37
+ * isn't installed. Exported so both the bin and its tests reference one string. */
38
+ export const TSX_MISSING_MESSAGE: string;
39
+ import { existsSync } from 'node:fs';
@@ -0,0 +1,96 @@
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
+ import { existsSync } from 'node:fs';
14
+ import { isAbsolute, join, resolve, sep } from 'node:path';
15
+ // Mirrors server/projectConfig.ts CONFIG_BASENAMES. Kept as a tiny local copy
16
+ // (not imported) because that module is TypeScript and pulls in server code.
17
+ const CONFIG_BASENAMES = ['emberflow.config.mjs', 'emberflow.config.js', 'emberflow.config.ts'];
18
+ /** Actionable message shown when a `.ts` config / source mode needs tsx but it
19
+ * isn't installed. Exported so both the bin and its tests reference one string. */
20
+ export const TSX_MISSING_MESSAGE = '[emberflow] This project needs tsx to run, but tsx is not installed.\n' +
21
+ ' Either a TypeScript config (emberflow.config.ts) or source mode requires the tsx loader.\n' +
22
+ ' Fix: install it as a dev dependency — npm i -D tsx\n' +
23
+ ' Or: rename emberflow.config.ts to emberflow.config.mjs (plain JS) so Emberflow runs on plain Node.';
24
+ /** The emberflow.config.* basename present in `dir`, or undefined. Local probe
25
+ * of the same basenames server/projectConfig.ts#configPathFor checks. */
26
+ export function configBasenameFor(dir, fsExists = existsSync) {
27
+ const root = resolve(dir);
28
+ return CONFIG_BASENAMES.find((b) => fsExists(join(root, b)));
29
+ }
30
+ /** Resolve the project directory the same way the commands do, in the binding
31
+ * order EMBERFLOW_PROJECT → --project → cwd. Values are resolved against cwd. */
32
+ export function resolveProjectDir(argv, env, cwd) {
33
+ const fromEnv = env.EMBERFLOW_PROJECT;
34
+ if (fromEnv)
35
+ return isAbsolute(fromEnv) ? fromEnv : resolve(cwd, fromEnv);
36
+ const i = argv.indexOf('--project');
37
+ if (i !== -1 && argv[i + 1])
38
+ return resolve(cwd, argv[i + 1]);
39
+ return cwd;
40
+ }
41
+ /**
42
+ * Decide how to run.
43
+ *
44
+ * @param packageRoot absolute path to the package root (dir above bin/).
45
+ * @param projectDir resolved project dir (see resolveProjectDir).
46
+ * @param env process.env-shaped object.
47
+ * @param fsExists existsSync-shaped probe (injectable for tests).
48
+ *
49
+ * Returns:
50
+ * needsTsx – register tsx and spawn the runner under tsx.
51
+ * runnerMode – 'node' (spawn `node dist/server/*.js`) or 'tsx' (spawn tsx source).
52
+ * useDist – import the compiled `dist/bin/commands.js` rather than source.
53
+ * sourceMode – repo checkout (dev loop) rather than an installed package.
54
+ * configBasename / reason – for the EMBERFLOW_DEBUG_RUNTIME trace.
55
+ */
56
+ export function decideRuntime({ packageRoot, projectDir, env = {}, fsExists = existsSync }) {
57
+ const hasDist = fsExists(join(packageRoot, 'dist'));
58
+ const hasSrc = fsExists(join(packageRoot, 'src'));
59
+ const underNodeModules = packageRoot.split(sep).includes('node_modules');
60
+ // source-mode = repo checkout, where we always run the TypeScript sources under
61
+ // tsx (the dev loop). `dist/` may EXIST in the repo after `build:lib`, so its
62
+ // presence can't be the signal. Heuristic: src/ exists AND we're not installed
63
+ // under node_modules. EMBERFLOW_SOURCE is an explicit escape hatch either way.
64
+ let sourceMode;
65
+ if (env.EMBERFLOW_SOURCE === '1')
66
+ sourceMode = true;
67
+ else if (env.EMBERFLOW_SOURCE === '0')
68
+ sourceMode = false;
69
+ else
70
+ sourceMode = hasSrc && !underNodeModules;
71
+ const configBasename = configBasenameFor(projectDir, fsExists);
72
+ const tsConfig = configBasename?.endsWith('.ts') ?? false;
73
+ const forced = env.EMBERFLOW_FORCE_TSX === '1';
74
+ // Import compiled commands only when we have a dist AND we're not in the source
75
+ // dev loop. Otherwise fall back to the TypeScript source (which requires tsx —
76
+ // and needsTsx is already true whenever sourceMode is).
77
+ const useDist = hasDist && !sourceMode;
78
+ // INVARIANT: importing TypeScript source requires tsx. When there is no dist
79
+ // to fall back on (`!useDist`), the launcher will import ./commands.ts, so tsx
80
+ // is required even if nothing else demanded it — otherwise plain node dies
81
+ // with a cryptic ERR_UNKNOWN_FILE_EXTENSION instead of TSX_MISSING_MESSAGE.
82
+ const needsTsx = sourceMode || forced || tsConfig || !useDist;
83
+ // A node-mode runner needs the compiled server; if there's no dist we can't run
84
+ // plain node, so fall back to tsx even when nothing else demanded it.
85
+ const runnerMode = !needsTsx && hasDist ? 'node' : 'tsx';
86
+ const reason = sourceMode
87
+ ? 'source-mode (repo checkout)'
88
+ : forced
89
+ ? 'EMBERFLOW_FORCE_TSX=1'
90
+ : tsConfig
91
+ ? `ts-config (${configBasename})`
92
+ : runnerMode === 'node'
93
+ ? 'js-consumer (plain node)'
94
+ : 'no-dist fallback';
95
+ return { needsTsx, runnerMode, useDist, sourceMode, hasDist, configBasename, reason };
96
+ }
@@ -0,0 +1,13 @@
1
+ export type Harness = 'claude' | 'codex';
2
+ export interface HarnessPresence {
3
+ claude: boolean;
4
+ codex: boolean;
5
+ }
6
+ /** Detect which harnesses the project/user uses: a harness counts as present if
7
+ * its repo dir (<cwd>/.claude or /.codex) OR its home dir (<home>/.claude or /.codex) exists. */
8
+ export declare function detectHarnesses(cwd: string, home: string): HarnessPresence;
9
+ /** The skills destination dirs to install into. For each PRESENT harness, choose
10
+ * the repo root (<cwd>/.<harness>/skills) when scope='repo', else the home root
11
+ * (<home>/.<harness>/skills). If NEITHER harness is present, default to Claude Code
12
+ * at the chosen scope (so a fresh project still gets skills). Returns absolute dirs. */
13
+ export declare function resolveSkillDirs(presence: HarnessPresence, scope: 'repo' | 'global', cwd: string, home: string): string[];
@@ -0,0 +1,22 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const HARNESSES = ['claude', 'codex'];
4
+ /** Detect which harnesses the project/user uses: a harness counts as present if
5
+ * its repo dir (<cwd>/.claude or /.codex) OR its home dir (<home>/.claude or /.codex) exists. */
6
+ export function detectHarnesses(cwd, home) {
7
+ const presence = {};
8
+ for (const h of HARNESSES) {
9
+ presence[h] = existsSync(join(cwd, `.${h}`)) || existsSync(join(home, `.${h}`));
10
+ }
11
+ return presence;
12
+ }
13
+ /** The skills destination dirs to install into. For each PRESENT harness, choose
14
+ * the repo root (<cwd>/.<harness>/skills) when scope='repo', else the home root
15
+ * (<home>/.<harness>/skills). If NEITHER harness is present, default to Claude Code
16
+ * at the chosen scope (so a fresh project still gets skills). Returns absolute dirs. */
17
+ export function resolveSkillDirs(presence, scope, cwd, home) {
18
+ const root = scope === 'repo' ? cwd : home;
19
+ const present = HARNESSES.filter((h) => presence[h]);
20
+ const targets = present.length > 0 ? present : ['claude'];
21
+ return targets.map((h) => join(root, `.${h}`, 'skills'));
22
+ }
@@ -0,0 +1,8 @@
1
+ import type { SpawnedAgent } from './codexAdapter.js';
2
+ export interface ClaudeOptions {
3
+ model?: string;
4
+ bin?: string;
5
+ /** Grace period (ms) between SIGTERM and the SIGKILL escalation in cancel(). Test hook. */
6
+ killGraceMs?: number;
7
+ }
8
+ export declare function spawnClaude(prompt: string, projectDir: string, opts?: ClaudeOptions): SpawnedAgent;
@@ -0,0 +1,190 @@
1
+ import { spawn } from 'node:child_process';
2
+ import readline from 'node:readline';
3
+ import { parseClaudeLine } from './claudeParse.js';
4
+ import { modelRejectionHint } from './modelRejectionHint.js';
5
+ const STDERR_TAIL_BYTES = 2048;
6
+ export function spawnClaude(prompt, projectDir, opts = {}) {
7
+ const bin = opts.bin ?? 'claude';
8
+ const args = [
9
+ '-p',
10
+ prompt,
11
+ '--output-format',
12
+ 'stream-json',
13
+ '--verbose',
14
+ // Never ask for per-call approval — the studio runs claude non-interactively
15
+ // (-p + SSE panel), so an approval prompt has no one to answer it and the
16
+ // command simply fails ("This command requires approval"), which broke even
17
+ // our own CLI calls (list-environments). Mirrors the codex adapter's
18
+ // never-ask contract; the --add-dir working-directory sandbox still fences
19
+ // file reads/edits to the project.
20
+ '--permission-mode',
21
+ 'bypassPermissions',
22
+ '--add-dir',
23
+ projectDir,
24
+ ...(opts.model ? ['--model', opts.model] : []),
25
+ ];
26
+ // detached: give claude its own process group so cancel() can tree-kill any
27
+ // subprocesses it spawns, not just the top-level process.
28
+ // stdio ['ignore','pipe','pipe'] → stdin is null, stdout/stderr are Readable.
29
+ const child = bin.endsWith('.mjs')
30
+ ? spawn(process.execPath, [bin, ...args], { cwd: projectDir, stdio: ['ignore', 'pipe', 'pipe'], detached: true })
31
+ : spawn(bin, args, { cwd: projectDir, stdio: ['ignore', 'pipe', 'pipe'], detached: true });
32
+ // Buffer only the last ~2KB of stderr — non-fatal in general, but useful as
33
+ // a diagnostic tail when the process dies without a terminal event (see
34
+ // maybeFinalize below).
35
+ let stderrTail = '';
36
+ child.stderr.on('data', (chunk) => {
37
+ stderrTail = (stderrTail + chunk.toString('utf8')).slice(-STDERR_TAIL_BYTES);
38
+ });
39
+ const queue = [];
40
+ const resolvers = [];
41
+ let done = false;
42
+ let sawTerminal = false;
43
+ function push(event) {
44
+ queue.push(event);
45
+ const resolve = resolvers.shift();
46
+ if (resolve)
47
+ resolve();
48
+ }
49
+ function finish() {
50
+ done = true;
51
+ while (resolvers.length) {
52
+ const resolve = resolvers.shift();
53
+ if (resolve)
54
+ resolve();
55
+ }
56
+ }
57
+ // Tracks in-flight Bash commands by their tool_use id, so that when the
58
+ // matching tool_result (a separate 'user' stream-json line) arrives we can
59
+ // recover the original command text — the tool_result line only carries the
60
+ // id + output, not the command. AgentConsole dedups an in_progress command
61
+ // event with a later completed one by matching `command` text, so the
62
+ // completed event we emit here must reuse the same text.
63
+ const pendingCommands = new Map();
64
+ const rl = readline.createInterface({ input: child.stdout });
65
+ rl.on('line', (line) => {
66
+ for (const event of parseClaudeLine(line)) {
67
+ if (event.type === 'done' || event.type === 'error')
68
+ sawTerminal = true;
69
+ if (event.type === 'command' && event.commandStatus === 'in_progress' && event.toolUseId && event.command) {
70
+ pendingCommands.set(event.toolUseId, event.command);
71
+ push(event);
72
+ continue;
73
+ }
74
+ if (event.type === 'command' && event.commandStatus !== 'in_progress' && event.toolUseId) {
75
+ // A tool_result — only surface it if it corresponds to a Bash command
76
+ // we're tracking; skip gracefully otherwise (e.g. a non-Bash tool).
77
+ const command = pendingCommands.get(event.toolUseId);
78
+ if (!command)
79
+ continue;
80
+ pendingCommands.delete(event.toolUseId);
81
+ push({ ...event, command });
82
+ continue;
83
+ }
84
+ push(event);
85
+ }
86
+ });
87
+ // Finalize only once BOTH the stdout stream is fully drained (readline 'close')
88
+ // AND the process has exited ('close', which fires after all stdio is flushed —
89
+ // unlike 'exit', which can fire while trailing stdout lines are still buffered).
90
+ let rlClosed = false;
91
+ let procClosed = false;
92
+ let exitCode = null;
93
+ let escalateTimer;
94
+ function clearEscalation() {
95
+ if (escalateTimer) {
96
+ clearTimeout(escalateTimer);
97
+ escalateTimer = undefined;
98
+ }
99
+ }
100
+ function maybeFinalize() {
101
+ if (!rlClosed || !procClosed)
102
+ return;
103
+ clearEscalation();
104
+ if (!sawTerminal) {
105
+ if (exitCode === 0) {
106
+ // Clean exit but the model never emitted a result event — synthesize a done.
107
+ push({ type: 'done' });
108
+ }
109
+ else {
110
+ const hint = modelRejectionHint('claude', stderrTail);
111
+ const text = hint ? `claude exited with code ${exitCode} (${hint})` : `claude exited with code ${exitCode}`;
112
+ push({ type: 'error', text });
113
+ }
114
+ sawTerminal = true;
115
+ }
116
+ finish();
117
+ }
118
+ rl.on('close', () => {
119
+ rlClosed = true;
120
+ maybeFinalize();
121
+ });
122
+ child.on('close', (code) => {
123
+ procClosed = true;
124
+ exitCode = code;
125
+ maybeFinalize();
126
+ });
127
+ // A failed spawn (missing binary, bad EMBERFLOW_*_BIN, permissions, ...) emits
128
+ // an 'error' event on the child instead of ever exiting/closing normally. Left
129
+ // unhandled, Node rethrows it and crashes the whole runner process — so we
130
+ // must handle it and finalize the stream ourselves. 'close' still fires after
131
+ // a spawn error (with code null), so guard against double-finalizing there.
132
+ let spawnFailed = false;
133
+ child.on('error', (err) => {
134
+ if (spawnFailed)
135
+ return;
136
+ spawnFailed = true;
137
+ clearEscalation();
138
+ push({ type: 'error', text: `failed to start claude: ${err.message}` });
139
+ sawTerminal = true;
140
+ rlClosed = true;
141
+ procClosed = true;
142
+ finish();
143
+ });
144
+ async function* generate() {
145
+ while (true) {
146
+ if (queue.length > 0) {
147
+ yield queue.shift();
148
+ continue;
149
+ }
150
+ if (done)
151
+ return;
152
+ await new Promise((resolve) => resolvers.push(resolve));
153
+ }
154
+ }
155
+ // Kill the whole process group (negative pid) so claude's own subprocesses die
156
+ // too; fall back to a plain kill if the group signal fails (e.g. already gone
157
+ // or no group). Used by both the initial SIGTERM and the SIGKILL escalation.
158
+ function killGroup(signal) {
159
+ try {
160
+ if (child.pid != null) {
161
+ process.kill(-child.pid, signal);
162
+ return;
163
+ }
164
+ }
165
+ catch {
166
+ // group kill failed — fall through to a direct kill
167
+ }
168
+ try {
169
+ child.kill(signal);
170
+ }
171
+ catch {
172
+ // process already gone — nothing to do
173
+ }
174
+ }
175
+ return {
176
+ events: { [Symbol.asyncIterator]: generate },
177
+ cancel() {
178
+ killGroup('SIGTERM');
179
+ // If the process (or a stubborn grandchild/MCP server) ignores SIGTERM,
180
+ // escalate to SIGKILL after a grace period so a hung agent can't
181
+ // permanently lock the run slot. Cleared once close/error finalizes.
182
+ clearEscalation();
183
+ escalateTimer = setTimeout(() => {
184
+ escalateTimer = undefined;
185
+ killGroup('SIGKILL');
186
+ }, opts.killGraceMs ?? 3000);
187
+ escalateTimer.unref?.();
188
+ },
189
+ };
190
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentEvent } from './types.js';
2
+ export declare function parseClaudeLine(line: string): AgentEvent[];