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