@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,75 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createDefaultRegistry } from './index';
|
|
3
|
+
import { isHttpError } from '../engine';
|
|
4
|
+
|
|
5
|
+
describe('requireAuth node', () => {
|
|
6
|
+
it('is registered with type requireAuth', () => {
|
|
7
|
+
expect(createDefaultRegistry().get('requireAuth').definition.type).toBe('requireAuth');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('returns { user } for a valid bearer token', async () => {
|
|
11
|
+
const impl = createDefaultRegistry().get('requireAuth').implementation;
|
|
12
|
+
const ctx = {
|
|
13
|
+
input: { headers: { authorization: 'Bearer good' } },
|
|
14
|
+
config: { scheme: 'bearer', secretRef: 'T' },
|
|
15
|
+
secrets: { T: 'good' },
|
|
16
|
+
vars: {},
|
|
17
|
+
safeMode: false,
|
|
18
|
+
runInput: {},
|
|
19
|
+
log: () => {},
|
|
20
|
+
} as never;
|
|
21
|
+
expect(await impl(ctx)).toEqual({ user: { scheme: 'bearer' } });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('throws HttpError 401 for a missing/wrong token', async () => {
|
|
25
|
+
const impl = createDefaultRegistry().get('requireAuth').implementation;
|
|
26
|
+
const ctx = {
|
|
27
|
+
input: { headers: {} },
|
|
28
|
+
config: { scheme: 'bearer', secretRef: 'T' },
|
|
29
|
+
secrets: { T: 'good' },
|
|
30
|
+
vars: {},
|
|
31
|
+
safeMode: false,
|
|
32
|
+
runInput: {},
|
|
33
|
+
log: () => {},
|
|
34
|
+
} as never;
|
|
35
|
+
try {
|
|
36
|
+
await impl(ctx);
|
|
37
|
+
expect.unreachable();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
expect(isHttpError(e) && e.status).toBe(401);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('throws HttpError 500 when the secret is unset (fail closed)', async () => {
|
|
44
|
+
const impl = createDefaultRegistry().get('requireAuth').implementation;
|
|
45
|
+
const ctx = {
|
|
46
|
+
input: { headers: { authorization: 'Bearer x' } },
|
|
47
|
+
config: { scheme: 'bearer', secretRef: 'T' },
|
|
48
|
+
secrets: {},
|
|
49
|
+
vars: {},
|
|
50
|
+
safeMode: false,
|
|
51
|
+
runInput: {},
|
|
52
|
+
log: () => {},
|
|
53
|
+
} as never;
|
|
54
|
+
try {
|
|
55
|
+
await impl(ctx);
|
|
56
|
+
expect.unreachable();
|
|
57
|
+
} catch (e) {
|
|
58
|
+
expect(isHttpError(e) && e.status).toBe(500);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('supports the apiKey scheme with a custom header', async () => {
|
|
63
|
+
const impl = createDefaultRegistry().get('requireAuth').implementation;
|
|
64
|
+
const ctx = {
|
|
65
|
+
input: { headers: { 'x-svc-key': 'k' } },
|
|
66
|
+
config: { scheme: 'apiKey', secretRef: 'K', header: 'x-svc-key' },
|
|
67
|
+
secrets: { K: 'k' },
|
|
68
|
+
vars: {},
|
|
69
|
+
safeMode: false,
|
|
70
|
+
runInput: {},
|
|
71
|
+
log: () => {},
|
|
72
|
+
} as never;
|
|
73
|
+
expect(await impl(ctx)).toEqual({ user: { scheme: 'apiKey' } });
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine';
|
|
2
|
+
import { createDefaultVerifierRegistry, enforceAuth } from '../engine';
|
|
3
|
+
import type { AuthPolicy } from '../engine';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Explicit in-flow auth check. Config carries the AuthPolicy fields (scheme,
|
|
7
|
+
* secretRef, verify?, header?). Reads the request off ctx.input (the
|
|
8
|
+
* operation's incoming { headers, ... }) and the secret off ctx.secrets,
|
|
9
|
+
* then reuses the same enforceAuth() the HTTP handler runs before the
|
|
10
|
+
* operation body (server/httpOperations.ts via server/auth/enforce.ts) so
|
|
11
|
+
* there is a single verify code path.
|
|
12
|
+
*
|
|
13
|
+
* Browser-safe: uses only src/engine (no server/ import), since src/nodes is
|
|
14
|
+
* bundled into the studio browser build (src/store/builderStore.ts). Custom
|
|
15
|
+
* verifiers registered server-side via project.registerVerifiers are a
|
|
16
|
+
* server-enforcement concern; this node only has the default bearer/apiKey
|
|
17
|
+
* verifiers (unless policy.verify happens to name one of those).
|
|
18
|
+
*
|
|
19
|
+
* Pure-read: throws HttpError(401/500) on failure, otherwise returns
|
|
20
|
+
* { user } for downstream nodes to see. No side effects, so no
|
|
21
|
+
* `effects: 'mutation'`.
|
|
22
|
+
*/
|
|
23
|
+
export function registerRequireAuthNode(registry: NodeRegistry): void {
|
|
24
|
+
registry.register(
|
|
25
|
+
{
|
|
26
|
+
type: 'requireAuth',
|
|
27
|
+
label: 'Require Auth',
|
|
28
|
+
description:
|
|
29
|
+
'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.',
|
|
30
|
+
simpleDescription: 'Checks the request is authorized before continuing',
|
|
31
|
+
category: 'http',
|
|
32
|
+
traceKind: 'compute',
|
|
33
|
+
tags: ['http', 'auth'],
|
|
34
|
+
configSchema: {
|
|
35
|
+
fields: [
|
|
36
|
+
{ name: 'scheme', type: 'string' },
|
|
37
|
+
{ name: 'secretRef', type: 'string' },
|
|
38
|
+
{ name: 'verify', type: 'string' },
|
|
39
|
+
{ name: 'header', type: 'string' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
outputSchema: {
|
|
43
|
+
fields: [{ name: 'user', type: 'object' }],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
async (ctx) => {
|
|
47
|
+
const policy = ctx.config as unknown as AuthPolicy;
|
|
48
|
+
const input = (ctx.input ?? {}) as { headers?: Record<string, unknown> };
|
|
49
|
+
const request = { headers: input.headers ?? {} };
|
|
50
|
+
const verifiers = createDefaultVerifierRegistry();
|
|
51
|
+
const { user } = enforceAuth({ policy, request, secrets: ctx.secrets, verifiers });
|
|
52
|
+
return { user };
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createDefaultRegistry } from './index';
|
|
3
|
+
|
|
4
|
+
describe('Response node', () => {
|
|
5
|
+
it('is registered with type Response', () => {
|
|
6
|
+
expect(createDefaultRegistry().get('Response').definition.type).toBe('Response');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('passes { status, body } through, defaulting status to 200', async () => {
|
|
10
|
+
const impl = createDefaultRegistry().get('Response').implementation;
|
|
11
|
+
const ctx = {
|
|
12
|
+
input: { body: { ok: true } },
|
|
13
|
+
config: {},
|
|
14
|
+
secrets: {},
|
|
15
|
+
vars: {},
|
|
16
|
+
safeMode: false,
|
|
17
|
+
runInput: {},
|
|
18
|
+
log: () => {},
|
|
19
|
+
} as never;
|
|
20
|
+
expect(await impl(ctx)).toEqual({ status: 200, body: { ok: true } });
|
|
21
|
+
|
|
22
|
+
const ctx2 = {
|
|
23
|
+
input: { status: 201, body: { id: 1 } },
|
|
24
|
+
config: {},
|
|
25
|
+
secrets: {},
|
|
26
|
+
vars: {},
|
|
27
|
+
safeMode: false,
|
|
28
|
+
runInput: {},
|
|
29
|
+
log: () => {},
|
|
30
|
+
} as never;
|
|
31
|
+
expect(await impl(ctx2)).toEqual({ status: 201, body: { id: 1 } });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Terminal node for an HTTP operation: its input `{ status, body }` becomes
|
|
5
|
+
* the HTTP response `extractResponse` (server/operationResult.ts) maps onto
|
|
6
|
+
* the wire. `status` defaults to 200 when the input omits it. Pure — no
|
|
7
|
+
* side effects, so no `effects: 'mutation'` declaration.
|
|
8
|
+
*/
|
|
9
|
+
export function registerResponseNodes(registry: NodeRegistry): void {
|
|
10
|
+
registry.register(
|
|
11
|
+
{
|
|
12
|
+
type: 'Response',
|
|
13
|
+
label: 'Response',
|
|
14
|
+
description:
|
|
15
|
+
'Terminal node for an HTTP operation: its input { status, body } becomes the HTTP response. Omit status to default to 200.',
|
|
16
|
+
simpleDescription: 'Shapes the HTTP response for this operation',
|
|
17
|
+
category: 'http',
|
|
18
|
+
traceKind: 'compute',
|
|
19
|
+
tags: ['http'],
|
|
20
|
+
inputSchema: {
|
|
21
|
+
fields: [
|
|
22
|
+
{ name: 'status', type: 'number' },
|
|
23
|
+
{ name: 'body', type: 'object' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
outputSchema: {
|
|
27
|
+
fields: [
|
|
28
|
+
{ name: 'status', type: 'number' },
|
|
29
|
+
{ name: 'body', type: 'object' },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
async (ctx) => {
|
|
34
|
+
const input = (ctx.input ?? {}) as { status?: unknown; body?: unknown };
|
|
35
|
+
const status = typeof input.status === 'number' ? input.status : 200;
|
|
36
|
+
return { status, body: input.body };
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createDefaultRegistry } from './index';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* traceDetail is a STATIC, honest descriptor of the concrete call each db/http/llm
|
|
6
|
+
* node makes — the "what it calls" line in the runbook trace-badge hover card.
|
|
7
|
+
* These assertions prove honesty (real table names, HTTP verbs, provider/model)
|
|
8
|
+
* without pinning brittle exact strings.
|
|
9
|
+
*/
|
|
10
|
+
describe('node traceDetail annotations', () => {
|
|
11
|
+
const registry = createDefaultRegistry();
|
|
12
|
+
const detail = (type: string): string | undefined => registry.get(type).definition.traceDetail;
|
|
13
|
+
|
|
14
|
+
it('every db/http/llm node carries a non-empty traceDetail; compute nodes carry none', () => {
|
|
15
|
+
for (const def of registry.list()) {
|
|
16
|
+
if (def.traceKind === 'db' || def.traceKind === 'http' || def.traceKind === 'llm') {
|
|
17
|
+
expect(def.traceDetail, `${def.type} should have traceDetail`).toBeTruthy();
|
|
18
|
+
expect(def.traceDetail!.trim().length).toBeGreaterThan(0);
|
|
19
|
+
}
|
|
20
|
+
if (def.traceKind === 'compute') {
|
|
21
|
+
expect(def.traceDetail, `${def.type} (compute) should have no traceDetail`).toBeUndefined();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('http nodes name the HTTP verb and endpoint', () => {
|
|
27
|
+
// Real anomaly service.
|
|
28
|
+
expect(detail('DetectLastPoint')).toMatch(/POST/);
|
|
29
|
+
expect(detail('DetectLastPoint')).toMatch(/timeseries\/last\/detect/);
|
|
30
|
+
|
|
31
|
+
// Open-Meteo public API.
|
|
32
|
+
expect(detail('GeocodeCity')).toMatch(/open-meteo\.com/);
|
|
33
|
+
|
|
34
|
+
// Simulated senders name the endpoint they represent AND flag the simulation.
|
|
35
|
+
expect(detail('EvNotify')).toMatch(/ntfy\.sh/);
|
|
36
|
+
expect(detail('EvNotify')).toMatch(/SIMULATED/i);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildAdvisory, classifyWeather, describeWeatherCode } from './weather';
|
|
3
|
+
import { createDefaultRegistry } from './index';
|
|
4
|
+
import { createWeatherFlow } from '../flows/weather-flow';
|
|
5
|
+
import { validateFlow } from '../engine';
|
|
6
|
+
|
|
7
|
+
describe('weather advisory logic', () => {
|
|
8
|
+
it('maps weather codes to descriptions', () => {
|
|
9
|
+
expect(describeWeatherCode(0)).toBe('clear sky');
|
|
10
|
+
expect(describeWeatherCode(63)).toBe('rain');
|
|
11
|
+
expect(describeWeatherCode(1234)).toContain('unknown');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('advises umbrella on high rain chance and warmth on cold', () => {
|
|
15
|
+
const advisory = buildAdvisory({
|
|
16
|
+
place: 'Belfast', temperatureC: 3, windKmh: 35, weatherCode: 61,
|
|
17
|
+
maxC: 6, minC: 1, rainChancePct: 80,
|
|
18
|
+
});
|
|
19
|
+
expect(advisory.advice).toContain('umbrella');
|
|
20
|
+
expect(advisory.advice).toContain('wind');
|
|
21
|
+
expect(advisory.advice).toContain('warm');
|
|
22
|
+
expect(advisory.headline).toBe('Belfast: 3°C, light rain');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('gives the all-clear on mild conditions', () => {
|
|
26
|
+
const advisory = buildAdvisory({
|
|
27
|
+
place: 'Nice', temperatureC: 21, windKmh: 8, weatherCode: 0,
|
|
28
|
+
maxC: 24, minC: 16, rainChancePct: 5,
|
|
29
|
+
});
|
|
30
|
+
expect(advisory.advice).toBe('no precautions needed');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('classifyWeather severity', () => {
|
|
35
|
+
it('flags severe on gale-strength wind (≥ 40 km/h)', () => {
|
|
36
|
+
const result = classifyWeather(45, 10);
|
|
37
|
+
expect(result.severity).toBe('severe');
|
|
38
|
+
expect(result.reason).toContain('wind');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('flags severe on high rain chance (≥ 70%)', () => {
|
|
42
|
+
const result = classifyWeather(12, 85);
|
|
43
|
+
expect(result.severity).toBe('severe');
|
|
44
|
+
expect(result.reason).toContain('rain');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('grades calm conditions as mild', () => {
|
|
48
|
+
expect(classifyWeather(15, 20).severity).toBe('mild');
|
|
49
|
+
// Boundaries are exclusive below the thresholds.
|
|
50
|
+
expect(classifyWeather(39, 69).severity).toBe('mild');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('weather flow', () => {
|
|
55
|
+
it('registers the weather nodes alongside the login set', () => {
|
|
56
|
+
const types = createDefaultRegistry(0).list().map((d) => d.type);
|
|
57
|
+
expect(types).toEqual(
|
|
58
|
+
expect.arrayContaining(['GeocodeCity', 'FetchForecast', 'SummarizeConditions', 'Result', 'ValidateCredentials']),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('validates cleanly against the default registry', () => {
|
|
63
|
+
expect(validateFlow(createWeatherFlow(), createDefaultRegistry(0))).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import type { NodeRegistry } from '../engine';
|
|
2
|
+
|
|
3
|
+
const WEATHER_CODES: Record<number, string> = {
|
|
4
|
+
0: 'clear sky',
|
|
5
|
+
1: 'mainly clear',
|
|
6
|
+
2: 'partly cloudy',
|
|
7
|
+
3: 'overcast',
|
|
8
|
+
45: 'fog',
|
|
9
|
+
48: 'rime fog',
|
|
10
|
+
51: 'light drizzle',
|
|
11
|
+
53: 'drizzle',
|
|
12
|
+
55: 'dense drizzle',
|
|
13
|
+
61: 'light rain',
|
|
14
|
+
63: 'rain',
|
|
15
|
+
65: 'heavy rain',
|
|
16
|
+
71: 'light snow',
|
|
17
|
+
73: 'snow',
|
|
18
|
+
75: 'heavy snow',
|
|
19
|
+
80: 'rain showers',
|
|
20
|
+
81: 'rain showers',
|
|
21
|
+
82: 'violent rain showers',
|
|
22
|
+
95: 'thunderstorm',
|
|
23
|
+
96: 'thunderstorm with hail',
|
|
24
|
+
99: 'thunderstorm with heavy hail',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function describeWeatherCode(code: number): string {
|
|
28
|
+
return WEATHER_CODES[code] ?? `unknown conditions (code ${code})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AdvisoryInput {
|
|
32
|
+
place: string;
|
|
33
|
+
temperatureC: number;
|
|
34
|
+
windKmh: number;
|
|
35
|
+
weatherCode: number;
|
|
36
|
+
maxC: number;
|
|
37
|
+
minC: number;
|
|
38
|
+
rainChancePct: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildAdvisory(input: AdvisoryInput) {
|
|
42
|
+
const condition = describeWeatherCode(input.weatherCode);
|
|
43
|
+
const advice: string[] = [];
|
|
44
|
+
if (input.rainChancePct >= 50) advice.push('take an umbrella');
|
|
45
|
+
if (input.windKmh >= 30) advice.push('expect strong wind');
|
|
46
|
+
if (input.temperatureC <= 5) advice.push('dress warm');
|
|
47
|
+
if (input.temperatureC >= 28) advice.push('stay hydrated');
|
|
48
|
+
if (advice.length === 0) advice.push('no precautions needed');
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
place: input.place,
|
|
52
|
+
temperatureC: input.temperatureC,
|
|
53
|
+
condition,
|
|
54
|
+
headline: `${input.place}: ${Math.round(input.temperatureC)}°C, ${condition}`,
|
|
55
|
+
range: `${Math.round(input.minC)}–${Math.round(input.maxC)}°C today, ${Math.round(input.rainChancePct)}% rain chance`,
|
|
56
|
+
advice: advice.join('; '),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type WeatherSeverity = 'severe' | 'mild';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pure severity rule: severe when wind is at gale strength (≥ 40 km/h) or rain
|
|
64
|
+
* is likely (≥ 70% chance), otherwise mild.
|
|
65
|
+
*/
|
|
66
|
+
export function classifyWeather(windKmh: number, rainChancePct: number): {
|
|
67
|
+
severity: WeatherSeverity;
|
|
68
|
+
reason: string;
|
|
69
|
+
} {
|
|
70
|
+
if (windKmh >= 40) {
|
|
71
|
+
return { severity: 'severe', reason: `high winds at ${Math.round(windKmh)} km/h` };
|
|
72
|
+
}
|
|
73
|
+
if (rainChancePct >= 70) {
|
|
74
|
+
return { severity: 'severe', reason: `${Math.round(rainChancePct)}% chance of rain` };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
severity: 'mild',
|
|
78
|
+
reason: `winds ${Math.round(windKmh)} km/h, ${Math.round(rainChancePct)}% rain chance`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Real-infrastructure nodes backed by the Open-Meteo public APIs
|
|
84
|
+
* (keyless, CORS-enabled): geocoding + weather forecast.
|
|
85
|
+
*/
|
|
86
|
+
export function registerWeatherNodes(registry: NodeRegistry): void {
|
|
87
|
+
registry.register(
|
|
88
|
+
{
|
|
89
|
+
type: 'GeocodeCity',
|
|
90
|
+
label: 'Geocode City',
|
|
91
|
+
description: 'Resolves a city name to coordinates via the Open-Meteo geocoding API.',
|
|
92
|
+
category: 'http',
|
|
93
|
+
traceKind: 'http',
|
|
94
|
+
traceDetail: 'GET https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1',
|
|
95
|
+
// city is input-drivable (e.g. from an Input node); config is the fallback.
|
|
96
|
+
inputSchema: {
|
|
97
|
+
fields: [{ name: 'city', type: 'string' }],
|
|
98
|
+
},
|
|
99
|
+
configSchema: {
|
|
100
|
+
fields: [{ name: 'city', type: 'string', required: true }],
|
|
101
|
+
},
|
|
102
|
+
outputSchema: {
|
|
103
|
+
fields: [
|
|
104
|
+
{ name: 'place', type: 'string' },
|
|
105
|
+
{ name: 'country', type: 'string' },
|
|
106
|
+
{ name: 'latitude', type: 'number' },
|
|
107
|
+
{ name: 'longitude', type: 'number' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
async (ctx) => {
|
|
112
|
+
const city = String(ctx.input.city ?? '');
|
|
113
|
+
if (!city) throw new Error('No city configured');
|
|
114
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`;
|
|
115
|
+
ctx.log('info', `GET ${url}`);
|
|
116
|
+
const response = await fetch(url);
|
|
117
|
+
if (!response.ok) throw new Error(`Geocoding failed: HTTP ${response.status}`);
|
|
118
|
+
const data = (await response.json()) as {
|
|
119
|
+
results?: Array<{ name: string; country: string; latitude: number; longitude: number }>;
|
|
120
|
+
};
|
|
121
|
+
const hit = data.results?.[0];
|
|
122
|
+
if (!hit) throw new Error(`No location found for "${city}"`);
|
|
123
|
+
ctx.log('info', `Resolved ${hit.name}, ${hit.country} → ${hit.latitude}, ${hit.longitude}`);
|
|
124
|
+
return {
|
|
125
|
+
place: hit.name,
|
|
126
|
+
country: hit.country,
|
|
127
|
+
latitude: hit.latitude,
|
|
128
|
+
longitude: hit.longitude,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
registry.register(
|
|
134
|
+
{
|
|
135
|
+
type: 'FetchForecast',
|
|
136
|
+
label: 'Fetch Forecast',
|
|
137
|
+
description: 'Fetches live current conditions and today’s outlook from the Open-Meteo forecast API.',
|
|
138
|
+
category: 'http',
|
|
139
|
+
traceKind: 'http',
|
|
140
|
+
traceDetail:
|
|
141
|
+
'GET https://api.open-meteo.com/v1/forecast?latitude&longitude¤t=temperature_2m,wind_speed_10m,weather_code&daily=…&forecast_days=1',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
fields: [
|
|
144
|
+
{ name: 'latitude', type: 'number', required: true },
|
|
145
|
+
{ name: 'longitude', type: 'number', required: true },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
outputSchema: {
|
|
149
|
+
fields: [
|
|
150
|
+
{ name: 'temperatureC', type: 'number' },
|
|
151
|
+
{ name: 'windKmh', type: 'number' },
|
|
152
|
+
{ name: 'weatherCode', type: 'number' },
|
|
153
|
+
{ name: 'maxC', type: 'number' },
|
|
154
|
+
{ name: 'minC', type: 'number' },
|
|
155
|
+
{ name: 'rainChancePct', type: 'number' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
async (ctx) => {
|
|
160
|
+
const latitude = Number(ctx.input.latitude);
|
|
161
|
+
const longitude = Number(ctx.input.longitude);
|
|
162
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
163
|
+
throw new Error('latitude/longitude missing or not numbers');
|
|
164
|
+
}
|
|
165
|
+
const url =
|
|
166
|
+
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}` +
|
|
167
|
+
'¤t=temperature_2m,wind_speed_10m,weather_code' +
|
|
168
|
+
'&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max' +
|
|
169
|
+
'&timezone=auto&forecast_days=1';
|
|
170
|
+
ctx.log('info', `GET ${url}`);
|
|
171
|
+
const response = await fetch(url);
|
|
172
|
+
if (!response.ok) throw new Error(`Forecast failed: HTTP ${response.status}`);
|
|
173
|
+
const data = (await response.json()) as {
|
|
174
|
+
current: { temperature_2m: number; wind_speed_10m: number; weather_code: number };
|
|
175
|
+
daily: {
|
|
176
|
+
temperature_2m_max: number[];
|
|
177
|
+
temperature_2m_min: number[];
|
|
178
|
+
precipitation_probability_max: Array<number | null>;
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
const output = {
|
|
182
|
+
temperatureC: data.current.temperature_2m,
|
|
183
|
+
windKmh: data.current.wind_speed_10m,
|
|
184
|
+
weatherCode: data.current.weather_code,
|
|
185
|
+
maxC: data.daily.temperature_2m_max[0],
|
|
186
|
+
minC: data.daily.temperature_2m_min[0],
|
|
187
|
+
rainChancePct: data.daily.precipitation_probability_max[0] ?? 0,
|
|
188
|
+
};
|
|
189
|
+
ctx.log('info', `Now ${output.temperatureC}°C, wind ${output.windKmh} km/h, code ${output.weatherCode}`);
|
|
190
|
+
return output;
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
registry.register(
|
|
195
|
+
{
|
|
196
|
+
type: 'SummarizeConditions',
|
|
197
|
+
label: 'Summarize Conditions',
|
|
198
|
+
description: 'Turns raw forecast numbers into a readable weather advisory.',
|
|
199
|
+
category: 'logic',
|
|
200
|
+
traceKind: 'compute',
|
|
201
|
+
inputSchema: {
|
|
202
|
+
fields: [
|
|
203
|
+
{ name: 'place', type: 'string', required: true },
|
|
204
|
+
{ name: 'temperatureC', type: 'number', required: true },
|
|
205
|
+
{ name: 'windKmh', type: 'number', required: true },
|
|
206
|
+
{ name: 'weatherCode', type: 'number', required: true },
|
|
207
|
+
{ name: 'maxC', type: 'number' },
|
|
208
|
+
{ name: 'minC', type: 'number' },
|
|
209
|
+
{ name: 'rainChancePct', type: 'number' },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
outputSchema: {
|
|
213
|
+
fields: [
|
|
214
|
+
{ name: 'headline', type: 'string' },
|
|
215
|
+
{ name: 'range', type: 'string' },
|
|
216
|
+
{ name: 'advice', type: 'string' },
|
|
217
|
+
{ name: 'place', type: 'string' },
|
|
218
|
+
{ name: 'temperatureC', type: 'number' },
|
|
219
|
+
{ name: 'condition', type: 'string' },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
async (ctx) => {
|
|
224
|
+
const advisory = buildAdvisory({
|
|
225
|
+
place: String(ctx.input.place ?? 'Unknown'),
|
|
226
|
+
temperatureC: Number(ctx.input.temperatureC),
|
|
227
|
+
windKmh: Number(ctx.input.windKmh),
|
|
228
|
+
weatherCode: Number(ctx.input.weatherCode),
|
|
229
|
+
maxC: Number(ctx.input.maxC ?? ctx.input.temperatureC),
|
|
230
|
+
minC: Number(ctx.input.minC ?? ctx.input.temperatureC),
|
|
231
|
+
rainChancePct: Number(ctx.input.rainChancePct ?? 0),
|
|
232
|
+
});
|
|
233
|
+
ctx.log('info', advisory.headline);
|
|
234
|
+
ctx.log('info', `Advice: ${advisory.advice}`);
|
|
235
|
+
return advisory;
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
registry.register(
|
|
240
|
+
{
|
|
241
|
+
type: 'ClassifyWeather',
|
|
242
|
+
label: 'Classify Weather',
|
|
243
|
+
description: 'Grades conditions as severe or mild from wind and rain thresholds.',
|
|
244
|
+
category: 'logic',
|
|
245
|
+
traceKind: 'compute',
|
|
246
|
+
tags: ['branching'],
|
|
247
|
+
inputSchema: {
|
|
248
|
+
fields: [
|
|
249
|
+
{ name: 'windKmh', type: 'number', required: true },
|
|
250
|
+
{ name: 'rainChancePct', type: 'number', required: true },
|
|
251
|
+
// Optional passthrough so fan-out consumers (e.g. a Collect
|
|
252
|
+
// gathering one verdict per city) keep the place name attached.
|
|
253
|
+
{ name: 'place', type: 'string' },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
outputSchema: {
|
|
257
|
+
fields: [
|
|
258
|
+
{ name: 'severity', type: 'enum', enumValues: ['severe', 'mild'] },
|
|
259
|
+
{ name: 'reason', type: 'string' },
|
|
260
|
+
{ name: 'place', type: 'string' },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
async (ctx) => {
|
|
265
|
+
const { severity, reason } = classifyWeather(
|
|
266
|
+
Number(ctx.input.windKmh),
|
|
267
|
+
Number(ctx.input.rainChancePct),
|
|
268
|
+
);
|
|
269
|
+
ctx.log('info', `Severity ${severity}: ${reason}`);
|
|
270
|
+
return {
|
|
271
|
+
severity,
|
|
272
|
+
reason,
|
|
273
|
+
...(ctx.input.place !== undefined ? { place: ctx.input.place } : {}),
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
registry.register(
|
|
279
|
+
{
|
|
280
|
+
type: 'ComposeAlert',
|
|
281
|
+
label: 'Compose Alert',
|
|
282
|
+
description: 'Formats a severe-weather alert line for a place.',
|
|
283
|
+
category: 'logic',
|
|
284
|
+
traceKind: 'compute',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
fields: [
|
|
287
|
+
{ name: 'place', type: 'string', required: true },
|
|
288
|
+
{ name: 'reason', type: 'string', required: true },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
outputSchema: {
|
|
292
|
+
fields: [
|
|
293
|
+
{ name: 'alert', type: 'string' },
|
|
294
|
+
{ name: 'place', type: 'string' },
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
async (ctx) => {
|
|
299
|
+
const place = String(ctx.input.place ?? 'the area');
|
|
300
|
+
const reason = String(ctx.input.reason ?? 'conditions worsening');
|
|
301
|
+
const alert = `⚠ Weather alert for ${place}: ${reason}`;
|
|
302
|
+
ctx.log('info', alert);
|
|
303
|
+
return { alert, place };
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
}
|