@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,357 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { CheckIcon, ChevronDownIcon, Settings2Icon, ShieldIcon, ShieldOffIcon, TheaterIcon } from 'lucide-react';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
} from '@/components/ui/dialog';
|
|
11
|
+
import { Input } from '@/components/ui/input';
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
13
|
+
import { EnvironmentDialog } from './EnvironmentDialog';
|
|
14
|
+
import { EnvironmentsDialog } from './EnvironmentsDialog';
|
|
15
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Environment + mode + safe-mode selector. One dropdown answers "where do
|
|
19
|
+
* runs point": Mock (example responses from scenarios — nothing executes) or
|
|
20
|
+
* a real environment. Picking Mock is instant; picking an environment while
|
|
21
|
+
* in Mock goes live through a confirm dialog (blocked outright when the
|
|
22
|
+
* runner's default environment is protected — served traffic always runs for
|
|
23
|
+
* real against it and ignores the studio's safe mode). Protected environments
|
|
24
|
+
* force safe mode on; disabling it there requires typing the env name back.
|
|
25
|
+
*/
|
|
26
|
+
export function EnvironmentPicker() {
|
|
27
|
+
const environments = useBuilderStore((s) => s.environments);
|
|
28
|
+
const selected = useBuilderStore((s) => s.selectedEnvironment);
|
|
29
|
+
const safeMode = useBuilderStore((s) => s.safeMode);
|
|
30
|
+
const selectEnvironment = useBuilderStore((s) => s.selectEnvironment);
|
|
31
|
+
const setSafeMode = useBuilderStore((s) => s.setSafeMode);
|
|
32
|
+
const runnerOnline = useBuilderStore((s) => s.runnerOnline);
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
35
|
+
const [manageEnv, setManageEnv] = useState<string | null>(null);
|
|
36
|
+
const [confirmText, setConfirmText] = useState('');
|
|
37
|
+
const runnerMock = useBuilderStore((s) => s.runnerMock);
|
|
38
|
+
const setServingMode = useBuilderStore((s) => s.setServingMode);
|
|
39
|
+
const environmentsDefault = useBuilderStore((s) => s.environmentsDefault);
|
|
40
|
+
const [liveConfirm, setLiveConfirm] = useState<{ env: string; blocked: boolean } | null>(null);
|
|
41
|
+
const [liveError, setLiveError] = useState<string | null>(null);
|
|
42
|
+
const [envsOpen, setEnvsOpen] = useState(false);
|
|
43
|
+
|
|
44
|
+
const current = environments.find((e) => e.name === selected);
|
|
45
|
+
const protectedEnv = current?.protected ?? false;
|
|
46
|
+
const offline = environments.length === 0;
|
|
47
|
+
// The unmissable state: writes will execute against a protected environment.
|
|
48
|
+
const live = protectedEnv && !safeMode;
|
|
49
|
+
const label = selected || 'browser';
|
|
50
|
+
|
|
51
|
+
const toggleSafe = () => {
|
|
52
|
+
if (safeMode) {
|
|
53
|
+
// Turning safe mode OFF on a protected env goes through the typed-confirm.
|
|
54
|
+
if (protectedEnv) {
|
|
55
|
+
setConfirmText('');
|
|
56
|
+
setConfirmOpen(true);
|
|
57
|
+
setOpen(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setSafeMode(false);
|
|
61
|
+
} else {
|
|
62
|
+
setSafeMode(true);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const confirmDisable = () => {
|
|
67
|
+
if (setSafeMode(false, confirmText.trim())) {
|
|
68
|
+
setConfirmOpen(false);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const pickMock = () => {
|
|
73
|
+
setOpen(false);
|
|
74
|
+
if (runnerMock) return;
|
|
75
|
+
// Harmless direction — nothing will execute.
|
|
76
|
+
void setServingMode('mock').catch(() => {});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const pickEnvironment = (name: string) => {
|
|
80
|
+
setOpen(false);
|
|
81
|
+
if (!runnerMock) {
|
|
82
|
+
selectEnvironment(name);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Leaving Mock: mounted endpoints resume real execution against the
|
|
86
|
+
// runner's DEFAULT environment (not the picked one) — confirm, and block
|
|
87
|
+
// outright when that default is protected.
|
|
88
|
+
const servedEnv = environments.find((e) => e.name === environmentsDefault);
|
|
89
|
+
setLiveError(null);
|
|
90
|
+
setLiveConfirm({ env: name, blocked: !!servedEnv?.protected });
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const openEnvironments = () => {
|
|
94
|
+
setOpen(false);
|
|
95
|
+
setEnvsOpen(true);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const confirmLive = () => {
|
|
99
|
+
if (!liveConfirm) return;
|
|
100
|
+
setLiveError(null);
|
|
101
|
+
setServingMode('real')
|
|
102
|
+
.then(() => {
|
|
103
|
+
selectEnvironment(liveConfirm.env);
|
|
104
|
+
setLiveConfirm(null);
|
|
105
|
+
})
|
|
106
|
+
.catch((err) =>
|
|
107
|
+
setLiveError(err instanceof Error ? err.message : 'Failed to switch serving mode'),
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
113
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
114
|
+
<PopoverTrigger asChild>
|
|
115
|
+
<button
|
|
116
|
+
className={cn(
|
|
117
|
+
'flex h-7 cursor-pointer items-center gap-1.5 rounded-md px-2 text-[11.5px] transition-colors',
|
|
118
|
+
runnerMock
|
|
119
|
+
? 'bg-warn/15 font-medium text-warn hover:bg-warn/25'
|
|
120
|
+
: live
|
|
121
|
+
? 'bg-destructive/15 font-medium text-destructive-foreground hover:bg-destructive/25'
|
|
122
|
+
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground',
|
|
123
|
+
)}
|
|
124
|
+
title={
|
|
125
|
+
runnerMock
|
|
126
|
+
? 'Mock mode — everything answers with example responses from scenarios. Nothing executes, no auth, no database. Pick an environment to go live.'
|
|
127
|
+
: 'Where runs point — pick Mock or a real environment'
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
{runnerMock ? (
|
|
131
|
+
<TheaterIcon className="size-3.5" />
|
|
132
|
+
) : live ? (
|
|
133
|
+
<ShieldOffIcon className="size-3.5" />
|
|
134
|
+
) : (
|
|
135
|
+
safeMode && <ShieldIcon className="size-3.5" />
|
|
136
|
+
)}
|
|
137
|
+
<span className={cn(live && !runnerMock && 'uppercase tracking-wide')}>
|
|
138
|
+
{runnerMock ? 'Mock' : live ? `LIVE · ${label}` : label}
|
|
139
|
+
</span>
|
|
140
|
+
<ChevronDownIcon className="size-3 opacity-50" />
|
|
141
|
+
</button>
|
|
142
|
+
</PopoverTrigger>
|
|
143
|
+
<PopoverContent className="w-64 p-1" align="end">
|
|
144
|
+
{runnerOnline === true && (
|
|
145
|
+
<>
|
|
146
|
+
<button
|
|
147
|
+
onClick={pickMock}
|
|
148
|
+
className="flex w-full cursor-pointer items-start gap-2 rounded-sm px-2.5 py-1.5 text-left transition-colors hover:bg-accent"
|
|
149
|
+
>
|
|
150
|
+
<CheckIcon className={cn('mt-0.5 size-3.5', runnerMock ? 'opacity-100' : 'opacity-0')} />
|
|
151
|
+
<TheaterIcon className={cn('mt-0.5 size-3.5', runnerMock ? 'text-warn' : 'text-muted-foreground')} />
|
|
152
|
+
<span className="flex min-w-0 flex-col gap-0.5">
|
|
153
|
+
<span className={cn('text-[12.5px] font-medium', runnerMock && 'text-warn')}>Mock</span>
|
|
154
|
+
<span className="text-[10.5px] leading-snug text-muted-foreground/80">
|
|
155
|
+
Example responses from scenarios — nothing executes.
|
|
156
|
+
</span>
|
|
157
|
+
</span>
|
|
158
|
+
</button>
|
|
159
|
+
<div className="my-1 h-px bg-border" />
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
{offline ? (
|
|
163
|
+
runnerOnline === true ? (
|
|
164
|
+
<div className="flex flex-col gap-1.5 px-2.5 py-2">
|
|
165
|
+
<span className="text-[12px] leading-snug text-muted-foreground">
|
|
166
|
+
No environments yet — you're in Mock, where nothing real is touched. Create
|
|
167
|
+
environments (dev? prod? test?) to go live.
|
|
168
|
+
</span>
|
|
169
|
+
<Button size="sm" variant="secondary" onClick={openEnvironments}>
|
|
170
|
+
<Settings2Icon className="size-3.5" />
|
|
171
|
+
Manage environments…
|
|
172
|
+
</Button>
|
|
173
|
+
</div>
|
|
174
|
+
) : (
|
|
175
|
+
<div className="px-2.5 py-3 text-[12px] text-muted-foreground">
|
|
176
|
+
{runnerOnline === false
|
|
177
|
+
? 'Runner offline — runs execute in the browser as environment "browser".'
|
|
178
|
+
: 'Loading environments…'}
|
|
179
|
+
</div>
|
|
180
|
+
)
|
|
181
|
+
) : (
|
|
182
|
+
<div className="flex flex-col">
|
|
183
|
+
{environments.map((env) => (
|
|
184
|
+
<div
|
|
185
|
+
key={env.name}
|
|
186
|
+
className="group flex items-center gap-2 rounded-sm transition-colors hover:bg-accent"
|
|
187
|
+
>
|
|
188
|
+
<button
|
|
189
|
+
onClick={() => pickEnvironment(env.name)}
|
|
190
|
+
title={runnerMock ? `Go live against ${env.name}` : undefined}
|
|
191
|
+
className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2.5 py-1.5 text-left"
|
|
192
|
+
>
|
|
193
|
+
<CheckIcon
|
|
194
|
+
className={cn(
|
|
195
|
+
'size-3.5',
|
|
196
|
+
env.name === selected && !runnerMock ? 'opacity-100' : 'opacity-0',
|
|
197
|
+
)}
|
|
198
|
+
/>
|
|
199
|
+
{env.protected && <span className="size-1.5 rounded-full bg-destructive" />}
|
|
200
|
+
<span className="text-[12.5px] font-medium">{env.name}</span>
|
|
201
|
+
{env.protected && (
|
|
202
|
+
<span className="ml-auto text-[10.5px] uppercase tracking-wide text-destructive-foreground/80">
|
|
203
|
+
protected
|
|
204
|
+
</span>
|
|
205
|
+
)}
|
|
206
|
+
</button>
|
|
207
|
+
<button
|
|
208
|
+
onClick={() => {
|
|
209
|
+
setManageEnv(env.name);
|
|
210
|
+
setOpen(false);
|
|
211
|
+
}}
|
|
212
|
+
title={`Manage ${env.name} — secrets & auth`}
|
|
213
|
+
className="mr-1.5 cursor-pointer rounded-sm p-1 text-muted-foreground opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100"
|
|
214
|
+
>
|
|
215
|
+
<Settings2Icon className="size-3.5" />
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
))}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
<div className="my-1 h-px bg-border" />
|
|
222
|
+
<button
|
|
223
|
+
onClick={toggleSafe}
|
|
224
|
+
className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-left transition-colors hover:bg-accent"
|
|
225
|
+
>
|
|
226
|
+
{safeMode ? (
|
|
227
|
+
<ShieldIcon className="size-3.5 text-success" />
|
|
228
|
+
) : (
|
|
229
|
+
<ShieldOffIcon className="size-3.5 text-destructive-foreground" />
|
|
230
|
+
)}
|
|
231
|
+
<span className="text-[12.5px] font-medium">Safe mode</span>
|
|
232
|
+
<span
|
|
233
|
+
className={cn(
|
|
234
|
+
'ml-auto text-[11px] font-medium',
|
|
235
|
+
safeMode ? 'text-success' : 'text-destructive-foreground',
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
{safeMode ? 'on' : 'off'}
|
|
239
|
+
</span>
|
|
240
|
+
</button>
|
|
241
|
+
{runnerOnline === true && !offline && (
|
|
242
|
+
<>
|
|
243
|
+
<div className="my-1 h-px bg-border" />
|
|
244
|
+
<button
|
|
245
|
+
onClick={openEnvironments}
|
|
246
|
+
title="See every environment, its secrets and auth — or describe changes to the agent."
|
|
247
|
+
className="flex w-full cursor-pointer items-center gap-2 rounded-sm px-2.5 py-1.5 text-left text-[12.5px] text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
248
|
+
>
|
|
249
|
+
<Settings2Icon className="size-3.5" />
|
|
250
|
+
Manage environments…
|
|
251
|
+
</button>
|
|
252
|
+
</>
|
|
253
|
+
)}
|
|
254
|
+
</PopoverContent>
|
|
255
|
+
</Popover>
|
|
256
|
+
|
|
257
|
+
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
|
258
|
+
<DialogContent className="max-w-md">
|
|
259
|
+
<DialogTitle>Disable safe mode on {selected}?</DialogTitle>
|
|
260
|
+
<DialogDescription>
|
|
261
|
+
Writes will execute for real against <span className="font-medium text-foreground">{selected}</span> —
|
|
262
|
+
database mutations, emails and other side effects. Type{' '}
|
|
263
|
+
<span className="font-mono text-foreground">{selected}</span> to confirm.
|
|
264
|
+
</DialogDescription>
|
|
265
|
+
<Input
|
|
266
|
+
value={confirmText}
|
|
267
|
+
onChange={(e) => setConfirmText(e.target.value)}
|
|
268
|
+
placeholder={selected}
|
|
269
|
+
autoFocus
|
|
270
|
+
spellCheck={false}
|
|
271
|
+
onKeyDown={(e) => {
|
|
272
|
+
if (e.key === 'Enter' && confirmText.trim() === selected) confirmDisable();
|
|
273
|
+
}}
|
|
274
|
+
/>
|
|
275
|
+
<div className="flex justify-end gap-2">
|
|
276
|
+
<Button variant="ghost" size="sm" onClick={() => setConfirmOpen(false)}>
|
|
277
|
+
Cancel
|
|
278
|
+
</Button>
|
|
279
|
+
<Button
|
|
280
|
+
variant="destructive"
|
|
281
|
+
size="sm"
|
|
282
|
+
disabled={confirmText.trim() !== selected}
|
|
283
|
+
onClick={confirmDisable}
|
|
284
|
+
>
|
|
285
|
+
Disable safe mode
|
|
286
|
+
</Button>
|
|
287
|
+
</div>
|
|
288
|
+
</DialogContent>
|
|
289
|
+
</Dialog>
|
|
290
|
+
|
|
291
|
+
<Dialog open={liveConfirm !== null} onOpenChange={(o) => !o && setLiveConfirm(null)}>
|
|
292
|
+
<DialogContent className="max-w-md">
|
|
293
|
+
{liveConfirm?.blocked ? (
|
|
294
|
+
<>
|
|
295
|
+
<DialogTitle>The served environment is protected</DialogTitle>
|
|
296
|
+
<DialogDescription>
|
|
297
|
+
Mounted endpoints execute against the runner's default environment
|
|
298
|
+
(<span className="font-medium text-foreground">{environmentsDefault}</span>),
|
|
299
|
+
which is protected — and served traffic always runs for real, with no dry-run.
|
|
300
|
+
The studio's safe mode does not apply to it. Change the default environment in{' '}
|
|
301
|
+
<span className="font-mono text-foreground">emberflow.environments.json</span>{' '}
|
|
302
|
+
before leaving Mock.
|
|
303
|
+
</DialogDescription>
|
|
304
|
+
<div className="flex justify-end">
|
|
305
|
+
<Button variant="ghost" size="sm" onClick={() => setLiveConfirm(null)}>
|
|
306
|
+
Close
|
|
307
|
+
</Button>
|
|
308
|
+
</div>
|
|
309
|
+
</>
|
|
310
|
+
) : (
|
|
311
|
+
<>
|
|
312
|
+
<DialogTitle>Go live?</DialogTitle>
|
|
313
|
+
<DialogDescription>
|
|
314
|
+
Runs will execute real nodes against{' '}
|
|
315
|
+
<span className="font-medium text-foreground">{liveConfirm?.env}</span>, and
|
|
316
|
+
mounted endpoints will stop answering from scenario examples and execute for
|
|
317
|
+
real against the{' '}
|
|
318
|
+
<span className="font-medium text-foreground">{environmentsDefault}</span>{' '}
|
|
319
|
+
environment — real side effects included. Served traffic ignores the studio's
|
|
320
|
+
safe mode.
|
|
321
|
+
</DialogDescription>
|
|
322
|
+
{liveError && <div className="text-[12px] text-destructive">{liveError}</div>}
|
|
323
|
+
<div className="flex justify-end gap-2">
|
|
324
|
+
<Button variant="ghost" size="sm" onClick={() => setLiveConfirm(null)}>
|
|
325
|
+
Cancel
|
|
326
|
+
</Button>
|
|
327
|
+
<Button size="sm" onClick={confirmLive}>
|
|
328
|
+
Go live
|
|
329
|
+
</Button>
|
|
330
|
+
</div>
|
|
331
|
+
</>
|
|
332
|
+
)}
|
|
333
|
+
</DialogContent>
|
|
334
|
+
</Dialog>
|
|
335
|
+
|
|
336
|
+
<EnvironmentDialog
|
|
337
|
+
env={environments.find((e) => e.name === manageEnv) ?? null}
|
|
338
|
+
open={manageEnv !== null}
|
|
339
|
+
onOpenChange={(o) => {
|
|
340
|
+
if (!o) setManageEnv(null);
|
|
341
|
+
}}
|
|
342
|
+
/>
|
|
343
|
+
|
|
344
|
+
<EnvironmentsDialog open={envsOpen} onOpenChange={setEnvsOpen} />
|
|
345
|
+
</>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Poll the runner health + environments on an interval. Mounted once (in the toolbar). */
|
|
350
|
+
export function useRunnerPolling(): void {
|
|
351
|
+
const checkRunner = useBuilderStore((s) => s.checkRunner);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
void checkRunner();
|
|
354
|
+
const interval = setInterval(() => void checkRunner(), 10_000);
|
|
355
|
+
return () => clearInterval(interval);
|
|
356
|
+
}, [checkRunner]);
|
|
357
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ArrowLeftIcon, KeyRoundIcon, Settings2Icon, SparklesIcon } from 'lucide-react';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
} from '@/components/ui/dialog';
|
|
11
|
+
import { EnvironmentDialog } from './EnvironmentDialog';
|
|
12
|
+
import { useBuilderStore } from '../store/builderStore';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The Environments overview modal — one place to SEE what exists. Each row
|
|
16
|
+
* opens the per-environment Manage dialog (secrets + auth); creating or
|
|
17
|
+
* reshaping environments is agent work: the call-to-action hands off to the
|
|
18
|
+
* agent panel in environment-setup mode, where the user describes what they
|
|
19
|
+
* want in prose and can keep refining it conversationally.
|
|
20
|
+
*/
|
|
21
|
+
export function EnvironmentsDialog({
|
|
22
|
+
open,
|
|
23
|
+
onOpenChange,
|
|
24
|
+
onBack,
|
|
25
|
+
}: {
|
|
26
|
+
open: boolean;
|
|
27
|
+
onOpenChange: (open: boolean) => void;
|
|
28
|
+
/** When set (opened from the Welcome checklist), a back arrow beside the
|
|
29
|
+
* title returns there instead of just closing. */
|
|
30
|
+
onBack?: () => void;
|
|
31
|
+
}) {
|
|
32
|
+
const environments = useBuilderStore((s) => s.environments);
|
|
33
|
+
const environmentsDefault = useBuilderStore((s) => s.environmentsDefault);
|
|
34
|
+
const beginEnvironmentSetup = useBuilderStore((s) => s.beginEnvironmentSetup);
|
|
35
|
+
const agentRunning = useBuilderStore((s) => s.agentRun?.status === 'running');
|
|
36
|
+
const [manageEnv, setManageEnv] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
const setupWithAi = () => {
|
|
39
|
+
onOpenChange(false);
|
|
40
|
+
beginEnvironmentSetup();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
46
|
+
<DialogContent className="max-w-lg">
|
|
47
|
+
<div className="flex items-center gap-2">
|
|
48
|
+
{onBack && (
|
|
49
|
+
<Button
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size="icon"
|
|
52
|
+
onClick={onBack}
|
|
53
|
+
aria-label="Back to setup checklist"
|
|
54
|
+
className="-ml-1.5 size-6 text-muted-foreground hover:text-foreground"
|
|
55
|
+
>
|
|
56
|
+
<ArrowLeftIcon className="size-4" />
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
<DialogTitle>Environments</DialogTitle>
|
|
60
|
+
</div>
|
|
61
|
+
{environments.length === 0 ? (
|
|
62
|
+
<>
|
|
63
|
+
<DialogDescription>
|
|
64
|
+
No environments yet — you're in Mock, where nothing real is touched. Environments
|
|
65
|
+
are the real places runs can point at (dev? prod? test?); create them to go live.
|
|
66
|
+
</DialogDescription>
|
|
67
|
+
<div className="flex justify-end">
|
|
68
|
+
<Button onClick={setupWithAi} disabled={agentRunning}>
|
|
69
|
+
<SparklesIcon className="size-3.5" />
|
|
70
|
+
Set up with AI
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
</>
|
|
74
|
+
) : (
|
|
75
|
+
<>
|
|
76
|
+
<DialogDescription>
|
|
77
|
+
Where runs can point. Secrets and auth live per environment — open one to manage
|
|
78
|
+
them. To add or reshape environments, describe the change to the agent.
|
|
79
|
+
</DialogDescription>
|
|
80
|
+
<div className="flex flex-col gap-1">
|
|
81
|
+
{environments.map((env) => (
|
|
82
|
+
<button
|
|
83
|
+
key={env.name}
|
|
84
|
+
onClick={() => setManageEnv(env.name)}
|
|
85
|
+
title={`Manage ${env.name} — secrets & auth`}
|
|
86
|
+
className="group flex w-full cursor-pointer items-center gap-2.5 rounded-md border border-border/70 px-3 py-2 text-left transition-colors hover:bg-accent"
|
|
87
|
+
>
|
|
88
|
+
<span
|
|
89
|
+
className={cn(
|
|
90
|
+
'size-2 shrink-0 rounded-full',
|
|
91
|
+
env.protected ? 'bg-destructive' : 'bg-success/70',
|
|
92
|
+
)}
|
|
93
|
+
/>
|
|
94
|
+
<span className="text-[13px] font-medium">{env.name}</span>
|
|
95
|
+
{env.name === environmentsDefault && (
|
|
96
|
+
<span className="rounded bg-secondary px-1.5 py-0.5 text-[9.5px] uppercase tracking-wide text-muted-foreground">
|
|
97
|
+
default
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
{env.protected && (
|
|
101
|
+
<span className="text-[10px] uppercase tracking-wide text-destructive-foreground/80">
|
|
102
|
+
protected
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
<span className="ml-auto flex items-center gap-2 text-[11px] text-muted-foreground">
|
|
106
|
+
{env.varKeys.length > 0 && <span>{env.varKeys.length} vars</span>}
|
|
107
|
+
{env.auth?.configured && (
|
|
108
|
+
<span
|
|
109
|
+
className="flex items-center gap-1"
|
|
110
|
+
title={env.auth.authenticated ? 'Auth configured — logged in' : 'Auth configured — not logged in'}
|
|
111
|
+
>
|
|
112
|
+
<KeyRoundIcon className="size-3" />
|
|
113
|
+
{env.auth.authenticated ? 'auth ✓' : 'auth'}
|
|
114
|
+
</span>
|
|
115
|
+
)}
|
|
116
|
+
<Settings2Icon className="size-3.5 opacity-0 transition-opacity group-hover:opacity-100" />
|
|
117
|
+
</span>
|
|
118
|
+
</button>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
<div className="flex justify-end gap-2">
|
|
122
|
+
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>
|
|
123
|
+
Close
|
|
124
|
+
</Button>
|
|
125
|
+
<Button variant="secondary" size="sm" onClick={setupWithAi} disabled={agentRunning}>
|
|
126
|
+
<SparklesIcon className="size-3.5" />
|
|
127
|
+
Change with AI
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</>
|
|
131
|
+
)}
|
|
132
|
+
</DialogContent>
|
|
133
|
+
</Dialog>
|
|
134
|
+
|
|
135
|
+
<EnvironmentDialog
|
|
136
|
+
env={environments.find((e) => e.name === manageEnv) ?? null}
|
|
137
|
+
open={manageEnv !== null}
|
|
138
|
+
onOpenChange={(o) => {
|
|
139
|
+
if (!o) setManageEnv(null);
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
</>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
import type { ExecutionRecord } from '../engine';
|
|
6
|
+
|
|
7
|
+
const statusDot: Record<string, string> = {
|
|
8
|
+
succeeded: 'bg-success',
|
|
9
|
+
failed: 'bg-destructive',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Browsable header + body over a loop-body node's `ExecutionRecord[]`.
|
|
14
|
+
* Selection is uncontrolled and defaults to (and snaps back to) the last
|
|
15
|
+
* record whenever the records array grows — i.e. a new run/iteration.
|
|
16
|
+
*/
|
|
17
|
+
export function ExecutionPager({
|
|
18
|
+
executions,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
}: {
|
|
22
|
+
executions: ExecutionRecord[];
|
|
23
|
+
children: (record: ExecutionRecord, index: number) => React.ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
}) {
|
|
26
|
+
const [index, setIndex] = useState(executions.length - 1);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setIndex(executions.length - 1);
|
|
30
|
+
}, [executions.length]);
|
|
31
|
+
|
|
32
|
+
const safeIndex = Math.min(Math.max(index, 0), executions.length - 1);
|
|
33
|
+
const record = executions[safeIndex];
|
|
34
|
+
if (!record) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className={className}>
|
|
38
|
+
<div className="mb-2 flex items-center gap-1">
|
|
39
|
+
<Button
|
|
40
|
+
variant="ghost"
|
|
41
|
+
size="icon"
|
|
42
|
+
className="size-5 shrink-0 text-muted-foreground hover:text-foreground"
|
|
43
|
+
disabled={safeIndex === 0}
|
|
44
|
+
onClick={() => setIndex(safeIndex - 1)}
|
|
45
|
+
aria-label="Previous execution"
|
|
46
|
+
>
|
|
47
|
+
<ChevronLeftIcon className="size-3.5" />
|
|
48
|
+
</Button>
|
|
49
|
+
<span className={cn('size-1.5 shrink-0 rounded-full', statusDot[record.status])} />
|
|
50
|
+
<span className="font-mono text-[11px] text-muted-foreground">
|
|
51
|
+
{safeIndex + 1}/{executions.length}
|
|
52
|
+
</span>
|
|
53
|
+
<Button
|
|
54
|
+
variant="ghost"
|
|
55
|
+
size="icon"
|
|
56
|
+
className="size-5 shrink-0 text-muted-foreground hover:text-foreground"
|
|
57
|
+
disabled={safeIndex === executions.length - 1}
|
|
58
|
+
onClick={() => setIndex(safeIndex + 1)}
|
|
59
|
+
aria-label="Next execution"
|
|
60
|
+
>
|
|
61
|
+
<ChevronRightIcon className="size-3.5" />
|
|
62
|
+
</Button>
|
|
63
|
+
</div>
|
|
64
|
+
{children(record, safeIndex)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { InfraPanel } from './InfraPanel';
|
|
4
|
+
import type { InfrastructureManifest, InfrastructureResponse } from '../store/infraClient';
|
|
5
|
+
|
|
6
|
+
const MANIFEST: InfrastructureManifest = {
|
|
7
|
+
version: 1,
|
|
8
|
+
scannedAt: '2026-07-12T00:00:00Z',
|
|
9
|
+
greenfield: false,
|
|
10
|
+
summary: 'Express app with Postgres (Prisma), Stripe, and SendGrid.',
|
|
11
|
+
items: [
|
|
12
|
+
{
|
|
13
|
+
id: 'postgres-main',
|
|
14
|
+
kind: 'database',
|
|
15
|
+
name: 'Postgres (Prisma)',
|
|
16
|
+
evidence: [{ file: 'prisma/schema.prisma', note: 'datasource db provider=postgresql' }],
|
|
17
|
+
suggestedSecretRefs: ['DATABASE_URL'],
|
|
18
|
+
suggestedVars: [],
|
|
19
|
+
notes: 'Schema defines User, Order, Invoice models.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'stripe',
|
|
23
|
+
kind: 'http-api',
|
|
24
|
+
name: 'Stripe',
|
|
25
|
+
evidence: [{ file: 'package.json', note: 'stripe dependency' }],
|
|
26
|
+
suggestedSecretRefs: ['STRIPE_SECRET_KEY'],
|
|
27
|
+
suggestedVars: [],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const html = (data: InfrastructureResponse | null): string =>
|
|
33
|
+
renderToStaticMarkup(<InfraPanel data={data} />);
|
|
34
|
+
|
|
35
|
+
describe('InfraPanel', () => {
|
|
36
|
+
it('renders the summary, each item card, kind, evidence, secretRefs and notes', () => {
|
|
37
|
+
const out = html({ present: true, manifest: MANIFEST });
|
|
38
|
+
expect(out).toContain('Express app with Postgres');
|
|
39
|
+
// Item names + kinds.
|
|
40
|
+
expect(out).toContain('Postgres (Prisma)');
|
|
41
|
+
expect(out).toContain('database');
|
|
42
|
+
expect(out).toContain('Stripe');
|
|
43
|
+
expect(out).toContain('http-api');
|
|
44
|
+
// Evidence file + note.
|
|
45
|
+
expect(out).toContain('prisma/schema.prisma');
|
|
46
|
+
expect(out).toContain('datasource db provider=postgresql');
|
|
47
|
+
// Secret ref chips (NAMES only).
|
|
48
|
+
expect(out).toContain('DATABASE_URL');
|
|
49
|
+
expect(out).toContain('STRIPE_SECRET_KEY');
|
|
50
|
+
// Notes.
|
|
51
|
+
expect(out).toContain('User, Order, Invoice');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders the not-scouted empty state with the scout button when absent', () => {
|
|
55
|
+
const out = renderToStaticMarkup(<InfraPanel data={{ present: false }} onScout={() => {}} />);
|
|
56
|
+
expect(out).toContain('Not scouted yet');
|
|
57
|
+
expect(out).toContain('emberflow/infrastructure.json');
|
|
58
|
+
expect(out).toContain('Scout infrastructure');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('treats a null (unreachable) response like not-scouted', () => {
|
|
62
|
+
const out = html(null);
|
|
63
|
+
expect(out).toContain('Not scouted yet');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders a distinct greenfield state', () => {
|
|
67
|
+
const out = html({
|
|
68
|
+
present: true,
|
|
69
|
+
manifest: { version: 1, greenfield: true, summary: 'Clean slate — no infrastructure yet.', items: [] },
|
|
70
|
+
});
|
|
71
|
+
expect(out).toContain('Greenfield project');
|
|
72
|
+
expect(out).toContain('Clean slate');
|
|
73
|
+
// No item cards.
|
|
74
|
+
expect(out).not.toContain('data-kind');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('disables the scout button with the reason tooltip when canScout is false (no agent CLI)', () => {
|
|
78
|
+
const out = renderToStaticMarkup(
|
|
79
|
+
<InfraPanel
|
|
80
|
+
data={{ present: false }}
|
|
81
|
+
onScout={() => {}}
|
|
82
|
+
canScout={false}
|
|
83
|
+
canScoutReason="Detect a coding agent first"
|
|
84
|
+
/>,
|
|
85
|
+
);
|
|
86
|
+
expect(out).toContain('disabled');
|
|
87
|
+
expect(out).toContain('Detect a coding agent first');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('shows the scouting state on the button when a scout run is active', () => {
|
|
91
|
+
const out = renderToStaticMarkup(
|
|
92
|
+
<InfraPanel data={{ present: false }} onScout={() => {}} scouting />,
|
|
93
|
+
);
|
|
94
|
+
expect(out).toContain('Scouting…');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('never renders a secret VALUE — only names appear', () => {
|
|
98
|
+
const out = html({ present: true, manifest: MANIFEST });
|
|
99
|
+
// The chip carries the NAME; there is no value anywhere in the manifest to leak.
|
|
100
|
+
expect(out).toContain('DATABASE_URL');
|
|
101
|
+
expect(out).not.toContain('postgres://');
|
|
102
|
+
});
|
|
103
|
+
});
|