@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
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { findScenario, isArtifact, publishFlow, scenarioNames, } from '../src/engine/index.js';
|
|
5
|
+
import { createDefaultRegistry } from '../src/nodes/index.js';
|
|
6
|
+
import { parsePathParams } from '../src/lib/pathParams.js';
|
|
7
|
+
import { diagnoseOperation } from '../src/engine/diagnostics.js';
|
|
8
|
+
import { seedParamDefaults } from './normalizeFlow.js';
|
|
9
|
+
import { loadProjectConfig } from './projectConfig.js';
|
|
10
|
+
import { loadEnvironments } from './environments.js';
|
|
11
|
+
import { buildApiStore, buildRegistries, requireProjectWhenExplicit } from './projectMode.js';
|
|
12
|
+
import * as client from './client.js';
|
|
13
|
+
import { RunnerUnreachableError } from './client.js';
|
|
14
|
+
import { formatScenarioLine, formatSummary, runScenarioSuite, ScenarioTestUsageError, } from './testRunner.js';
|
|
15
|
+
const USAGE = `Usage: emberflow-cli <command> [args]
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
list-nodes List all registered node types
|
|
19
|
+
node-schema <type> Print the full NodeDefinition for a node type
|
|
20
|
+
list-workflows List workflows known to the runner
|
|
21
|
+
get-workflow <id> Print a workflow's full flow JSON
|
|
22
|
+
get-node <opId> <nodeId> Print one node's wiring: its instance
|
|
23
|
+
(config, inputMap, retry/optional), its inbound
|
|
24
|
+
+ outbound edges, and — when the type is
|
|
25
|
+
registered — a definition summary (input/output
|
|
26
|
+
field names, traceKind, effects). Runner-
|
|
27
|
+
preferred; falls back to an in-process offline
|
|
28
|
+
load when the runner is unreachable. Unknown op
|
|
29
|
+
or node exits 1.
|
|
30
|
+
list-environments List configured environments (names + key lists, no values;
|
|
31
|
+
includes per-env auth: {configured, authenticated} when set).
|
|
32
|
+
Prefers the runner; if it's unreachable, falls back to an
|
|
33
|
+
in-process offline load of the same files (NO runner
|
|
34
|
+
required) — prints "source": "offline" and, since login
|
|
35
|
+
state lives runner-side, auth.authenticated: "unknown".
|
|
36
|
+
login-environment <name> Perform the environment's configured login and store the
|
|
37
|
+
captured credential runner-side; prints {environment,
|
|
38
|
+
authenticated, secretRef} (never the secret value)
|
|
39
|
+
set-environment-auth <name> --json '<EnvAuth JSON>'
|
|
40
|
+
Set (or, with --json 'null', clear) an environment's auth
|
|
41
|
+
config; prints {environment, configured: true}. Carries no
|
|
42
|
+
secret values — only refs/names.
|
|
43
|
+
serving <real|mock> Switch whether mounted HTTP endpoints execute for real or
|
|
44
|
+
answer from scenario expectations (mock); prints
|
|
45
|
+
{serving: mode}.
|
|
46
|
+
validate <file|id> Validate a flow (file path or workflow id)
|
|
47
|
+
publish <id|file> [--out <path>] Publish a flow to a sealed artifact JSON file
|
|
48
|
+
run <file|id> [--input '<json>' | --input-file <path> | --scenario <name>]
|
|
49
|
+
[--env <name>] [--safe | --unsafe] [--confirm <name>] [--full]
|
|
50
|
+
Run a flow to completion. Prints a CONCISE result by
|
|
51
|
+
default (per-node status + truncated I/O); --full for raw.
|
|
52
|
+
save <file> Save a flow file to the runner
|
|
53
|
+
create <id> --method <M> --path </route> [--name "<display>"]
|
|
54
|
+
Create a new operation shell (Input → Response + http
|
|
55
|
+
trigger) at the given apis id. Then edit it to add nodes.
|
|
56
|
+
delete <id> Delete an operation/workflow from the runner
|
|
57
|
+
rename <old-id> <new-id> [--name "<display>"]
|
|
58
|
+
Rename an operation (updates id + display name + http route)
|
|
59
|
+
samples <nodeId> Print recorded execution samples for a node
|
|
60
|
+
test [opId] [--environment <name>] [--json] [--mock]
|
|
61
|
+
Run every scenario with an \`expect\` assertion
|
|
62
|
+
in-process (NO runner required) and report
|
|
63
|
+
pass/fail/skip. [opId] limits to one operation.
|
|
64
|
+
Scenarios without \`expect\` (or an empty \`{}\`)
|
|
65
|
+
are skipped. --json prints a machine-readable
|
|
66
|
+
array instead of the default text summary.
|
|
67
|
+
--mock runs each scenario as a Mock run
|
|
68
|
+
(mockRun: true): infrastructure nodes
|
|
69
|
+
(traceKind db/http/llm) return their sidecar
|
|
70
|
+
mock instead of touching real infra — an
|
|
71
|
+
op-level \`flow.mocks\` overlaid by the
|
|
72
|
+
scenario's own \`mocks\` (scenario wins per
|
|
73
|
+
nodeId) — and fail loudly if unmocked. Off by
|
|
74
|
+
default (real runs). The hermetic way to prove
|
|
75
|
+
branch coverage for infra-heavy ops without
|
|
76
|
+
live Postgres/Weld/OpenRouter.
|
|
77
|
+
Exit 0 all pass, 1 any fail, 2 usage (unknown
|
|
78
|
+
opId/environment).
|
|
79
|
+
doctor [opId] [--fix] Diagnose operation(s) in-process (NO runner
|
|
80
|
+
required): missing path-param defaults, params
|
|
81
|
+
no scenario exercises, scenarios with no
|
|
82
|
+
expect. [opId] limits to one operation.
|
|
83
|
+
--fix seeds '' param defaults in-place (never
|
|
84
|
+
overwrites an existing value) and re-diagnoses
|
|
85
|
+
so the report reflects post-fix state.
|
|
86
|
+
Exit 0 no error-severity diagnostics, 1 at
|
|
87
|
+
least one error-severity diagnostic, 2 usage
|
|
88
|
+
(unknown opId / unknown flag).
|
|
89
|
+
|
|
90
|
+
Env:
|
|
91
|
+
EMBERFLOW_RUNNER_URL Runner base URL (default http://127.0.0.1:8092)
|
|
92
|
+
|
|
93
|
+
Notes:
|
|
94
|
+
run --step is not supported by the CLI; step mode is interactive-only.
|
|
95
|
+
run --input, --input-file, and --scenario are mutually exclusive.
|
|
96
|
+
run --scenario <name> looks up a named test input embedded in the flow
|
|
97
|
+
(scenario name or id); artifacts carry no scenarios.
|
|
98
|
+
run accepts either a flow file/id or a published artifact file ($artifact) —
|
|
99
|
+
artifacts always execute under production semantics.
|
|
100
|
+
run --env <name> selects an environment from emberflow.environments.json
|
|
101
|
+
(omitted → the runner's default environment).
|
|
102
|
+
run --safe / --unsafe overrides safe mode (omitted → the environment's
|
|
103
|
+
default: protected environments default to safe).
|
|
104
|
+
run --unsafe on a protected environment additionally requires
|
|
105
|
+
--confirm <name> matching --env exactly, or the runner rejects the run.
|
|
106
|
+
`;
|
|
107
|
+
function printJson(value) {
|
|
108
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
109
|
+
}
|
|
110
|
+
/** Value after a `--flag` in argv, or undefined. */
|
|
111
|
+
function flagValue(args, flag) {
|
|
112
|
+
const i = args.indexOf(flag);
|
|
113
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
114
|
+
}
|
|
115
|
+
/** kebab/snake slug → Title Case display name (e.g. "latest-results" → "Latest Results"). */
|
|
116
|
+
function titleCaseSlug(slug) {
|
|
117
|
+
return slug
|
|
118
|
+
.split(/[-_\s]+/)
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
121
|
+
.join(' ');
|
|
122
|
+
}
|
|
123
|
+
/** Shrink a large value to a keys+preview stub so a `run` summary stays readable.
|
|
124
|
+
* Small values pass through untouched; anything over `cap` chars of JSON is
|
|
125
|
+
* replaced by its shape (keys/length) + a short preview. */
|
|
126
|
+
function summarizeValue(v, cap = 4000) {
|
|
127
|
+
if (v === undefined || v === null)
|
|
128
|
+
return v;
|
|
129
|
+
const s = JSON.stringify(v);
|
|
130
|
+
if (s.length <= cap)
|
|
131
|
+
return v;
|
|
132
|
+
const stub = { __truncated: true, chars: s.length };
|
|
133
|
+
if (Array.isArray(v)) {
|
|
134
|
+
stub.arrayLength = v.length;
|
|
135
|
+
stub.first = summarizeValue(v[0], 300);
|
|
136
|
+
}
|
|
137
|
+
else if (typeof v === 'object') {
|
|
138
|
+
stub.keys = Object.keys(v);
|
|
139
|
+
}
|
|
140
|
+
stub.preview = s.slice(0, 300);
|
|
141
|
+
return stub;
|
|
142
|
+
}
|
|
143
|
+
/** A concise per-node view of a run: status + error + truncated input/output —
|
|
144
|
+
* the default `run` output, so a flow whose fetch nodes dump 70KB of raw API
|
|
145
|
+
* JSON doesn't blow up the agent's context on every run-inspect cycle. */
|
|
146
|
+
function summarizeNodeStates(states) {
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const [id, raw] of Object.entries(states)) {
|
|
149
|
+
const st = (raw ?? {});
|
|
150
|
+
out[id] = {
|
|
151
|
+
status: st.status,
|
|
152
|
+
...(st.error ? { error: st.error } : {}),
|
|
153
|
+
...(st.output !== undefined ? { output: summarizeValue(st.output) } : {}),
|
|
154
|
+
...(st.input !== undefined ? { input: summarizeValue(st.input) } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
/** Field names of a node schema, tolerant of the loose `unknown` shape the
|
|
160
|
+
* runner's /nodes payload carries (vs. the typed offline registry). */
|
|
161
|
+
function schemaFieldNames(schema) {
|
|
162
|
+
const fields = schema?.fields;
|
|
163
|
+
if (!Array.isArray(fields))
|
|
164
|
+
return [];
|
|
165
|
+
return fields.map((f) => f?.name).filter((n) => typeof n === 'string');
|
|
166
|
+
}
|
|
167
|
+
/** Compact `definition` summary for `get-node`: input/output field names, trace
|
|
168
|
+
* kind, effects (absent = 'read' per NodeDefinition). Accepts either a runner
|
|
169
|
+
* NodeMeta or an offline NodeDefinition — both carry these fields. */
|
|
170
|
+
function summarizeNodeDefinition(def) {
|
|
171
|
+
return {
|
|
172
|
+
type: def.type,
|
|
173
|
+
label: def.label,
|
|
174
|
+
traceKind: def.traceKind,
|
|
175
|
+
effects: def.effects ?? 'read',
|
|
176
|
+
inputFields: schemaFieldNames(def.inputSchema),
|
|
177
|
+
outputFields: schemaFieldNames(def.outputSchema),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Thrown by `fail` to unwind to `runCli`, which turns it into a return code.
|
|
181
|
+
* Lets the parse helpers keep their `never`-returning `fail(...)` call sites
|
|
182
|
+
* while runCli stays in-process (no process.exit) so it's usable as a library. */
|
|
183
|
+
class CliExit extends Error {
|
|
184
|
+
code;
|
|
185
|
+
constructor(code) {
|
|
186
|
+
super(`CliExit(${code})`);
|
|
187
|
+
this.code = code;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Prints {error} JSON to stderr and unwinds with the given code. Never returns. */
|
|
191
|
+
function fail(message, code) {
|
|
192
|
+
process.stderr.write(`${JSON.stringify({ error: message })}\n`);
|
|
193
|
+
throw new CliExit(code);
|
|
194
|
+
}
|
|
195
|
+
/** File path if it exists on disk, else fetched by id from the runner. */
|
|
196
|
+
async function resolveFlow(arg) {
|
|
197
|
+
if (existsSync(arg)) {
|
|
198
|
+
return JSON.parse(readFileSync(arg, 'utf8'));
|
|
199
|
+
}
|
|
200
|
+
const flow = await client.getWorkflow(arg);
|
|
201
|
+
if (!flow) {
|
|
202
|
+
throw new Error(`No flow file at "${arg}" and no workflow with id "${arg}" on the runner`);
|
|
203
|
+
}
|
|
204
|
+
return flow;
|
|
205
|
+
}
|
|
206
|
+
/** Like resolveFlow, but recognizes a file whose JSON is a published artifact. */
|
|
207
|
+
async function resolveFlowOrArtifact(arg) {
|
|
208
|
+
if (existsSync(arg)) {
|
|
209
|
+
const parsed = JSON.parse(readFileSync(arg, 'utf8'));
|
|
210
|
+
return isArtifact(parsed) ? parsed : parsed;
|
|
211
|
+
}
|
|
212
|
+
const flow = await client.getWorkflow(arg);
|
|
213
|
+
if (!flow) {
|
|
214
|
+
throw new Error(`No flow file at "${arg}" and no workflow with id "${arg}" on the runner`);
|
|
215
|
+
}
|
|
216
|
+
return flow;
|
|
217
|
+
}
|
|
218
|
+
/** Parses --env/--safe/--unsafe/--confirm flags. --safe and --unsafe are mutually exclusive. */
|
|
219
|
+
function parseEnvFlags(args) {
|
|
220
|
+
const envIdx = args.indexOf('--env');
|
|
221
|
+
let environment;
|
|
222
|
+
if (envIdx !== -1) {
|
|
223
|
+
environment = args[envIdx + 1];
|
|
224
|
+
if (!environment)
|
|
225
|
+
fail('Usage: run <file|id> --env <name>', 2);
|
|
226
|
+
}
|
|
227
|
+
const hasSafe = args.includes('--safe');
|
|
228
|
+
const hasUnsafe = args.includes('--unsafe');
|
|
229
|
+
if (hasSafe && hasUnsafe) {
|
|
230
|
+
fail('--safe and --unsafe are mutually exclusive', 2);
|
|
231
|
+
}
|
|
232
|
+
const safeMode = hasSafe ? true : hasUnsafe ? false : undefined;
|
|
233
|
+
const confirmIdx = args.indexOf('--confirm');
|
|
234
|
+
let confirm;
|
|
235
|
+
if (confirmIdx !== -1) {
|
|
236
|
+
confirm = args[confirmIdx + 1];
|
|
237
|
+
if (!confirm)
|
|
238
|
+
fail('Usage: run <file|id> --confirm <name>', 2);
|
|
239
|
+
}
|
|
240
|
+
return { environment, safeMode, confirm };
|
|
241
|
+
}
|
|
242
|
+
/** Parses --input/--input-file flags. Mutually exclusive; parse errors exit 2. */
|
|
243
|
+
function parseInputFlag(args) {
|
|
244
|
+
const inputIdx = args.indexOf('--input');
|
|
245
|
+
const inputFileIdx = args.indexOf('--input-file');
|
|
246
|
+
if (inputIdx !== -1 && inputFileIdx !== -1) {
|
|
247
|
+
fail('--input and --input-file are mutually exclusive', 2);
|
|
248
|
+
}
|
|
249
|
+
if (inputIdx !== -1) {
|
|
250
|
+
const raw = args[inputIdx + 1];
|
|
251
|
+
if (!raw)
|
|
252
|
+
fail("Usage: run <file|id> --input '<json>'", 2);
|
|
253
|
+
try {
|
|
254
|
+
return JSON.parse(raw);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
fail(`Invalid --input JSON: ${err instanceof Error ? err.message : String(err)}`, 2);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (inputFileIdx !== -1) {
|
|
261
|
+
const path = args[inputFileIdx + 1];
|
|
262
|
+
if (!path)
|
|
263
|
+
fail('Usage: run <file|id> --input-file <path>', 2);
|
|
264
|
+
if (!existsSync(path))
|
|
265
|
+
fail(`No such --input-file: ${path}`, 2);
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
fail(`Invalid JSON in --input-file ${path}: ${err instanceof Error ? err.message : String(err)}`, 2);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Runs a single CLI command in-process and resolves to a process exit code
|
|
277
|
+
* (0 success, non-zero on error) — never calls process.exit, so it's safe to
|
|
278
|
+
* drive from the register-API bin inside a sandbox (no `tsx` child spawn).
|
|
279
|
+
* `argv` is `[command, ...rest]`.
|
|
280
|
+
*/
|
|
281
|
+
export async function runCli(argv) {
|
|
282
|
+
const [command, ...args] = argv;
|
|
283
|
+
try {
|
|
284
|
+
return await dispatch(command, args);
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
if (err instanceof CliExit)
|
|
288
|
+
return err.code;
|
|
289
|
+
if (err instanceof RunnerUnreachableError) {
|
|
290
|
+
process.stderr.write(`${JSON.stringify({ error: err.message })}\n`);
|
|
291
|
+
return 2;
|
|
292
|
+
}
|
|
293
|
+
process.stderr.write(`${JSON.stringify({ error: err instanceof Error ? err.message : String(err) })}\n`);
|
|
294
|
+
return 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function dispatch(command, args) {
|
|
298
|
+
switch (command) {
|
|
299
|
+
case 'list-nodes': {
|
|
300
|
+
// The RUNNER's live registry — built-ins + the project's registerNodes —
|
|
301
|
+
// NOT a local createDefaultRegistry(), which is blind to project nodes.
|
|
302
|
+
const nodes = await client.listNodes();
|
|
303
|
+
printJson(nodes.map((d) => ({
|
|
304
|
+
type: d.type,
|
|
305
|
+
label: d.label,
|
|
306
|
+
category: d.category,
|
|
307
|
+
description: d.description,
|
|
308
|
+
})));
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
case 'node-schema': {
|
|
312
|
+
const type = args[0];
|
|
313
|
+
if (!type)
|
|
314
|
+
fail('Usage: node-schema <type>', 2);
|
|
315
|
+
const nodes = await client.listNodes();
|
|
316
|
+
const node = nodes.find((n) => n.type === type);
|
|
317
|
+
if (!node) {
|
|
318
|
+
fail(`Unknown node type: ${type}. (Run list-nodes to see registered types.)`, 2);
|
|
319
|
+
}
|
|
320
|
+
printJson(node);
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
case 'list-workflows': {
|
|
324
|
+
const flows = await client.listWorkflows();
|
|
325
|
+
printJson(flows.map((f) => ({ id: f.id, name: f.name, folder: f.folder, nodes: f.nodes.length })));
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
case 'get-workflow': {
|
|
329
|
+
const id = args[0];
|
|
330
|
+
if (!id)
|
|
331
|
+
fail('Usage: get-workflow <id>', 2);
|
|
332
|
+
const flow = await client.getWorkflow(id);
|
|
333
|
+
if (!flow)
|
|
334
|
+
fail(`Unknown workflow: ${id}`, 1);
|
|
335
|
+
printJson(flow);
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
case 'get-node': {
|
|
339
|
+
const opId = args[0];
|
|
340
|
+
const nodeId = args[1];
|
|
341
|
+
if (!opId || !nodeId)
|
|
342
|
+
fail('Usage: get-node <opId> <nodeId>', 2);
|
|
343
|
+
// Resolve the flow + a node-type definition lookup. Runner-preferred
|
|
344
|
+
// (mirrors get-workflow / node-schema); falls back to an in-process
|
|
345
|
+
// offline load of the project files when the runner is unreachable, so
|
|
346
|
+
// node inspection works with no server up.
|
|
347
|
+
let flow;
|
|
348
|
+
let lookupDef;
|
|
349
|
+
try {
|
|
350
|
+
flow = await client.getWorkflow(opId);
|
|
351
|
+
const nodes = await client.listNodes();
|
|
352
|
+
lookupDef = (type) => {
|
|
353
|
+
const def = nodes.find((n) => n.type === type);
|
|
354
|
+
return def ? summarizeNodeDefinition(def) : undefined;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
if (!(err instanceof RunnerUnreachableError))
|
|
359
|
+
throw err;
|
|
360
|
+
const projectDir = process.env.EMBERFLOW_PROJECT
|
|
361
|
+
? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
|
|
362
|
+
: process.cwd();
|
|
363
|
+
const project = requireProjectWhenExplicit(await loadProjectConfig(projectDir), process.env.EMBERFLOW_PROJECT, projectDir);
|
|
364
|
+
const apiStore = buildApiStore(project);
|
|
365
|
+
flow = apiStore.load(opId);
|
|
366
|
+
const { validation } = buildRegistries(project);
|
|
367
|
+
lookupDef = (type) => validation.has(type) ? summarizeNodeDefinition(validation.get(type).definition) : undefined;
|
|
368
|
+
}
|
|
369
|
+
if (!flow)
|
|
370
|
+
fail(`Unknown operation: ${opId}`, 1);
|
|
371
|
+
const node = flow.nodes.find((n) => n.id === nodeId);
|
|
372
|
+
if (!node)
|
|
373
|
+
fail(`Unknown node "${nodeId}" in operation "${opId}"`, 1);
|
|
374
|
+
const edges = flow.edges ?? [];
|
|
375
|
+
const inbound = edges
|
|
376
|
+
.filter((e) => e.target === nodeId)
|
|
377
|
+
.map((e) => ({ source: e.source, sourceHandle: e.sourceHandle, targetHandle: e.targetHandle }));
|
|
378
|
+
const outbound = edges
|
|
379
|
+
.filter((e) => e.source === nodeId)
|
|
380
|
+
.map((e) => ({ target: e.target, sourceHandle: e.sourceHandle, targetHandle: e.targetHandle }));
|
|
381
|
+
printJson({
|
|
382
|
+
operation: opId,
|
|
383
|
+
node: {
|
|
384
|
+
id: node.id,
|
|
385
|
+
type: node.type,
|
|
386
|
+
label: node.label,
|
|
387
|
+
config: node.config,
|
|
388
|
+
inputMap: node.inputMap,
|
|
389
|
+
...(node.retry ? { retry: node.retry } : {}),
|
|
390
|
+
...(node.optional !== undefined ? { optional: node.optional } : {}),
|
|
391
|
+
},
|
|
392
|
+
inbound,
|
|
393
|
+
outbound,
|
|
394
|
+
definition: lookupDef(node.type) ?? null,
|
|
395
|
+
});
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
case 'list-environments': {
|
|
399
|
+
try {
|
|
400
|
+
printJson({ source: 'runner', ...(await client.listEnvironments()) });
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
if (!(err instanceof RunnerUnreachableError))
|
|
404
|
+
throw err;
|
|
405
|
+
// Runner unreachable — fall back to an in-process offline load of the
|
|
406
|
+
// same emberflow.environments.json/emberflow.secrets.json the runner
|
|
407
|
+
// would read, so intake (which the skills tell agents to run before a
|
|
408
|
+
// runner is up) still works. Auth *status* (logged-in state) lives
|
|
409
|
+
// runner-side (it's set by a live login run) — offline we can only
|
|
410
|
+
// report whether auth is *configured*, not whether it's authenticated.
|
|
411
|
+
const projectDir = process.env.EMBERFLOW_PROJECT
|
|
412
|
+
? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
|
|
413
|
+
: process.cwd();
|
|
414
|
+
const envFile = loadEnvironments(projectDir);
|
|
415
|
+
printJson({
|
|
416
|
+
source: 'offline',
|
|
417
|
+
defaultEnvironment: envFile.defaultEnvironment,
|
|
418
|
+
environments: Object.entries(envFile.environments).map(([name, env]) => ({
|
|
419
|
+
name,
|
|
420
|
+
protected: !!env.protected,
|
|
421
|
+
varKeys: Object.keys(env.vars),
|
|
422
|
+
secretKeys: Object.keys(env.secrets),
|
|
423
|
+
...(env.auth ? { auth: { configured: true, authenticated: 'unknown' } } : {}),
|
|
424
|
+
})),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
case 'login-environment': {
|
|
430
|
+
const name = args[0];
|
|
431
|
+
if (!name)
|
|
432
|
+
fail('Usage: login-environment <name>', 2);
|
|
433
|
+
// Errors (unknown env 404, no auth.login 400, upstream login failure 502)
|
|
434
|
+
// bubble to runCli's catch-all, which prints {error} and returns 1.
|
|
435
|
+
const { authenticated, secretRef } = await client.loginEnvironment(name);
|
|
436
|
+
printJson({ environment: name, authenticated, secretRef });
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
case 'set-environment-auth': {
|
|
440
|
+
const name = args[0];
|
|
441
|
+
if (!name)
|
|
442
|
+
fail("Usage: set-environment-auth <name> --json '<EnvAuth JSON>'", 2);
|
|
443
|
+
const raw = flagValue(args, '--json');
|
|
444
|
+
if (!raw)
|
|
445
|
+
fail("Usage: set-environment-auth <name> --json '<EnvAuth JSON>'", 2);
|
|
446
|
+
let auth;
|
|
447
|
+
try {
|
|
448
|
+
auth = JSON.parse(raw);
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
fail(`Invalid --json: ${err instanceof Error ? err.message : String(err)}`, 2);
|
|
452
|
+
}
|
|
453
|
+
// Errors (unknown env 404, invalid auth shape 400) bubble to runCli's
|
|
454
|
+
// catch-all, which prints {error} and returns 1.
|
|
455
|
+
await client.setEnvironmentAuth(name, auth);
|
|
456
|
+
printJson({ environment: name, configured: true });
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
case 'serving': {
|
|
460
|
+
const mode = args[0];
|
|
461
|
+
if (mode !== 'real' && mode !== 'mock') {
|
|
462
|
+
fail('Usage: serving <real|mock>', 2);
|
|
463
|
+
}
|
|
464
|
+
// Errors (e.g. runner rejects the mode) bubble to runCli's catch-all,
|
|
465
|
+
// which prints {error} and returns 1.
|
|
466
|
+
await client.setServingMode(mode);
|
|
467
|
+
printJson({ serving: mode });
|
|
468
|
+
return 0;
|
|
469
|
+
}
|
|
470
|
+
case 'validate': {
|
|
471
|
+
const arg = args[0];
|
|
472
|
+
if (!arg)
|
|
473
|
+
fail('Usage: validate <file|id>', 2);
|
|
474
|
+
const flow = await resolveFlow(arg);
|
|
475
|
+
// Validate against the runner's live registry (built-ins + project nodes),
|
|
476
|
+
// not a project-blind local createDefaultRegistry().
|
|
477
|
+
const { valid, issues } = await client.validateOperation(flow);
|
|
478
|
+
printJson({ valid, issues });
|
|
479
|
+
return valid ? 0 : 1;
|
|
480
|
+
}
|
|
481
|
+
case 'run': {
|
|
482
|
+
if (args.includes('--step')) {
|
|
483
|
+
fail('run --step is not supported by the CLI; step mode is interactive-only', 2);
|
|
484
|
+
}
|
|
485
|
+
const arg = args[0];
|
|
486
|
+
if (!arg) {
|
|
487
|
+
fail("Usage: run <file|id> [--input '<json>' | --input-file <path> | --scenario <name>]", 2);
|
|
488
|
+
}
|
|
489
|
+
const scenarioIdx = args.indexOf('--scenario');
|
|
490
|
+
if (scenarioIdx !== -1 && (args.includes('--input') || args.includes('--input-file'))) {
|
|
491
|
+
fail('--scenario and --input/--input-file are mutually exclusive', 2);
|
|
492
|
+
}
|
|
493
|
+
let scenarioName;
|
|
494
|
+
if (scenarioIdx !== -1) {
|
|
495
|
+
scenarioName = args[scenarioIdx + 1];
|
|
496
|
+
if (!scenarioName)
|
|
497
|
+
fail('Usage: run <file|id> --scenario <name>', 2);
|
|
498
|
+
}
|
|
499
|
+
let input = parseInputFlag(args);
|
|
500
|
+
const { environment, safeMode, confirm } = parseEnvFlags(args);
|
|
501
|
+
const resolved = await resolveFlowOrArtifact(arg);
|
|
502
|
+
if (scenarioName !== undefined) {
|
|
503
|
+
if (isArtifact(resolved)) {
|
|
504
|
+
fail('Artifacts carry no scenarios; pass --input instead', 2);
|
|
505
|
+
}
|
|
506
|
+
const scenario = findScenario(resolved, scenarioName);
|
|
507
|
+
if (!scenario) {
|
|
508
|
+
const names = scenarioNames(resolved);
|
|
509
|
+
fail(`Unknown scenario: "${scenarioName}". Available: ${names.length ? names.join(', ') : 'none'}`, 1);
|
|
510
|
+
}
|
|
511
|
+
input = scenario.input;
|
|
512
|
+
}
|
|
513
|
+
const runId = isArtifact(resolved)
|
|
514
|
+
? await client.startRun({ artifact: resolved, input, environment, safeMode, confirm })
|
|
515
|
+
: await client.startRun({ flow: resolved, input, environment, safeMode, confirm });
|
|
516
|
+
const { run: finished, logs } = await client.waitForRun(runId);
|
|
517
|
+
const full = args.includes('--full');
|
|
518
|
+
printJson({
|
|
519
|
+
status: finished.status,
|
|
520
|
+
environment: finished.environment,
|
|
521
|
+
safeMode: finished.safeMode,
|
|
522
|
+
nodeStates: full ? finished.nodeStates : summarizeNodeStates(finished.nodeStates),
|
|
523
|
+
logs,
|
|
524
|
+
...(full ? {} : { note: 'node inputs/outputs over 4000 chars are truncated (keys + preview shown); pass --full for the raw nodeStates.' }),
|
|
525
|
+
});
|
|
526
|
+
return finished.status === 'succeeded' ? 0 : 1;
|
|
527
|
+
}
|
|
528
|
+
case 'publish': {
|
|
529
|
+
const arg = args[0];
|
|
530
|
+
if (!arg)
|
|
531
|
+
fail('Usage: publish <id|file> [--out <path>]', 2);
|
|
532
|
+
const outIdx = args.indexOf('--out');
|
|
533
|
+
const outPathArg = outIdx !== -1 ? args[outIdx + 1] : undefined;
|
|
534
|
+
if (outIdx !== -1 && !outPathArg)
|
|
535
|
+
fail('Usage: publish <id|file> [--out <path>]', 2);
|
|
536
|
+
const flow = await resolveFlow(arg);
|
|
537
|
+
const registry = createDefaultRegistry();
|
|
538
|
+
const artifact = await publishFlow(flow, registry);
|
|
539
|
+
const outPath = outPathArg ?? `artifacts/${flow.id}.json`;
|
|
540
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
541
|
+
writeFileSync(outPath, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
542
|
+
printJson({ ok: true, path: outPath, nodeTypes: Object.keys(artifact.nodeHashes).length });
|
|
543
|
+
return 0;
|
|
544
|
+
}
|
|
545
|
+
case 'save': {
|
|
546
|
+
const file = args[0];
|
|
547
|
+
if (!file)
|
|
548
|
+
fail('Usage: save <file>', 2);
|
|
549
|
+
if (!existsSync(file))
|
|
550
|
+
fail(`No such file: ${file}`, 2);
|
|
551
|
+
const flow = JSON.parse(readFileSync(file, 'utf8'));
|
|
552
|
+
await client.saveWorkflow(flow);
|
|
553
|
+
printJson({ ok: true, id: flow.id });
|
|
554
|
+
return 0;
|
|
555
|
+
}
|
|
556
|
+
case 'delete': {
|
|
557
|
+
const id = args[0];
|
|
558
|
+
if (!id)
|
|
559
|
+
fail('Usage: delete <id>', 2);
|
|
560
|
+
const result = await client.deleteOperation(id);
|
|
561
|
+
printJson(result);
|
|
562
|
+
return result.ok ? 0 : 1;
|
|
563
|
+
}
|
|
564
|
+
case 'create': {
|
|
565
|
+
const id = args.filter((a) => !a.startsWith('--'))[0];
|
|
566
|
+
if (!id) {
|
|
567
|
+
fail('Usage: create <id> --method <GET|POST|PATCH|PUT|DELETE> --path </route> [--name "<display>"]', 2);
|
|
568
|
+
}
|
|
569
|
+
const method = flagValue(args, '--method')?.toUpperCase();
|
|
570
|
+
const path = flagValue(args, '--path');
|
|
571
|
+
const name = flagValue(args, '--name') ?? titleCaseSlug(id.split('/').filter(Boolean).pop() ?? id);
|
|
572
|
+
const now = new Date().toISOString();
|
|
573
|
+
// Minimal valid shell: Input → terminus. HTTP endpoints get a Response
|
|
574
|
+
// node + http trigger (method decided up front); non-HTTP ops get a Result.
|
|
575
|
+
// A path with :params (e.g. /channels/:id) needs a matching Input default
|
|
576
|
+
// so a plain "Run" (no scenario) doesn't crash the first node reading
|
|
577
|
+
// ctx.input.params.<name> — seed empty-string placeholders up front.
|
|
578
|
+
const pathParams = path ? parsePathParams(path) : [];
|
|
579
|
+
const inputConfig = pathParams.length > 0
|
|
580
|
+
? { defaults: { params: Object.fromEntries(pathParams.map((p) => [p, ''])) } }
|
|
581
|
+
: {};
|
|
582
|
+
const nodes = [{ id: 'input', type: 'Input', label: 'Input', position: { x: 0, y: 0 }, config: inputConfig }];
|
|
583
|
+
const edges = [];
|
|
584
|
+
const terminus = method ? 'response' : 'result';
|
|
585
|
+
nodes.push({ id: terminus, type: method ? 'Response' : 'Result', label: method ? 'Response' : 'Result', position: { x: 320, y: 0 }, config: {} });
|
|
586
|
+
edges.push({ id: 'input-' + terminus, source: 'input', target: terminus });
|
|
587
|
+
const flow = {
|
|
588
|
+
id,
|
|
589
|
+
name,
|
|
590
|
+
version: 1,
|
|
591
|
+
nodes,
|
|
592
|
+
edges,
|
|
593
|
+
createdAt: now,
|
|
594
|
+
updatedAt: now,
|
|
595
|
+
...(method && path ? { http: { method, path } } : {}),
|
|
596
|
+
};
|
|
597
|
+
const result = await client.createOperation(flow, id);
|
|
598
|
+
printJson(result.ok ? { ok: true, id, name, ...(method && path ? { http: { method, path } } : {}) } : { error: result.error });
|
|
599
|
+
return result.ok ? 0 : 1;
|
|
600
|
+
}
|
|
601
|
+
case 'rename': {
|
|
602
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
603
|
+
const [oldId, newId] = positional;
|
|
604
|
+
if (!oldId || !newId)
|
|
605
|
+
fail('Usage: rename <old-id> <new-id> [--name "<display>"]', 2);
|
|
606
|
+
const nameIdx = args.indexOf('--name');
|
|
607
|
+
const customName = nameIdx !== -1 ? args[nameIdx + 1] : undefined;
|
|
608
|
+
const flow = (await client.getWorkflow(oldId));
|
|
609
|
+
if (!flow)
|
|
610
|
+
fail(`Unknown operation: ${oldId}`, 1);
|
|
611
|
+
// The op id IS its apis path. Rename it AND bring the human-facing bits
|
|
612
|
+
// along: the display `name` (so the sidebar/title stops showing a vague
|
|
613
|
+
// auto-generated label) and the http path when it mirrored the old id.
|
|
614
|
+
const leaf = newId.split('/').filter(Boolean).pop() ?? newId;
|
|
615
|
+
const displayName = customName ?? titleCaseSlug(leaf);
|
|
616
|
+
const next = { ...flow, id: newId, name: displayName };
|
|
617
|
+
if (next.http && next.http.path === `/${oldId}`) {
|
|
618
|
+
next.http = { ...next.http, path: `/${newId}` };
|
|
619
|
+
}
|
|
620
|
+
const created = await client.createOperation(next, newId);
|
|
621
|
+
if (!created.ok) {
|
|
622
|
+
printJson({ error: `rename failed at create: ${created.error}` });
|
|
623
|
+
return 1;
|
|
624
|
+
}
|
|
625
|
+
const removed = await client.deleteOperation(oldId);
|
|
626
|
+
printJson({ ok: true, from: oldId, to: newId, name: displayName, httpPath: next.http?.path, oldRemoved: removed.ok });
|
|
627
|
+
return 0;
|
|
628
|
+
}
|
|
629
|
+
case 'samples': {
|
|
630
|
+
const nodeId = args[0];
|
|
631
|
+
if (!nodeId)
|
|
632
|
+
fail('Usage: samples <nodeId>', 2);
|
|
633
|
+
printJson(await client.samples(nodeId));
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
case 'test': {
|
|
637
|
+
let opId;
|
|
638
|
+
let environmentName;
|
|
639
|
+
let json = false;
|
|
640
|
+
let mock = false;
|
|
641
|
+
for (let i = 0; i < args.length; i++) {
|
|
642
|
+
const a = args[i];
|
|
643
|
+
if (a === '--environment') {
|
|
644
|
+
environmentName = args[++i];
|
|
645
|
+
}
|
|
646
|
+
else if (a === '--json') {
|
|
647
|
+
json = true;
|
|
648
|
+
}
|
|
649
|
+
else if (a === '--mock') {
|
|
650
|
+
mock = true;
|
|
651
|
+
}
|
|
652
|
+
else if (opId === undefined) {
|
|
653
|
+
opId = a;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const projectDir = process.env.EMBERFLOW_PROJECT
|
|
657
|
+
? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
|
|
658
|
+
: process.cwd();
|
|
659
|
+
let report;
|
|
660
|
+
try {
|
|
661
|
+
report = await runScenarioSuite({ projectDir, opId, environmentName, mock });
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
if (err instanceof ScenarioTestUsageError)
|
|
665
|
+
fail(err.message, 2);
|
|
666
|
+
throw err;
|
|
667
|
+
}
|
|
668
|
+
if (json) {
|
|
669
|
+
printJson(report.results);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
for (const r of report.results) {
|
|
673
|
+
const line = formatScenarioLine(r);
|
|
674
|
+
if (line)
|
|
675
|
+
process.stdout.write(`${line}\n`);
|
|
676
|
+
}
|
|
677
|
+
process.stdout.write(`${formatSummary(report)}\n`);
|
|
678
|
+
}
|
|
679
|
+
// CI foot-gun guard: a suite that asserted NOTHING still exits 0, but
|
|
680
|
+
// that green is vacuous — say so on stderr (stdout stays parseable).
|
|
681
|
+
if (report.passed + report.failed === 0) {
|
|
682
|
+
process.stderr.write(`warning: no scenarios asserted (${report.skipped} skipped)\n`);
|
|
683
|
+
}
|
|
684
|
+
return report.failed > 0 ? 1 : 0;
|
|
685
|
+
}
|
|
686
|
+
case 'doctor': {
|
|
687
|
+
let opId;
|
|
688
|
+
let fix = false;
|
|
689
|
+
for (const a of args) {
|
|
690
|
+
if (a === '--fix') {
|
|
691
|
+
fix = true;
|
|
692
|
+
}
|
|
693
|
+
else if (a.startsWith('--')) {
|
|
694
|
+
fail(`Unknown flag: ${a}. Usage: doctor [opId] [--fix]`, 2);
|
|
695
|
+
}
|
|
696
|
+
else if (opId === undefined) {
|
|
697
|
+
opId = a;
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
fail('Usage: doctor [opId] [--fix]', 2);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const projectDir = process.env.EMBERFLOW_PROJECT
|
|
704
|
+
? resolve(process.cwd(), process.env.EMBERFLOW_PROJECT)
|
|
705
|
+
: process.cwd();
|
|
706
|
+
let project;
|
|
707
|
+
try {
|
|
708
|
+
project = requireProjectWhenExplicit(await loadProjectConfig(projectDir), process.env.EMBERFLOW_PROJECT, projectDir);
|
|
709
|
+
}
|
|
710
|
+
catch (err) {
|
|
711
|
+
// Unreadable project (EMBERFLOW_PROJECT points at a dir with no
|
|
712
|
+
// emberflow.config.*) is a usage-shaped failure for `doctor`, not an
|
|
713
|
+
// internal error — exit 2 per the documented contract, not the
|
|
714
|
+
// generic-catch 1 in runCli.
|
|
715
|
+
fail(err instanceof Error ? err.message : String(err), 2);
|
|
716
|
+
}
|
|
717
|
+
const apiStore = buildApiStore(project);
|
|
718
|
+
const { validation: doctorRegistry } = buildRegistries(project);
|
|
719
|
+
const allOps = apiStore.list();
|
|
720
|
+
let targetOps = allOps;
|
|
721
|
+
if (opId !== undefined) {
|
|
722
|
+
const op = allOps.find((o) => o.id === opId);
|
|
723
|
+
if (!op)
|
|
724
|
+
fail(`Unknown operation: '${opId}'`, 2);
|
|
725
|
+
targetOps = [op];
|
|
726
|
+
}
|
|
727
|
+
let errors = 0;
|
|
728
|
+
let warnings = 0;
|
|
729
|
+
let info = 0;
|
|
730
|
+
const lines = [];
|
|
731
|
+
for (const op of targetOps) {
|
|
732
|
+
let flow = op;
|
|
733
|
+
lines.push(op.id);
|
|
734
|
+
if (fix) {
|
|
735
|
+
const relPath = apiStore.pathOf(op.id);
|
|
736
|
+
const { flow: seededFlow, seeded } = seedParamDefaults(op);
|
|
737
|
+
if (seeded.length > 0 && relPath) {
|
|
738
|
+
apiStore.save(seededFlow, relPath);
|
|
739
|
+
flow = apiStore.load(op.id) ?? seededFlow;
|
|
740
|
+
lines.push(`fixed ${op.id}: seeded params — ${seeded.join(', ')}`);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
flow = seededFlow;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const infraNodes = [];
|
|
747
|
+
const outputFieldsByNode = {};
|
|
748
|
+
const mutationSourcesByNode = {};
|
|
749
|
+
for (const n of flow.nodes) {
|
|
750
|
+
if (!doctorRegistry.has(n.type))
|
|
751
|
+
continue;
|
|
752
|
+
const { definition, implementation } = doctorRegistry.get(n.type);
|
|
753
|
+
const { traceKind } = definition;
|
|
754
|
+
if (traceKind === 'db' || traceKind === 'http' || traceKind === 'llm') {
|
|
755
|
+
infraNodes.push({ id: n.id, traceKind });
|
|
756
|
+
}
|
|
757
|
+
// Declared output fields for inputmap-schema-mismatch — only when the
|
|
758
|
+
// node's outputSchema actually declares fields (empty/absent → skip, so
|
|
759
|
+
// dynamic-output nodes like Input/Subflow never false-positive).
|
|
760
|
+
const fields = definition.outputSchema?.fields;
|
|
761
|
+
if (fields && fields.length > 0) {
|
|
762
|
+
outputFieldsByNode[n.id] = fields.map((f) => f.name);
|
|
763
|
+
}
|
|
764
|
+
// Implementation source for simulated-commit — only mutation nodes,
|
|
765
|
+
// and only when the implementation is a function (String() is always
|
|
766
|
+
// safe, but a native/bound stub's source never carries the marker).
|
|
767
|
+
if (definition.effects === 'mutation' && typeof implementation === 'function') {
|
|
768
|
+
mutationSourcesByNode[n.id] = String(implementation);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const diagnostics = diagnoseOperation(flow, flow.scenarios, {
|
|
772
|
+
infraNodes,
|
|
773
|
+
outputFieldsByNode,
|
|
774
|
+
mutationSourcesByNode,
|
|
775
|
+
languageDrift: project?.languageDrift,
|
|
776
|
+
});
|
|
777
|
+
for (const d of diagnostics) {
|
|
778
|
+
lines.push(` ${d.severity} ${d.code}: ${d.message}`);
|
|
779
|
+
if (d.severity === 'error')
|
|
780
|
+
errors++;
|
|
781
|
+
else if (d.severity === 'warning')
|
|
782
|
+
warnings++;
|
|
783
|
+
else
|
|
784
|
+
info++;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
lines.push(`${errors} errors, ${warnings} warnings, ${info} info across ${targetOps.length} operations`);
|
|
788
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
789
|
+
return errors > 0 ? 1 : 0;
|
|
790
|
+
}
|
|
791
|
+
default: {
|
|
792
|
+
process.stderr.write(USAGE);
|
|
793
|
+
return 2;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/** True when this module is the process entry point (run directly, not imported). */
|
|
798
|
+
const invokedDirectly = process.argv[1] !== undefined &&
|
|
799
|
+
realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
800
|
+
if (invokedDirectly) {
|
|
801
|
+
void runCli(process.argv.slice(2)).then((code) => {
|
|
802
|
+
process.exitCode = code;
|
|
803
|
+
});
|
|
804
|
+
}
|