@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,229 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { ArrowLeftIcon, GlobeIcon, RotateCcwIcon, ServerIcon, SettingsIcon } from 'lucide-react';
3
+ import { cn } from '@/lib/utils';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogTitle,
9
+ DialogTrigger,
10
+ } from '@/components/ui/dialog';
11
+ import { useBuilderStore } from '../store/builderStore';
12
+ import { fetchAvailableAgents } from '../store/agentClient';
13
+ import type { AgentKind, DetectedAgent } from '../store/agentClient';
14
+
15
+ const AGENT_LABELS: Record<AgentKind, string> = {
16
+ codex: 'Codex',
17
+ claude: 'Claude',
18
+ };
19
+
20
+ const ENGINE_OPTIONS: Array<{
21
+ value: 'auto' | 'server' | 'browser';
22
+ label: string;
23
+ hint: string;
24
+ }> = [
25
+ { value: 'auto', label: 'Auto', hint: 'runner when available (recommended)' },
26
+ { value: 'server', label: 'Server', hint: 'always the local runner' },
27
+ { value: 'browser', label: 'Browser', hint: 'in-tab engine, no secrets/CORS-free APIs' },
28
+ ];
29
+
30
+ function SectionTitle({ children }: { children: React.ReactNode }) {
31
+ return (
32
+ <div className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
33
+ {children}
34
+ </div>
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Settings dialog behind the toolbar gear. Absorbs the old EngineChip (execution
40
+ * engine + runner status) and the Reset button, keeping the toolbar itself to
41
+ * authoring + running.
42
+ */
43
+ export function SettingsDialog() {
44
+ const mode = useBuilderStore((s) => s.executionMode);
45
+ const setMode = useBuilderStore((s) => s.setExecutionMode);
46
+ const runnerOnline = useBuilderStore((s) => s.runnerOnline);
47
+ const resetRun = useBuilderStore((s) => s.resetRun);
48
+ const agentChoice = useBuilderStore((s) => s.agentChoice);
49
+ const setAgentChoice = useBuilderStore((s) => s.setAgentChoice);
50
+ // Open state lives in the store so the Welcome checklist can deep-link here.
51
+ const open = useBuilderStore((s) => s.settingsOpen);
52
+ const setOpen = useBuilderStore((s) => s.setSettingsOpen);
53
+ const fromWelcome = useBuilderStore((s) => s.settingsFromWelcome);
54
+ const setWelcomeOpen = useBuilderStore((s) => s.setWelcomeOpen);
55
+ const [availableAgents, setAvailableAgents] = useState<DetectedAgent[]>([]);
56
+
57
+ useEffect(() => {
58
+ if (!open) return;
59
+ let cancelled = false;
60
+ void fetchAvailableAgents().then((agents) => {
61
+ if (!cancelled) setAvailableAgents(agents);
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, [open]);
67
+
68
+ const effective = mode === 'auto' ? (runnerOnline ? 'server' : 'browser') : mode;
69
+ const offline = effective === 'server' && runnerOnline === false;
70
+
71
+ return (
72
+ <Dialog open={open} onOpenChange={setOpen}>
73
+ <DialogTrigger asChild>
74
+ <Button
75
+ variant="ghost"
76
+ size="icon"
77
+ aria-label="Settings"
78
+ className="text-muted-foreground hover:text-foreground"
79
+ >
80
+ <SettingsIcon />
81
+ </Button>
82
+ </DialogTrigger>
83
+ <DialogContent className="max-w-md">
84
+ <div className="flex items-center gap-2">
85
+ {fromWelcome && (
86
+ <Button
87
+ variant="ghost"
88
+ size="icon"
89
+ onClick={() => {
90
+ setOpen(false);
91
+ setWelcomeOpen(true);
92
+ }}
93
+ aria-label="Back to setup checklist"
94
+ className="-ml-1.5 size-6 text-muted-foreground hover:text-foreground"
95
+ >
96
+ <ArrowLeftIcon className="size-4" />
97
+ </Button>
98
+ )}
99
+ <DialogTitle>Settings</DialogTitle>
100
+ </div>
101
+
102
+ <div className="space-y-2">
103
+ <div className="flex items-center gap-2">
104
+ <SectionTitle>Execution engine</SectionTitle>
105
+ <span className="ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground">
106
+ {effective === 'server' ? (
107
+ <ServerIcon className="size-3" />
108
+ ) : (
109
+ <GlobeIcon className="size-3" />
110
+ )}
111
+ <span
112
+ className={cn(
113
+ 'size-1.5 rounded-full',
114
+ offline
115
+ ? 'bg-destructive'
116
+ : effective === 'server'
117
+ ? 'bg-success'
118
+ : 'bg-muted-foreground',
119
+ )}
120
+ />
121
+ {effective === 'server' ? 'runner online' : offline ? 'runner offline' : 'browser'}
122
+ </span>
123
+ </div>
124
+ <div className="flex flex-col gap-1">
125
+ {ENGINE_OPTIONS.map((option) => (
126
+ <button
127
+ key={option.value}
128
+ onClick={() => setMode(option.value)}
129
+ className={cn(
130
+ 'flex cursor-pointer flex-col items-start rounded-md border px-2.5 py-1.5 text-left transition-colors hover:bg-accent',
131
+ mode === option.value ? 'border-ring bg-secondary/60' : 'border-border',
132
+ )}
133
+ >
134
+ <span className="text-[12.5px] font-medium">{option.label}</span>
135
+ <span className="text-[11px] text-muted-foreground">{option.hint}</span>
136
+ </button>
137
+ ))}
138
+ </div>
139
+ </div>
140
+
141
+ <div className="space-y-2">
142
+ <SectionTitle>Coding agent</SectionTitle>
143
+ {availableAgents.length === 0 ? (
144
+ <span className="text-[12px] text-muted-foreground">
145
+ No agent CLIs detected on PATH (looked for codex, claude).
146
+ </span>
147
+ ) : (
148
+ <div className="flex flex-col gap-1">
149
+ {availableAgents.map(({ kind, version }) => (
150
+ <button
151
+ key={kind}
152
+ onClick={() => setAgentChoice({ ...agentChoice, agent: kind })}
153
+ className={cn(
154
+ 'flex cursor-pointer items-center rounded-md border px-2.5 py-1.5 text-left transition-colors hover:bg-accent',
155
+ (agentChoice.agent ?? availableAgents[0]?.kind) === kind
156
+ ? 'border-ring bg-secondary/60'
157
+ : 'border-border',
158
+ )}
159
+ >
160
+ <span className="text-[12.5px] font-medium">
161
+ {AGENT_LABELS[kind]}
162
+ {version ? <span className="ml-1.5 font-normal text-muted-foreground">— {version}</span> : null}
163
+ </span>
164
+ </button>
165
+ ))}
166
+ </div>
167
+ )}
168
+ <label className="flex flex-col gap-1">
169
+ <span className="text-[11px] text-muted-foreground">Model (blank = default)</span>
170
+ <input
171
+ type="text"
172
+ value={agentChoice.model ?? ''}
173
+ onChange={(e) => setAgentChoice({ ...agentChoice, model: e.target.value.trim() || undefined })}
174
+ placeholder="default"
175
+ className="rounded-md border border-border bg-background px-2.5 py-1.5 text-[12.5px] outline-none focus:border-ring"
176
+ />
177
+ </label>
178
+ <label className="flex flex-col gap-1">
179
+ <span className="text-[11px] text-muted-foreground">Reasoning effort</span>
180
+ <div className="flex gap-1">
181
+ {(['low', 'medium', 'high'] as const).map((level) => {
182
+ const active = (agentChoice.reasoning ?? 'medium') === level;
183
+ return (
184
+ <button
185
+ key={level}
186
+ type="button"
187
+ onClick={() => setAgentChoice({ ...agentChoice, reasoning: level })}
188
+ className={cn(
189
+ 'flex-1 rounded-md border px-2 py-1.5 text-[12px] capitalize transition-colors',
190
+ active
191
+ ? 'border-highlight bg-highlight/15 text-foreground'
192
+ : 'border-border text-muted-foreground hover:text-foreground',
193
+ )}
194
+ >
195
+ {level}
196
+ </button>
197
+ );
198
+ })}
199
+ </div>
200
+ <span className="text-[10.5px] text-muted-foreground/70">
201
+ Higher = smarter but slower. Default medium; the agent builds better at medium/high.
202
+ </span>
203
+ </label>
204
+ </div>
205
+
206
+ <div className="space-y-2">
207
+ <SectionTitle>Run state</SectionTitle>
208
+ <div className="flex items-center justify-between gap-3">
209
+ <span className="text-[12px] text-muted-foreground">
210
+ Clear the current run, its logs and any live execution.
211
+ </span>
212
+ <Button
213
+ variant="outline"
214
+ size="sm"
215
+ className="shrink-0 gap-1.5"
216
+ onClick={() => {
217
+ resetRun();
218
+ setOpen(false);
219
+ }}
220
+ >
221
+ <RotateCcwIcon className="size-3.5" />
222
+ Reset run state
223
+ </Button>
224
+ </div>
225
+ </div>
226
+ </DialogContent>
227
+ </Dialog>
228
+ );
229
+ }
@@ -0,0 +1,436 @@
1
+ import { useMemo, useState } from 'react';
2
+ import {
3
+ ChevronDownIcon,
4
+ ChevronRightIcon,
5
+ CopyIcon,
6
+ FolderIcon,
7
+ MoreHorizontalIcon,
8
+ PlusIcon,
9
+ SettingsIcon,
10
+ Trash2Icon,
11
+ WorkflowIcon,
12
+ XIcon,
13
+ } from 'lucide-react';
14
+ import { Button } from '@/components/ui/button';
15
+ import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
16
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
17
+ import { Badge } from '@/components/ui/badge';
18
+ import { CreateModal } from './CreateModal';
19
+ import type { CreateModalState } from './CreateModal';
20
+ import { useBuilderStore } from '../store/builderStore';
21
+ import { buildApiTree } from '../store/apiTree';
22
+ import type { ApiTreeNode, OpItem } from '../store/apiTree';
23
+ import { cn } from '@/lib/utils';
24
+
25
+ function methodBadgeVariant(method?: string): 'success' | 'highlight' | 'default' | 'destructive' | 'outline' {
26
+ switch (method) {
27
+ case 'GET':
28
+ return 'success';
29
+ case 'POST':
30
+ return 'highlight';
31
+ case 'DELETE':
32
+ return 'destructive';
33
+ case 'PUT':
34
+ case 'PATCH':
35
+ return 'default';
36
+ default:
37
+ return 'outline';
38
+ }
39
+ }
40
+
41
+ /** Every operation id under this node (including nested folders). */
42
+ function collectOpIds(node: ApiTreeNode): string[] {
43
+ return node.operations.map((o) => o.id).concat(node.folders.flatMap(collectOpIds));
44
+ }
45
+
46
+ /**
47
+ * The hover "⋯" menu shared by API and operation rows. Reveals on the row's
48
+ * group-hover, stays visible while open. Currently one item — Delete — which
49
+ * fires immediately (no confirm dialog, per the delete-immediately spec).
50
+ */
51
+ function RowMenu({ label, onDelete }: { label: string; onDelete: () => void }) {
52
+ const [open, setOpen] = useState(false);
53
+ return (
54
+ <Popover open={open} onOpenChange={setOpen}>
55
+ <PopoverTrigger asChild>
56
+ <button
57
+ type="button"
58
+ onClick={(e) => e.stopPropagation()}
59
+ aria-label={label}
60
+ title={label}
61
+ className="shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
62
+ >
63
+ <MoreHorizontalIcon className="size-3.5" />
64
+ </button>
65
+ </PopoverTrigger>
66
+ <PopoverContent align="end" className="w-40 p-1" onClick={(e) => e.stopPropagation()}>
67
+ <button
68
+ type="button"
69
+ onClick={(e) => {
70
+ e.stopPropagation();
71
+ setOpen(false);
72
+ onDelete();
73
+ }}
74
+ className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-[12.5px] text-destructive transition-colors hover:bg-destructive/10"
75
+ >
76
+ <Trash2Icon className="size-3.5" /> Delete
77
+ </button>
78
+ </PopoverContent>
79
+ </Popover>
80
+ );
81
+ }
82
+
83
+ function OperationRow({ op, depth }: { op: OpItem; depth: number }) {
84
+ const activeId = useBuilderStore((s) => s.flow.id);
85
+ const switchWorkflow = useBuilderStore((s) => s.switchWorkflow);
86
+ const deleteOperations = useBuilderStore((s) => s.deleteOperations);
87
+ const isActive = op.id === activeId;
88
+ const rowTitle = op.method
89
+ ? `HTTP endpoint: ${op.method} ${op.httpPath ?? ''}`.trim()
90
+ : 'Runs inside the studio or as a sub-step — no HTTP endpoint';
91
+
92
+ return (
93
+ <div
94
+ role="button"
95
+ tabIndex={0}
96
+ onClick={() => switchWorkflow(op.id)}
97
+ onKeyDown={(e) => {
98
+ if (e.key === 'Enter' || e.key === ' ') {
99
+ e.preventDefault();
100
+ switchWorkflow(op.id);
101
+ }
102
+ }}
103
+ style={{ paddingLeft: 8 + depth * 14 }}
104
+ title={rowTitle}
105
+ className={cn(
106
+ 'group flex w-full cursor-pointer items-center gap-2 rounded-md py-1.5 pr-2.5 text-left text-[12.5px] transition-colors',
107
+ isActive
108
+ ? 'bg-sidebar-accent text-sidebar-accent-foreground'
109
+ : 'text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground',
110
+ )}
111
+ >
112
+ {op.method ? (
113
+ <Badge variant={methodBadgeVariant(op.method)} className="shrink-0">
114
+ {op.method}
115
+ </Badge>
116
+ ) : (
117
+ <span
118
+ className="inline-flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/50"
119
+ title="Runs inside the studio or as a sub-step — no HTTP endpoint"
120
+ >
121
+ <span className="size-1.5 rounded-full bg-muted-foreground/40" />
122
+ no endpoint
123
+ </span>
124
+ )}
125
+ <span className="truncate">{op.name}</span>
126
+ {op.httpPath && (
127
+ <span className="ml-auto truncate font-mono text-[10px] text-muted-foreground/60">
128
+ {op.httpPath}
129
+ </span>
130
+ )}
131
+ <span className={op.httpPath ? '' : 'ml-auto'}>
132
+ <RowMenu label={`Delete ${op.name}`} onDelete={() => void deleteOperations([op.id])} />
133
+ </span>
134
+ </div>
135
+ );
136
+ }
137
+
138
+ function countOps(node: ApiTreeNode): number {
139
+ return node.operations.length + node.folders.reduce((sum, f) => sum + countOps(f), 0);
140
+ }
141
+
142
+ /** Every operation under an API (including nested folders) that has an HTTP trigger. */
143
+ function collectHttpOps(node: ApiTreeNode): OpItem[] {
144
+ const own = node.operations.filter((op) => op.method && op.httpPath);
145
+ return node.folders.reduce((acc, f) => acc.concat(collectHttpOps(f)), own);
146
+ }
147
+
148
+ /**
149
+ * Gear/info affordance on each API row: opens a centered modal with
150
+ * API-level settings. Currently shows the absolute invocation URL
151
+ * (origin + http path) for every operation under this API that has an
152
+ * HTTP trigger, with a per-row copy button — laid out as a settings panel
153
+ * so more API-level sections can be added here later.
154
+ */
155
+ function ApiSettingsButton({ node }: { node: ApiTreeNode }) {
156
+ const [open, setOpen] = useState(false);
157
+ const [copiedId, setCopiedId] = useState<string | null>(null);
158
+ const httpOps = useMemo(() => collectHttpOps(node), [node]);
159
+
160
+ const copy = (op: OpItem) => {
161
+ const url = `${window.location.origin}${op.httpPath}`;
162
+ void navigator.clipboard.writeText(url);
163
+ setCopiedId(op.id);
164
+ window.setTimeout(() => setCopiedId((id) => (id === op.id ? null : id)), 1200);
165
+ };
166
+
167
+ return (
168
+ <Dialog open={open} onOpenChange={setOpen}>
169
+ <button
170
+ type="button"
171
+ onClick={(e) => {
172
+ e.stopPropagation();
173
+ setOpen(true);
174
+ }}
175
+ aria-label={`${node.name} settings`}
176
+ title="API settings"
177
+ className="ml-1 shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
178
+ >
179
+ <SettingsIcon className="size-3" />
180
+ </button>
181
+ <DialogContent className="max-w-lg" onClick={(e) => e.stopPropagation()}>
182
+ <div>
183
+ <DialogTitle>{node.name}</DialogTitle>
184
+ <p className="mt-0.5 text-[12px] text-muted-foreground">
185
+ {httpOps.length} operation{httpOps.length === 1 ? '' : 's'}
186
+ </p>
187
+ </div>
188
+ <div className="flex flex-col gap-1.5">
189
+ <div className="text-[11px] font-semibold tracking-wide text-muted-foreground uppercase">
190
+ Endpoints
191
+ </div>
192
+ <div className="max-h-96 overflow-y-auto rounded-md border border-border">
193
+ {httpOps.length === 0 ? (
194
+ <div className="px-3 py-3 text-[12px] text-muted-foreground/70">
195
+ No HTTP endpoints on this API.
196
+ </div>
197
+ ) : (
198
+ <div className="divide-y divide-border">
199
+ {httpOps.map((op) => {
200
+ const url = `${window.location.origin}${op.httpPath}`;
201
+ return (
202
+ <div key={op.id} className="flex items-center gap-2 px-2.5 py-2 hover:bg-accent">
203
+ <Badge variant={methodBadgeVariant(op.method)} className="shrink-0">
204
+ {op.method}
205
+ </Badge>
206
+ <span
207
+ className="min-w-0 flex-1 truncate font-mono text-[11.5px] text-foreground/90"
208
+ title={url}
209
+ >
210
+ {url}
211
+ </span>
212
+ <button
213
+ type="button"
214
+ onClick={() => copy(op)}
215
+ aria-label={`Copy URL for ${op.name}`}
216
+ title="Copy URL"
217
+ className="shrink-0 cursor-pointer rounded-sm p-1 text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
218
+ >
219
+ <CopyIcon className="size-3" />
220
+ </button>
221
+ {copiedId === op.id && (
222
+ <span className="shrink-0 text-[10px] text-success">Copied</span>
223
+ )}
224
+ </div>
225
+ );
226
+ })}
227
+ </div>
228
+ )}
229
+ </div>
230
+ </div>
231
+ </DialogContent>
232
+ </Dialog>
233
+ );
234
+ }
235
+
236
+ /** Flatten an API tree into every API/folder path (e.g. 'default', 'billing', 'billing/charges'). */
237
+ function flattenLocations(tree: ApiTreeNode[]): string[] {
238
+ const paths: string[] = [];
239
+ const walk = (node: ApiTreeNode, prefix: string) => {
240
+ const path = prefix ? `${prefix}/${node.name}` : node.name;
241
+ paths.push(path);
242
+ for (const folder of node.folders) walk(folder, path);
243
+ };
244
+ for (const api of tree) walk(api, '');
245
+ return paths;
246
+ }
247
+
248
+ function TreeNode({
249
+ node,
250
+ keyPath,
251
+ depth,
252
+ isApi,
253
+ collapsed,
254
+ toggle,
255
+ onAddOperation,
256
+ }: {
257
+ node: ApiTreeNode;
258
+ keyPath: string;
259
+ depth: number;
260
+ isApi?: boolean;
261
+ collapsed: Set<string>;
262
+ toggle: (key: string) => void;
263
+ /** Open the agentic create modal scoped to this API (from the hover +). */
264
+ onAddOperation: (location: string) => void;
265
+ }) {
266
+ const isCollapsed = collapsed.has(keyPath);
267
+ const deleteOperations = useBuilderStore((s) => s.deleteOperations);
268
+
269
+ return (
270
+ <div className={isApi ? 'group' : undefined}>
271
+ <div
272
+ role="button"
273
+ tabIndex={0}
274
+ onClick={() => toggle(keyPath)}
275
+ onKeyDown={(e) => {
276
+ if (e.key === 'Enter' || e.key === ' ') {
277
+ e.preventDefault();
278
+ toggle(keyPath);
279
+ }
280
+ }}
281
+ style={{ paddingLeft: 8 + depth * 14 }}
282
+ className={cn(
283
+ 'flex w-full cursor-pointer items-center gap-1.5 rounded-md py-1.5 pr-2 text-left transition-colors hover:bg-sidebar-accent/60',
284
+ isApi ? 'text-[12.5px] font-semibold text-foreground' : 'text-[12px] font-medium text-foreground/80',
285
+ )}
286
+ >
287
+ {isCollapsed ? (
288
+ <ChevronRightIcon className="size-3.5 shrink-0 text-muted-foreground" />
289
+ ) : (
290
+ <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" />
291
+ )}
292
+ {isApi ? (
293
+ <WorkflowIcon className="size-3.5 shrink-0 text-muted-foreground" />
294
+ ) : (
295
+ <FolderIcon className="size-3.5 shrink-0 text-muted-foreground" />
296
+ )}
297
+ <span className="truncate">{node.name}</span>
298
+ {isApi && <ApiSettingsButton node={node} />}
299
+ {isApi && (
300
+ <button
301
+ type="button"
302
+ onClick={(e) => {
303
+ e.stopPropagation();
304
+ onAddOperation(keyPath);
305
+ }}
306
+ aria-label={`New operation in ${node.name}`}
307
+ title="New operation"
308
+ className="shrink-0 cursor-pointer rounded-sm p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100"
309
+ >
310
+ <PlusIcon className="size-3.5" />
311
+ </button>
312
+ )}
313
+ {isApi && (
314
+ <RowMenu
315
+ label={`Delete ${node.name} API`}
316
+ onDelete={() => void deleteOperations(collectOpIds(node))}
317
+ />
318
+ )}
319
+ <span className="ml-auto font-mono text-[10px] text-muted-foreground/60">
320
+ {countOps(node)}
321
+ </span>
322
+ </div>
323
+ {!isCollapsed && (
324
+ <>
325
+ {node.folders.map((folder) => (
326
+ <TreeNode
327
+ key={folder.name}
328
+ node={folder}
329
+ keyPath={`${keyPath}/${folder.name}`}
330
+ depth={depth + 1}
331
+ collapsed={collapsed}
332
+ toggle={toggle}
333
+ onAddOperation={onAddOperation}
334
+ />
335
+ ))}
336
+ {node.operations.map((op) => (
337
+ <OperationRow key={op.id} op={op} depth={depth + 1} />
338
+ ))}
339
+ </>
340
+ )}
341
+ </div>
342
+ );
343
+ }
344
+
345
+ export function Sidebar() {
346
+ const workflows = useBuilderStore((s) => s.workflows);
347
+ const toggleSidebar = useBuilderStore((s) => s.toggleSidebar);
348
+ const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
349
+ const [createState, setCreateState] = useState<CreateModalState | null>(null);
350
+
351
+ const tree = useMemo(
352
+ () =>
353
+ buildApiTree(
354
+ workflows.map((w) => ({
355
+ id: w.id,
356
+ name: w.name,
357
+ path: w.path ?? `default/${w.id}`,
358
+ http: w.http,
359
+ })),
360
+ ),
361
+ [workflows],
362
+ );
363
+
364
+ const locations = useMemo(() => flattenLocations(tree), [tree]);
365
+
366
+ const toggle = (key: string) => {
367
+ setCollapsed((prev) => {
368
+ const next = new Set(prev);
369
+ if (next.has(key)) next.delete(key);
370
+ else next.add(key);
371
+ return next;
372
+ });
373
+ };
374
+
375
+ return (
376
+ <aside className="flex h-full w-full flex-col bg-sidebar">
377
+ <div className="flex items-center justify-between px-3 pt-3 pb-2">
378
+ <span className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
379
+ APIs
380
+ </span>
381
+ <span className="flex items-center">
382
+ <Button
383
+ variant="ghost"
384
+ size="xs"
385
+ aria-label="New operation"
386
+ title="New operation"
387
+ onClick={() => setCreateState({ mode: 'operation' })}
388
+ className="text-muted-foreground hover:text-foreground"
389
+ >
390
+ <PlusIcon />
391
+ </Button>
392
+ <Button
393
+ variant="ghost"
394
+ size="xs"
395
+ onClick={toggleSidebar}
396
+ aria-label="Close sidebar"
397
+ title="Close sidebar (⌘B)"
398
+ className="text-muted-foreground hover:text-foreground"
399
+ >
400
+ <XIcon />
401
+ </Button>
402
+ </span>
403
+ </div>
404
+ <nav className="flex-1 overflow-y-auto px-2 pb-2">
405
+ {tree.map((api) => (
406
+ <TreeNode
407
+ key={api.name}
408
+ node={api}
409
+ keyPath={api.name}
410
+ depth={0}
411
+ isApi
412
+ collapsed={collapsed}
413
+ toggle={toggle}
414
+ onAddOperation={(location) => setCreateState({ mode: 'operation', location })}
415
+ />
416
+ ))}
417
+ </nav>
418
+ <div className="border-t border-sidebar-border p-2">
419
+ <Button
420
+ variant="secondary"
421
+ size="sm"
422
+ className="w-full"
423
+ onClick={() => setCreateState({ mode: 'api' })}
424
+ >
425
+ <PlusIcon /> New API
426
+ </Button>
427
+ </div>
428
+
429
+ <CreateModal
430
+ state={createState}
431
+ onOpenChange={(open) => !open && setCreateState(null)}
432
+ locations={locations}
433
+ />
434
+ </aside>
435
+ );
436
+ }