@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,20 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Explicit in-flow auth check. Config carries the AuthPolicy fields (scheme,
|
|
4
|
+
* secretRef, verify?, header?). Reads the request off ctx.input (the
|
|
5
|
+
* operation's incoming { headers, ... }) and the secret off ctx.secrets,
|
|
6
|
+
* then reuses the same enforceAuth() the HTTP handler runs before the
|
|
7
|
+
* operation body (server/httpOperations.ts via server/auth/enforce.ts) so
|
|
8
|
+
* there is a single verify code path.
|
|
9
|
+
*
|
|
10
|
+
* Browser-safe: uses only src/engine (no server/ import), since src/nodes is
|
|
11
|
+
* bundled into the studio browser build (src/store/builderStore.ts). Custom
|
|
12
|
+
* verifiers registered server-side via project.registerVerifiers are a
|
|
13
|
+
* server-enforcement concern; this node only has the default bearer/apiKey
|
|
14
|
+
* verifiers (unless policy.verify happens to name one of those).
|
|
15
|
+
*
|
|
16
|
+
* Pure-read: throws HttpError(401/500) on failure, otherwise returns
|
|
17
|
+
* { user } for downstream nodes to see. No side effects, so no
|
|
18
|
+
* `effects: 'mutation'`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerRequireAuthNode(registry: NodeRegistry): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createDefaultVerifierRegistry, enforceAuth } from '../engine/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Explicit in-flow auth check. Config carries the AuthPolicy fields (scheme,
|
|
4
|
+
* secretRef, verify?, header?). Reads the request off ctx.input (the
|
|
5
|
+
* operation's incoming { headers, ... }) and the secret off ctx.secrets,
|
|
6
|
+
* then reuses the same enforceAuth() the HTTP handler runs before the
|
|
7
|
+
* operation body (server/httpOperations.ts via server/auth/enforce.ts) so
|
|
8
|
+
* there is a single verify code path.
|
|
9
|
+
*
|
|
10
|
+
* Browser-safe: uses only src/engine (no server/ import), since src/nodes is
|
|
11
|
+
* bundled into the studio browser build (src/store/builderStore.ts). Custom
|
|
12
|
+
* verifiers registered server-side via project.registerVerifiers are a
|
|
13
|
+
* server-enforcement concern; this node only has the default bearer/apiKey
|
|
14
|
+
* verifiers (unless policy.verify happens to name one of those).
|
|
15
|
+
*
|
|
16
|
+
* Pure-read: throws HttpError(401/500) on failure, otherwise returns
|
|
17
|
+
* { user } for downstream nodes to see. No side effects, so no
|
|
18
|
+
* `effects: 'mutation'`.
|
|
19
|
+
*/
|
|
20
|
+
export function registerRequireAuthNode(registry) {
|
|
21
|
+
registry.register({
|
|
22
|
+
type: 'requireAuth',
|
|
23
|
+
label: 'Require Auth',
|
|
24
|
+
description: 'Verifies the incoming request against an auth policy (bearer/apiKey shared-secret, or a named custom verifier) and attaches { user } for downstream nodes. Throws HttpError(401) on failure, HttpError(500) if misconfigured.',
|
|
25
|
+
simpleDescription: 'Checks the request is authorized before continuing',
|
|
26
|
+
category: 'http',
|
|
27
|
+
traceKind: 'compute',
|
|
28
|
+
tags: ['http', 'auth'],
|
|
29
|
+
configSchema: {
|
|
30
|
+
fields: [
|
|
31
|
+
{ name: 'scheme', type: 'string' },
|
|
32
|
+
{ name: 'secretRef', type: 'string' },
|
|
33
|
+
{ name: 'verify', type: 'string' },
|
|
34
|
+
{ name: 'header', type: 'string' },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
outputSchema: {
|
|
38
|
+
fields: [{ name: 'user', type: 'object' }],
|
|
39
|
+
},
|
|
40
|
+
}, async (ctx) => {
|
|
41
|
+
const policy = ctx.config;
|
|
42
|
+
const input = (ctx.input ?? {});
|
|
43
|
+
const request = { headers: input.headers ?? {} };
|
|
44
|
+
const verifiers = createDefaultVerifierRegistry();
|
|
45
|
+
const { user } = enforceAuth({ policy, request, secrets: ctx.secrets, verifiers });
|
|
46
|
+
return { user };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Terminal node for an HTTP operation: its input `{ status, body }` becomes
|
|
4
|
+
* the HTTP response `extractResponse` (server/operationResult.ts) maps onto
|
|
5
|
+
* the wire. `status` defaults to 200 when the input omits it. Pure — no
|
|
6
|
+
* side effects, so no `effects: 'mutation'` declaration.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerResponseNodes(registry: NodeRegistry): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal node for an HTTP operation: its input `{ status, body }` becomes
|
|
3
|
+
* the HTTP response `extractResponse` (server/operationResult.ts) maps onto
|
|
4
|
+
* the wire. `status` defaults to 200 when the input omits it. Pure — no
|
|
5
|
+
* side effects, so no `effects: 'mutation'` declaration.
|
|
6
|
+
*/
|
|
7
|
+
export function registerResponseNodes(registry) {
|
|
8
|
+
registry.register({
|
|
9
|
+
type: 'Response',
|
|
10
|
+
label: 'Response',
|
|
11
|
+
description: 'Terminal node for an HTTP operation: its input { status, body } becomes the HTTP response. Omit status to default to 200.',
|
|
12
|
+
simpleDescription: 'Shapes the HTTP response for this operation',
|
|
13
|
+
category: 'http',
|
|
14
|
+
traceKind: 'compute',
|
|
15
|
+
tags: ['http'],
|
|
16
|
+
inputSchema: {
|
|
17
|
+
fields: [
|
|
18
|
+
{ name: 'status', type: 'number' },
|
|
19
|
+
{ name: 'body', type: 'object' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
outputSchema: {
|
|
23
|
+
fields: [
|
|
24
|
+
{ name: 'status', type: 'number' },
|
|
25
|
+
{ name: 'body', type: 'object' },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
}, async (ctx) => {
|
|
29
|
+
const input = (ctx.input ?? {});
|
|
30
|
+
const status = typeof input.status === 'number' ? input.status : 200;
|
|
31
|
+
return { status, body: input.body };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine/index.js';
|
|
2
|
+
export declare function describeWeatherCode(code: number): string;
|
|
3
|
+
export interface AdvisoryInput {
|
|
4
|
+
place: string;
|
|
5
|
+
temperatureC: number;
|
|
6
|
+
windKmh: number;
|
|
7
|
+
weatherCode: number;
|
|
8
|
+
maxC: number;
|
|
9
|
+
minC: number;
|
|
10
|
+
rainChancePct: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildAdvisory(input: AdvisoryInput): {
|
|
13
|
+
place: string;
|
|
14
|
+
temperatureC: number;
|
|
15
|
+
condition: string;
|
|
16
|
+
headline: string;
|
|
17
|
+
range: string;
|
|
18
|
+
advice: string;
|
|
19
|
+
};
|
|
20
|
+
export type WeatherSeverity = 'severe' | 'mild';
|
|
21
|
+
/**
|
|
22
|
+
* Pure severity rule: severe when wind is at gale strength (≥ 40 km/h) or rain
|
|
23
|
+
* is likely (≥ 70% chance), otherwise mild.
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifyWeather(windKmh: number, rainChancePct: number): {
|
|
26
|
+
severity: WeatherSeverity;
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Real-infrastructure nodes backed by the Open-Meteo public APIs
|
|
31
|
+
* (keyless, CORS-enabled): geocoding + weather forecast.
|
|
32
|
+
*/
|
|
33
|
+
export declare function registerWeatherNodes(registry: NodeRegistry): void;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const WEATHER_CODES = {
|
|
2
|
+
0: 'clear sky',
|
|
3
|
+
1: 'mainly clear',
|
|
4
|
+
2: 'partly cloudy',
|
|
5
|
+
3: 'overcast',
|
|
6
|
+
45: 'fog',
|
|
7
|
+
48: 'rime fog',
|
|
8
|
+
51: 'light drizzle',
|
|
9
|
+
53: 'drizzle',
|
|
10
|
+
55: 'dense drizzle',
|
|
11
|
+
61: 'light rain',
|
|
12
|
+
63: 'rain',
|
|
13
|
+
65: 'heavy rain',
|
|
14
|
+
71: 'light snow',
|
|
15
|
+
73: 'snow',
|
|
16
|
+
75: 'heavy snow',
|
|
17
|
+
80: 'rain showers',
|
|
18
|
+
81: 'rain showers',
|
|
19
|
+
82: 'violent rain showers',
|
|
20
|
+
95: 'thunderstorm',
|
|
21
|
+
96: 'thunderstorm with hail',
|
|
22
|
+
99: 'thunderstorm with heavy hail',
|
|
23
|
+
};
|
|
24
|
+
export function describeWeatherCode(code) {
|
|
25
|
+
return WEATHER_CODES[code] ?? `unknown conditions (code ${code})`;
|
|
26
|
+
}
|
|
27
|
+
export function buildAdvisory(input) {
|
|
28
|
+
const condition = describeWeatherCode(input.weatherCode);
|
|
29
|
+
const advice = [];
|
|
30
|
+
if (input.rainChancePct >= 50)
|
|
31
|
+
advice.push('take an umbrella');
|
|
32
|
+
if (input.windKmh >= 30)
|
|
33
|
+
advice.push('expect strong wind');
|
|
34
|
+
if (input.temperatureC <= 5)
|
|
35
|
+
advice.push('dress warm');
|
|
36
|
+
if (input.temperatureC >= 28)
|
|
37
|
+
advice.push('stay hydrated');
|
|
38
|
+
if (advice.length === 0)
|
|
39
|
+
advice.push('no precautions needed');
|
|
40
|
+
return {
|
|
41
|
+
place: input.place,
|
|
42
|
+
temperatureC: input.temperatureC,
|
|
43
|
+
condition,
|
|
44
|
+
headline: `${input.place}: ${Math.round(input.temperatureC)}°C, ${condition}`,
|
|
45
|
+
range: `${Math.round(input.minC)}–${Math.round(input.maxC)}°C today, ${Math.round(input.rainChancePct)}% rain chance`,
|
|
46
|
+
advice: advice.join('; '),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Pure severity rule: severe when wind is at gale strength (≥ 40 km/h) or rain
|
|
51
|
+
* is likely (≥ 70% chance), otherwise mild.
|
|
52
|
+
*/
|
|
53
|
+
export function classifyWeather(windKmh, rainChancePct) {
|
|
54
|
+
if (windKmh >= 40) {
|
|
55
|
+
return { severity: 'severe', reason: `high winds at ${Math.round(windKmh)} km/h` };
|
|
56
|
+
}
|
|
57
|
+
if (rainChancePct >= 70) {
|
|
58
|
+
return { severity: 'severe', reason: `${Math.round(rainChancePct)}% chance of rain` };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
severity: 'mild',
|
|
62
|
+
reason: `winds ${Math.round(windKmh)} km/h, ${Math.round(rainChancePct)}% rain chance`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Real-infrastructure nodes backed by the Open-Meteo public APIs
|
|
67
|
+
* (keyless, CORS-enabled): geocoding + weather forecast.
|
|
68
|
+
*/
|
|
69
|
+
export function registerWeatherNodes(registry) {
|
|
70
|
+
registry.register({
|
|
71
|
+
type: 'GeocodeCity',
|
|
72
|
+
label: 'Geocode City',
|
|
73
|
+
description: 'Resolves a city name to coordinates via the Open-Meteo geocoding API.',
|
|
74
|
+
category: 'http',
|
|
75
|
+
traceKind: 'http',
|
|
76
|
+
traceDetail: 'GET https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1',
|
|
77
|
+
// city is input-drivable (e.g. from an Input node); config is the fallback.
|
|
78
|
+
inputSchema: {
|
|
79
|
+
fields: [{ name: 'city', type: 'string' }],
|
|
80
|
+
},
|
|
81
|
+
configSchema: {
|
|
82
|
+
fields: [{ name: 'city', type: 'string', required: true }],
|
|
83
|
+
},
|
|
84
|
+
outputSchema: {
|
|
85
|
+
fields: [
|
|
86
|
+
{ name: 'place', type: 'string' },
|
|
87
|
+
{ name: 'country', type: 'string' },
|
|
88
|
+
{ name: 'latitude', type: 'number' },
|
|
89
|
+
{ name: 'longitude', type: 'number' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
}, async (ctx) => {
|
|
93
|
+
const city = String(ctx.input.city ?? '');
|
|
94
|
+
if (!city)
|
|
95
|
+
throw new Error('No city configured');
|
|
96
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
|
|
97
|
+
ctx.log('info', `GET ${url}`);
|
|
98
|
+
const response = await fetch(url);
|
|
99
|
+
if (!response.ok)
|
|
100
|
+
throw new Error(`Geocoding failed: HTTP ${response.status}`);
|
|
101
|
+
const data = (await response.json());
|
|
102
|
+
const hit = data.results?.[0];
|
|
103
|
+
if (!hit)
|
|
104
|
+
throw new Error(`No location found for "${city}"`);
|
|
105
|
+
ctx.log('info', `Resolved ${hit.name}, ${hit.country} → ${hit.latitude}, ${hit.longitude}`);
|
|
106
|
+
return {
|
|
107
|
+
place: hit.name,
|
|
108
|
+
country: hit.country,
|
|
109
|
+
latitude: hit.latitude,
|
|
110
|
+
longitude: hit.longitude,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
registry.register({
|
|
114
|
+
type: 'FetchForecast',
|
|
115
|
+
label: 'Fetch Forecast',
|
|
116
|
+
description: 'Fetches live current conditions and today’s outlook from the Open-Meteo forecast API.',
|
|
117
|
+
category: 'http',
|
|
118
|
+
traceKind: 'http',
|
|
119
|
+
traceDetail: 'GET https://api.open-meteo.com/v1/forecast?latitude&longitude¤t=temperature_2m,wind_speed_10m,weather_code&daily=…&forecast_days=1',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
fields: [
|
|
122
|
+
{ name: 'latitude', type: 'number', required: true },
|
|
123
|
+
{ name: 'longitude', type: 'number', required: true },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
outputSchema: {
|
|
127
|
+
fields: [
|
|
128
|
+
{ name: 'temperatureC', type: 'number' },
|
|
129
|
+
{ name: 'windKmh', type: 'number' },
|
|
130
|
+
{ name: 'weatherCode', type: 'number' },
|
|
131
|
+
{ name: 'maxC', type: 'number' },
|
|
132
|
+
{ name: 'minC', type: 'number' },
|
|
133
|
+
{ name: 'rainChancePct', type: 'number' },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
}, async (ctx) => {
|
|
137
|
+
const latitude = Number(ctx.input.latitude);
|
|
138
|
+
const longitude = Number(ctx.input.longitude);
|
|
139
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
140
|
+
throw new Error('latitude/longitude missing or not numbers');
|
|
141
|
+
}
|
|
142
|
+
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}` +
|
|
143
|
+
'¤t=temperature_2m,wind_speed_10m,weather_code' +
|
|
144
|
+
'&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max' +
|
|
145
|
+
'&timezone=auto&forecast_days=1';
|
|
146
|
+
ctx.log('info', `GET ${url}`);
|
|
147
|
+
const response = await fetch(url);
|
|
148
|
+
if (!response.ok)
|
|
149
|
+
throw new Error(`Forecast failed: HTTP ${response.status}`);
|
|
150
|
+
const data = (await response.json());
|
|
151
|
+
const output = {
|
|
152
|
+
temperatureC: data.current.temperature_2m,
|
|
153
|
+
windKmh: data.current.wind_speed_10m,
|
|
154
|
+
weatherCode: data.current.weather_code,
|
|
155
|
+
maxC: data.daily.temperature_2m_max[0],
|
|
156
|
+
minC: data.daily.temperature_2m_min[0],
|
|
157
|
+
rainChancePct: data.daily.precipitation_probability_max[0] ?? 0,
|
|
158
|
+
};
|
|
159
|
+
ctx.log('info', `Now ${output.temperatureC}°C, wind ${output.windKmh} km/h, code ${output.weatherCode}`);
|
|
160
|
+
return output;
|
|
161
|
+
});
|
|
162
|
+
registry.register({
|
|
163
|
+
type: 'SummarizeConditions',
|
|
164
|
+
label: 'Summarize Conditions',
|
|
165
|
+
description: 'Turns raw forecast numbers into a readable weather advisory.',
|
|
166
|
+
category: 'logic',
|
|
167
|
+
traceKind: 'compute',
|
|
168
|
+
inputSchema: {
|
|
169
|
+
fields: [
|
|
170
|
+
{ name: 'place', type: 'string', required: true },
|
|
171
|
+
{ name: 'temperatureC', type: 'number', required: true },
|
|
172
|
+
{ name: 'windKmh', type: 'number', required: true },
|
|
173
|
+
{ name: 'weatherCode', type: 'number', required: true },
|
|
174
|
+
{ name: 'maxC', type: 'number' },
|
|
175
|
+
{ name: 'minC', type: 'number' },
|
|
176
|
+
{ name: 'rainChancePct', type: 'number' },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
outputSchema: {
|
|
180
|
+
fields: [
|
|
181
|
+
{ name: 'headline', type: 'string' },
|
|
182
|
+
{ name: 'range', type: 'string' },
|
|
183
|
+
{ name: 'advice', type: 'string' },
|
|
184
|
+
{ name: 'place', type: 'string' },
|
|
185
|
+
{ name: 'temperatureC', type: 'number' },
|
|
186
|
+
{ name: 'condition', type: 'string' },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
}, async (ctx) => {
|
|
190
|
+
const advisory = buildAdvisory({
|
|
191
|
+
place: String(ctx.input.place ?? 'Unknown'),
|
|
192
|
+
temperatureC: Number(ctx.input.temperatureC),
|
|
193
|
+
windKmh: Number(ctx.input.windKmh),
|
|
194
|
+
weatherCode: Number(ctx.input.weatherCode),
|
|
195
|
+
maxC: Number(ctx.input.maxC ?? ctx.input.temperatureC),
|
|
196
|
+
minC: Number(ctx.input.minC ?? ctx.input.temperatureC),
|
|
197
|
+
rainChancePct: Number(ctx.input.rainChancePct ?? 0),
|
|
198
|
+
});
|
|
199
|
+
ctx.log('info', advisory.headline);
|
|
200
|
+
ctx.log('info', `Advice: ${advisory.advice}`);
|
|
201
|
+
return advisory;
|
|
202
|
+
});
|
|
203
|
+
registry.register({
|
|
204
|
+
type: 'ClassifyWeather',
|
|
205
|
+
label: 'Classify Weather',
|
|
206
|
+
description: 'Grades conditions as severe or mild from wind and rain thresholds.',
|
|
207
|
+
category: 'logic',
|
|
208
|
+
traceKind: 'compute',
|
|
209
|
+
tags: ['branching'],
|
|
210
|
+
inputSchema: {
|
|
211
|
+
fields: [
|
|
212
|
+
{ name: 'windKmh', type: 'number', required: true },
|
|
213
|
+
{ name: 'rainChancePct', type: 'number', required: true },
|
|
214
|
+
// Optional passthrough so fan-out consumers (e.g. a Collect
|
|
215
|
+
// gathering one verdict per city) keep the place name attached.
|
|
216
|
+
{ name: 'place', type: 'string' },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
outputSchema: {
|
|
220
|
+
fields: [
|
|
221
|
+
{ name: 'severity', type: 'enum', enumValues: ['severe', 'mild'] },
|
|
222
|
+
{ name: 'reason', type: 'string' },
|
|
223
|
+
{ name: 'place', type: 'string' },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
}, async (ctx) => {
|
|
227
|
+
const { severity, reason } = classifyWeather(Number(ctx.input.windKmh), Number(ctx.input.rainChancePct));
|
|
228
|
+
ctx.log('info', `Severity ${severity}: ${reason}`);
|
|
229
|
+
return {
|
|
230
|
+
severity,
|
|
231
|
+
reason,
|
|
232
|
+
...(ctx.input.place !== undefined ? { place: ctx.input.place } : {}),
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
registry.register({
|
|
236
|
+
type: 'ComposeAlert',
|
|
237
|
+
label: 'Compose Alert',
|
|
238
|
+
description: 'Formats a severe-weather alert line for a place.',
|
|
239
|
+
category: 'logic',
|
|
240
|
+
traceKind: 'compute',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
fields: [
|
|
243
|
+
{ name: 'place', type: 'string', required: true },
|
|
244
|
+
{ name: 'reason', type: 'string', required: true },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
outputSchema: {
|
|
248
|
+
fields: [
|
|
249
|
+
{ name: 'alert', type: 'string' },
|
|
250
|
+
{ name: 'place', type: 'string' },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
}, async (ctx) => {
|
|
254
|
+
const place = String(ctx.input.place ?? 'the area');
|
|
255
|
+
const reason = String(ctx.input.reason ?? 'conditions worsening');
|
|
256
|
+
const alert = `⚠ Weather alert for ${place}: ${reason}`;
|
|
257
|
+
ctx.log('info', alert);
|
|
258
|
+
return { alert, place };
|
|
259
|
+
});
|
|
260
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xdelivered/emberflow",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Visual builder and runner for API operations modelled as node graphs, with scenarios, mocks and agent skills.",
|
|
6
|
+
"keywords": ["api", "workflow", "node-graph", "low-code", "visual-builder", "agent-skills"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"publishConfig": { "access": "public" },
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/pmccurley87/emberflow.git"
|
|
12
|
+
},
|
|
13
|
+
"bin": { "emberflow": "bin/emberflow.mjs" },
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/src/config.d.ts",
|
|
17
|
+
"default": "./dist/src/config.js"
|
|
18
|
+
},
|
|
19
|
+
"./engine": {
|
|
20
|
+
"types": "./dist/src/engine/index.d.ts",
|
|
21
|
+
"default": "./dist/src/engine/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./server/environments": {
|
|
24
|
+
"types": "./dist/server/environments.d.ts",
|
|
25
|
+
"default": "./dist/server/environments.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": ["bin", "server", "src", "dist", "studio-dist", "templates", "README.md"],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "vite",
|
|
31
|
+
"build": "tsc -b && vite build",
|
|
32
|
+
"build:studio": "vite build",
|
|
33
|
+
"build:lib": "tsc -p tsconfig.build.json && node scripts/fix-dist-imports.mjs dist",
|
|
34
|
+
"prepack": "npm run build:studio && npm run build:lib",
|
|
35
|
+
"prepare": "node scripts/prepare-if-source.mjs",
|
|
36
|
+
"lint": "oxlint",
|
|
37
|
+
"typecheck": "tsc -b && tsc --noEmit -p server/tsconfig.json",
|
|
38
|
+
"preview": "vite preview",
|
|
39
|
+
"server": "tsx watch server/index.ts",
|
|
40
|
+
"dev:project": "EMBERFLOW_PROJECT=examples/demo-project tsx watch server/index.ts",
|
|
41
|
+
"cli": "tsx server/cli.ts",
|
|
42
|
+
"mcp": "tsx server/mcp.ts",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"smoke": "bash scripts/smoke-install.sh"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
48
|
+
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
|
+
"@radix-ui/react-dialog": "^1.1.18",
|
|
51
|
+
"@radix-ui/react-popover": "^1.1.18",
|
|
52
|
+
"@radix-ui/react-slot": "^1.3.0",
|
|
53
|
+
"@radix-ui/react-tabs": "^1.1.16",
|
|
54
|
+
"@tailwindcss/vite": "^4.3.2",
|
|
55
|
+
"@types/suncalc": "^1.9.2",
|
|
56
|
+
"class-variance-authority": "^0.7.1",
|
|
57
|
+
"clsx": "^2.1.1",
|
|
58
|
+
"cmdk": "^1.1.1",
|
|
59
|
+
"express": "^5.2.1",
|
|
60
|
+
"lucide-react": "^1.23.0",
|
|
61
|
+
"pg": "^8.22.0",
|
|
62
|
+
"react": "^19.2.7",
|
|
63
|
+
"react-dom": "^19.2.7",
|
|
64
|
+
"react-resizable-panels": "^2.1.9",
|
|
65
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
66
|
+
"suncalc": "^2.0.0",
|
|
67
|
+
"tailwind-merge": "^3.6.0",
|
|
68
|
+
"tailwindcss": "^4.3.2",
|
|
69
|
+
"tw-animate-css": "^1.4.0",
|
|
70
|
+
"zustand": "^5.0.14"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"tsx": "^4.22.5"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"tsx": { "optional": true }
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@types/express": "^5.0.6",
|
|
80
|
+
"@types/node": "^24.13.2",
|
|
81
|
+
"@types/pg": "^8.20.0",
|
|
82
|
+
"@types/react": "^19.2.17",
|
|
83
|
+
"@types/react-dom": "^19.2.3",
|
|
84
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
85
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
86
|
+
"oxlint": "^1.71.0",
|
|
87
|
+
"tsx": "^4.22.5",
|
|
88
|
+
"typescript": "~6.0.2",
|
|
89
|
+
"vite": "^8.1.1",
|
|
90
|
+
"vitest": "^4.1.9"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
4
|
+
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
// Task 2 review finding: the `setup-environments` agent intent (route
|
|
9
|
+
// validation added in server/index.ts, ~L712) shipped without a test that
|
|
10
|
+
// boots a real runner and drives POST /agent end to end. This covers the
|
|
11
|
+
// accept path (no flowId/environment required) and the two reject paths
|
|
12
|
+
// (missing instruction; an action outside the allowlist), against a real
|
|
13
|
+
// runner subprocess the way server/mockServe.test.ts does.
|
|
14
|
+
|
|
15
|
+
const PORT = 8151;
|
|
16
|
+
const base = `http://127.0.0.1:${PORT}`;
|
|
17
|
+
|
|
18
|
+
async function waitHealthy(url: string, tries = 40): Promise<void> {
|
|
19
|
+
for (let i = 0; i < tries; i++) {
|
|
20
|
+
try {
|
|
21
|
+
if ((await fetch(url)).ok) return;
|
|
22
|
+
} catch {
|
|
23
|
+
/* not up yet */
|
|
24
|
+
}
|
|
25
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
26
|
+
}
|
|
27
|
+
throw new Error('runner did not become healthy');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function git(dir: string, args: string[]): void {
|
|
31
|
+
execFileSync('git', args, { cwd: dir, stdio: 'ignore' });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let proc: ChildProcess;
|
|
35
|
+
let projectDir: string;
|
|
36
|
+
let codexStubPath: string;
|
|
37
|
+
|
|
38
|
+
async function postAgent(body: unknown): Promise<Response> {
|
|
39
|
+
return fetch(`${base}/agent`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(body),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
projectDir = mkdtempSync(join(tmpdir(), 'agentroute-'));
|
|
48
|
+
mkdirSync(join(projectDir, 'emberflow', 'apis', 'default'), { recursive: true });
|
|
49
|
+
writeFileSync(join(projectDir, 'emberflow.config.mjs'), 'export default {};\n');
|
|
50
|
+
// Minimal real operation so ask-with-flowId can resolve a relPath.
|
|
51
|
+
writeFileSync(
|
|
52
|
+
join(projectDir, 'emberflow', 'apis', 'default', 'hello.json'),
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
id: 'default/hello',
|
|
55
|
+
name: 'Hello',
|
|
56
|
+
version: 1,
|
|
57
|
+
nodes: [
|
|
58
|
+
{ id: 'in', type: 'Input', label: 'Input', position: { x: 0, y: 0 }, config: {} },
|
|
59
|
+
{ id: 'out', type: 'Result', label: 'Result', position: { x: 300, y: 0 }, config: {}, inputMap: { value: { sourceNodeId: 'in', sourceField: '$' } } },
|
|
60
|
+
],
|
|
61
|
+
edges: [{ id: 'e1', source: 'in', target: 'out' }],
|
|
62
|
+
createdAt: '2026-07-12T00:00:00Z',
|
|
63
|
+
updatedAt: '2026-07-12T00:00:00Z',
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// AgentRunManager.start() requires a git repo (isGitRepo check) before it
|
|
68
|
+
// will snapshot + spawn — give the fixture project one, with an initial
|
|
69
|
+
// commit so the snapshot has something to diff against.
|
|
70
|
+
git(projectDir, ['init']);
|
|
71
|
+
git(projectDir, ['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'add', '-A']);
|
|
72
|
+
git(projectDir, ['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'init']);
|
|
73
|
+
|
|
74
|
+
// A harmless stand-in for the codex CLI: an accepted setup-environments
|
|
75
|
+
// intent spawns the coding agent for real, so without this the test would
|
|
76
|
+
// shell out to an actual `codex` binary. EMBERFLOW_CODEX_BIN (consumed by
|
|
77
|
+
// AgentRunManager.start(), server/agents/runManager.ts) overrides the bin
|
|
78
|
+
// passed to spawnCodex (server/agents/codexAdapter.ts), which spawns it
|
|
79
|
+
// directly with codex's CLI args and reads its stdout as JSON lines. Exiting
|
|
80
|
+
// 0 with no stdout is enough for the adapter to reach a clean 'done'/'error'
|
|
81
|
+
// terminal state without any real agent behavior.
|
|
82
|
+
codexStubPath = join(projectDir, 'fake-codex.sh');
|
|
83
|
+
writeFileSync(codexStubPath, '#!/bin/sh\nexit 0\n');
|
|
84
|
+
chmodSync(codexStubPath, 0o755);
|
|
85
|
+
|
|
86
|
+
proc = spawn('npx', ['tsx', 'server/index.ts'], {
|
|
87
|
+
env: {
|
|
88
|
+
...process.env,
|
|
89
|
+
EMBERFLOW_RUNNER_PORT: String(PORT),
|
|
90
|
+
EMBERFLOW_PROJECT: projectDir,
|
|
91
|
+
EMBERFLOW_CODEX_BIN: codexStubPath,
|
|
92
|
+
},
|
|
93
|
+
stdio: 'ignore',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await waitHealthy(`${base}/healthz`);
|
|
97
|
+
}, 20_000);
|
|
98
|
+
|
|
99
|
+
afterAll(() => {
|
|
100
|
+
proc?.kill();
|
|
101
|
+
if (projectDir) rmSync(projectDir, { recursive: true, force: true });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('POST /agent — setup-environments route validation', () => {
|
|
105
|
+
it('accepts a setup-environments intent WITHOUT flowId or environment', async () => {
|
|
106
|
+
const res = await postAgent({ intent: { action: 'setup-environments', instruction: 'dev and prod' } });
|
|
107
|
+
expect(res.status).toBe(201);
|
|
108
|
+
const body = await res.json();
|
|
109
|
+
expect(typeof body.agentRunId).toBe('string');
|
|
110
|
+
expect(body.agentRunId.length).toBeGreaterThan(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('400s a setup-environments intent with no instruction', async () => {
|
|
114
|
+
const res = await postAgent({ intent: { action: 'setup-environments' } });
|
|
115
|
+
expect(res.status).toBe(400);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('400s an action outside the allowlist, mentioning the unsupported action', async () => {
|
|
119
|
+
const res = await postAgent({ intent: { action: 'nonsense', instruction: 'x' } });
|
|
120
|
+
expect(res.status).toBe(400);
|
|
121
|
+
const body = await res.json();
|
|
122
|
+
expect(typeof body.error).toBe('string');
|
|
123
|
+
expect(body.error).toContain('nonsense');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/** The run manager is single-flight per project (409 while a previous stub run
|
|
128
|
+
* winds down) — retry briefly so route-validation asserts don't race it. */
|
|
129
|
+
async function postAgentWhenFree(body: unknown): Promise<Response> {
|
|
130
|
+
for (let i = 0; i < 40; i++) {
|
|
131
|
+
const res = await postAgent(body);
|
|
132
|
+
if (res.status !== 409) return res;
|
|
133
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
134
|
+
}
|
|
135
|
+
return postAgent(body);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
describe('POST /agent — ask route validation', () => {
|
|
139
|
+
it('accepts an ask intent WITHOUT flowId', async () => {
|
|
140
|
+
const res = await postAgentWhenFree({ intent: { action: 'ask', instruction: 'where is the slack rendering?' } });
|
|
141
|
+
expect(res.status).toBe(201);
|
|
142
|
+
const body = await res.json();
|
|
143
|
+
expect(typeof body.agentRunId).toBe('string');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('accepts an ask intent WITH a flowId', async () => {
|
|
147
|
+
const res = await postAgentWhenFree({ intent: { action: 'ask', flowId: 'default/hello', instruction: 'what does this op do?' } });
|
|
148
|
+
expect(res.status).toBe(201);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('400s an ask intent with a non-string flowId', async () => {
|
|
152
|
+
const res = await postAgent({ intent: { action: 'ask', flowId: 42, instruction: 'x' } });
|
|
153
|
+
expect(res.status).toBe(400);
|
|
154
|
+
const body = await res.json();
|
|
155
|
+
expect(body.error).toContain('flowId');
|
|
156
|
+
});
|
|
157
|
+
});
|