@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,167 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
configBasenameFor,
|
|
5
|
+
decideRuntime,
|
|
6
|
+
resolveProjectDir,
|
|
7
|
+
TSX_MISSING_MESSAGE,
|
|
8
|
+
} from './runtime.mjs';
|
|
9
|
+
|
|
10
|
+
// A fake filesystem: `decideRuntime`/`configBasenameFor` take an injectable
|
|
11
|
+
// `fsExists`, so the whole decision matrix runs without touching disk.
|
|
12
|
+
function fsWith(paths: string[]) {
|
|
13
|
+
const set = new Set(paths);
|
|
14
|
+
return (p: import('node:fs').PathLike) => set.has(String(p));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// An installed JS consumer: package under node_modules, dist present, no src.
|
|
18
|
+
const CONSUMER_ROOT = '/app/node_modules/emberflow';
|
|
19
|
+
const PROJECT = '/app';
|
|
20
|
+
|
|
21
|
+
function consumerFs(configBasename?: string) {
|
|
22
|
+
const paths = [join(CONSUMER_ROOT, 'dist')];
|
|
23
|
+
if (configBasename) paths.push(join(PROJECT, configBasename));
|
|
24
|
+
return fsWith(paths);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('configBasenameFor', () => {
|
|
28
|
+
it('finds .mjs before .ts', () => {
|
|
29
|
+
const fsExists = fsWith([join(PROJECT, 'emberflow.config.mjs')]);
|
|
30
|
+
expect(configBasenameFor(PROJECT, fsExists)).toBe('emberflow.config.mjs');
|
|
31
|
+
});
|
|
32
|
+
it('finds a .ts config', () => {
|
|
33
|
+
const fsExists = fsWith([join(PROJECT, 'emberflow.config.ts')]);
|
|
34
|
+
expect(configBasenameFor(PROJECT, fsExists)).toBe('emberflow.config.ts');
|
|
35
|
+
});
|
|
36
|
+
it('returns undefined when no config exists', () => {
|
|
37
|
+
expect(configBasenameFor(PROJECT, () => false)).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('resolveProjectDir', () => {
|
|
42
|
+
it('prefers EMBERFLOW_PROJECT (absolute) over --project and cwd', () => {
|
|
43
|
+
expect(
|
|
44
|
+
resolveProjectDir(['--project', './other'], { EMBERFLOW_PROJECT: '/env/proj' }, '/cwd')
|
|
45
|
+
).toBe('/env/proj');
|
|
46
|
+
});
|
|
47
|
+
it('resolves a relative EMBERFLOW_PROJECT against cwd', () => {
|
|
48
|
+
expect(resolveProjectDir([], { EMBERFLOW_PROJECT: 'proj' }, '/cwd')).toBe('/cwd/proj');
|
|
49
|
+
});
|
|
50
|
+
it('falls back to --project when no env var', () => {
|
|
51
|
+
expect(resolveProjectDir(['--project', 'p'], {}, '/cwd')).toBe('/cwd/p');
|
|
52
|
+
});
|
|
53
|
+
it('falls back to cwd when neither is set', () => {
|
|
54
|
+
expect(resolveProjectDir([], {}, '/cwd')).toBe('/cwd');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('decideRuntime — JS consumer (installed under node_modules, dist present)', () => {
|
|
59
|
+
it('.mjs config → plain node, use dist, no tsx', () => {
|
|
60
|
+
const d = decideRuntime({
|
|
61
|
+
packageRoot: CONSUMER_ROOT,
|
|
62
|
+
projectDir: PROJECT,
|
|
63
|
+
env: {},
|
|
64
|
+
fsExists: consumerFs('emberflow.config.mjs'),
|
|
65
|
+
});
|
|
66
|
+
expect(d).toMatchObject({ needsTsx: false, runnerMode: 'node', useDist: true, sourceMode: false });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('no config → still plain node + dist (e.g. `init`)', () => {
|
|
70
|
+
const d = decideRuntime({
|
|
71
|
+
packageRoot: CONSUMER_ROOT,
|
|
72
|
+
projectDir: PROJECT,
|
|
73
|
+
env: {},
|
|
74
|
+
fsExists: consumerFs(),
|
|
75
|
+
});
|
|
76
|
+
expect(d).toMatchObject({ needsTsx: false, runnerMode: 'node', useDist: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('.ts config → needs tsx, still imports dist commands', () => {
|
|
80
|
+
const d = decideRuntime({
|
|
81
|
+
packageRoot: CONSUMER_ROOT,
|
|
82
|
+
projectDir: PROJECT,
|
|
83
|
+
env: {},
|
|
84
|
+
fsExists: consumerFs('emberflow.config.ts'),
|
|
85
|
+
});
|
|
86
|
+
expect(d).toMatchObject({ needsTsx: true, runnerMode: 'tsx', useDist: true });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('EMBERFLOW_FORCE_TSX=1 forces tsx even with a .mjs config', () => {
|
|
90
|
+
const d = decideRuntime({
|
|
91
|
+
packageRoot: CONSUMER_ROOT,
|
|
92
|
+
projectDir: PROJECT,
|
|
93
|
+
env: { EMBERFLOW_FORCE_TSX: '1' },
|
|
94
|
+
fsExists: consumerFs('emberflow.config.mjs'),
|
|
95
|
+
});
|
|
96
|
+
expect(d).toMatchObject({ needsTsx: true, runnerMode: 'tsx' });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('no dist installed → falls back to tsx AND requires it (importing .ts source without tsx would die with ERR_UNKNOWN_FILE_EXTENSION)', () => {
|
|
100
|
+
const d = decideRuntime({
|
|
101
|
+
packageRoot: CONSUMER_ROOT,
|
|
102
|
+
projectDir: PROJECT,
|
|
103
|
+
env: {},
|
|
104
|
+
fsExists: fsWith([join(PROJECT, 'emberflow.config.mjs')]), // no dist
|
|
105
|
+
});
|
|
106
|
+
expect(d).toMatchObject({ needsTsx: true, runnerMode: 'tsx', useDist: false });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('decideRuntime — source checkout (repo dev loop)', () => {
|
|
111
|
+
const REPO_ROOT = '/work/emberflow';
|
|
112
|
+
// src/ present, NOT under node_modules — the checkout signal. dist/ may or may
|
|
113
|
+
// not exist (after build:lib); source-mode must ignore it.
|
|
114
|
+
function repoFs(withDist: boolean, configBasename?: string) {
|
|
115
|
+
const paths = [join(REPO_ROOT, 'src')];
|
|
116
|
+
if (withDist) paths.push(join(REPO_ROOT, 'dist'));
|
|
117
|
+
if (configBasename) paths.push(join(PROJECT, configBasename));
|
|
118
|
+
return fsWith(paths);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
it('always tsx + source commands, even with a .mjs project config', () => {
|
|
122
|
+
const d = decideRuntime({
|
|
123
|
+
packageRoot: REPO_ROOT,
|
|
124
|
+
projectDir: PROJECT,
|
|
125
|
+
env: {},
|
|
126
|
+
fsExists: repoFs(false, 'emberflow.config.mjs'),
|
|
127
|
+
});
|
|
128
|
+
expect(d).toMatchObject({ needsTsx: true, runnerMode: 'tsx', useDist: false, sourceMode: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('dist present in the repo does NOT switch to node/dist', () => {
|
|
132
|
+
const d = decideRuntime({
|
|
133
|
+
packageRoot: REPO_ROOT,
|
|
134
|
+
projectDir: PROJECT,
|
|
135
|
+
env: {},
|
|
136
|
+
fsExists: repoFs(true, 'emberflow.config.mjs'),
|
|
137
|
+
});
|
|
138
|
+
expect(d).toMatchObject({ sourceMode: true, useDist: false, needsTsx: true });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('EMBERFLOW_SOURCE=0 escape hatch opts a checkout out of source mode', () => {
|
|
142
|
+
const d = decideRuntime({
|
|
143
|
+
packageRoot: REPO_ROOT,
|
|
144
|
+
projectDir: PROJECT,
|
|
145
|
+
env: { EMBERFLOW_SOURCE: '0' },
|
|
146
|
+
fsExists: repoFs(true, 'emberflow.config.mjs'),
|
|
147
|
+
});
|
|
148
|
+
expect(d).toMatchObject({ sourceMode: false, runnerMode: 'node', useDist: true });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('EMBERFLOW_SOURCE=1 forces source mode for an installed package', () => {
|
|
152
|
+
const d = decideRuntime({
|
|
153
|
+
packageRoot: CONSUMER_ROOT,
|
|
154
|
+
projectDir: PROJECT,
|
|
155
|
+
env: { EMBERFLOW_SOURCE: '1' },
|
|
156
|
+
fsExists: consumerFs('emberflow.config.mjs'),
|
|
157
|
+
});
|
|
158
|
+
expect(d).toMatchObject({ sourceMode: true, needsTsx: true, useDist: false });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('TSX_MISSING_MESSAGE', () => {
|
|
163
|
+
it('is actionable: names the install command and the JS-config alternative', () => {
|
|
164
|
+
expect(TSX_MISSING_MESSAGE).toContain('npm i -D tsx');
|
|
165
|
+
expect(TSX_MISSING_MESSAGE).toMatch(/emberflow\.config\.mjs/);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// bin/skillTargets.test.ts
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { detectHarnesses, resolveSkillDirs } from './skillTargets';
|
|
7
|
+
|
|
8
|
+
const dirs: string[] = [];
|
|
9
|
+
function scratch(): string {
|
|
10
|
+
const d = mkdtempSync(join(tmpdir(), 'ef-skilltargets-'));
|
|
11
|
+
dirs.push(d);
|
|
12
|
+
return d;
|
|
13
|
+
}
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('detectHarnesses', () => {
|
|
19
|
+
it('detects neither present when no markers exist', () => {
|
|
20
|
+
const cwd = scratch();
|
|
21
|
+
const home = scratch();
|
|
22
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: false, codex: false });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('detects claude present via repo dir', () => {
|
|
26
|
+
const cwd = scratch();
|
|
27
|
+
const home = scratch();
|
|
28
|
+
mkdirSync(join(cwd, '.claude'));
|
|
29
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: true, codex: false });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('detects claude present via home dir', () => {
|
|
33
|
+
const cwd = scratch();
|
|
34
|
+
const home = scratch();
|
|
35
|
+
mkdirSync(join(home, '.claude'));
|
|
36
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: true, codex: false });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('detects codex present via repo dir', () => {
|
|
40
|
+
const cwd = scratch();
|
|
41
|
+
const home = scratch();
|
|
42
|
+
mkdirSync(join(cwd, '.codex'));
|
|
43
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: false, codex: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('detects codex present via home dir', () => {
|
|
47
|
+
const cwd = scratch();
|
|
48
|
+
const home = scratch();
|
|
49
|
+
mkdirSync(join(home, '.codex'));
|
|
50
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: false, codex: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('detects both present', () => {
|
|
54
|
+
const cwd = scratch();
|
|
55
|
+
const home = scratch();
|
|
56
|
+
mkdirSync(join(cwd, '.claude'));
|
|
57
|
+
mkdirSync(join(home, '.codex'));
|
|
58
|
+
expect(detectHarnesses(cwd, home)).toEqual({ claude: true, codex: true });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('resolveSkillDirs', () => {
|
|
63
|
+
it('picks repo dirs for present harnesses at repo scope', () => {
|
|
64
|
+
const cwd = scratch();
|
|
65
|
+
const home = scratch();
|
|
66
|
+
const dirs = resolveSkillDirs({ claude: true, codex: true }, 'repo', cwd, home);
|
|
67
|
+
expect(dirs.sort()).toEqual(
|
|
68
|
+
[join(cwd, '.claude', 'skills'), join(cwd, '.codex', 'skills')].sort()
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('picks home dirs for present harnesses at global scope', () => {
|
|
73
|
+
const cwd = scratch();
|
|
74
|
+
const home = scratch();
|
|
75
|
+
const dirs = resolveSkillDirs({ claude: true, codex: true }, 'global', cwd, home);
|
|
76
|
+
expect(dirs.sort()).toEqual(
|
|
77
|
+
[join(home, '.claude', 'skills'), join(home, '.codex', 'skills')].sort()
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns only the present harness dir when just one is present (repo scope)', () => {
|
|
82
|
+
const cwd = scratch();
|
|
83
|
+
const home = scratch();
|
|
84
|
+
const dirs = resolveSkillDirs({ claude: true, codex: false }, 'repo', cwd, home);
|
|
85
|
+
expect(dirs).toEqual([join(cwd, '.claude', 'skills')]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns only the present harness dir when just one is present (global scope)', () => {
|
|
89
|
+
const cwd = scratch();
|
|
90
|
+
const home = scratch();
|
|
91
|
+
const dirs = resolveSkillDirs({ claude: false, codex: true }, 'global', cwd, home);
|
|
92
|
+
expect(dirs).toEqual([join(home, '.codex', 'skills')]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('falls back to claude at repo scope when neither harness is present', () => {
|
|
96
|
+
const cwd = scratch();
|
|
97
|
+
const home = scratch();
|
|
98
|
+
const dirs = resolveSkillDirs({ claude: false, codex: false }, 'repo', cwd, home);
|
|
99
|
+
expect(dirs).toEqual([join(cwd, '.claude', 'skills')]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('falls back to claude at global scope when neither harness is present', () => {
|
|
103
|
+
const cwd = scratch();
|
|
104
|
+
const home = scratch();
|
|
105
|
+
const dirs = resolveSkillDirs({ claude: false, codex: false }, 'global', cwd, home);
|
|
106
|
+
expect(dirs).toEqual([join(home, '.claude', 'skills')]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export type Harness = 'claude' | 'codex';
|
|
5
|
+
export interface HarnessPresence {
|
|
6
|
+
claude: boolean;
|
|
7
|
+
codex: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HARNESSES: Harness[] = ['claude', 'codex'];
|
|
11
|
+
|
|
12
|
+
/** Detect which harnesses the project/user uses: a harness counts as present if
|
|
13
|
+
* its repo dir (<cwd>/.claude or /.codex) OR its home dir (<home>/.claude or /.codex) exists. */
|
|
14
|
+
export function detectHarnesses(cwd: string, home: string): HarnessPresence {
|
|
15
|
+
const presence = {} as HarnessPresence;
|
|
16
|
+
for (const h of HARNESSES) {
|
|
17
|
+
presence[h] = existsSync(join(cwd, `.${h}`)) || existsSync(join(home, `.${h}`));
|
|
18
|
+
}
|
|
19
|
+
return presence;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The skills destination dirs to install into. For each PRESENT harness, choose
|
|
23
|
+
* the repo root (<cwd>/.<harness>/skills) when scope='repo', else the home root
|
|
24
|
+
* (<home>/.<harness>/skills). If NEITHER harness is present, default to Claude Code
|
|
25
|
+
* at the chosen scope (so a fresh project still gets skills). Returns absolute dirs. */
|
|
26
|
+
export function resolveSkillDirs(
|
|
27
|
+
presence: HarnessPresence,
|
|
28
|
+
scope: 'repo' | 'global',
|
|
29
|
+
cwd: string,
|
|
30
|
+
home: string
|
|
31
|
+
): string[] {
|
|
32
|
+
const root = scope === 'repo' ? cwd : home;
|
|
33
|
+
const present = HARNESSES.filter((h) => presence[h]);
|
|
34
|
+
const targets = present.length > 0 ? present : (['claude'] as Harness[]);
|
|
35
|
+
return targets.map((h) => join(root, `.${h}`, 'skills'));
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const skill = (name: string): string =>
|
|
6
|
+
readFileSync(join('templates', 'skills', name, 'SKILL.md'), 'utf8');
|
|
7
|
+
|
|
8
|
+
describe('Emberflow skill templates', () => {
|
|
9
|
+
it('prevents scenario-only verification from hiding missing Input defaults', () => {
|
|
10
|
+
const basics = skill('emberflow-basics');
|
|
11
|
+
const newWorkflow = skill('emberflow-new-workflow');
|
|
12
|
+
const modelProcess = skill('emberflow-model-process');
|
|
13
|
+
const review = skill('emberflow-review-workflow');
|
|
14
|
+
|
|
15
|
+
expect(basics).toContain('Plain Run');
|
|
16
|
+
expect(newWorkflow).toContain('Run both: plain Run and scenarios');
|
|
17
|
+
expect(modelProcess).toContain('Run both: plain Run and every scenario');
|
|
18
|
+
expect(review).toContain('Plain Run / default input check');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('documents the committed infrastructure manifest and points the intake skills at it first', () => {
|
|
22
|
+
const basics = skill('emberflow-basics');
|
|
23
|
+
const newWorkflow = skill('emberflow-new-workflow');
|
|
24
|
+
const modelProcess = skill('emberflow-model-process');
|
|
25
|
+
|
|
26
|
+
// Basics: file-layout entry — committed, structure-only, written by the scout.
|
|
27
|
+
expect(basics).toContain('emberflow/infrastructure.json');
|
|
28
|
+
expect(basics).toMatch(/committed.*structure-only|structure-only.*committed/i);
|
|
29
|
+
expect(basics).toMatch(/scout/i);
|
|
30
|
+
|
|
31
|
+
// Intake sections: check the manifest FIRST before interviewing about infra.
|
|
32
|
+
for (const skillText of [newWorkflow, modelProcess]) {
|
|
33
|
+
expect(skillText).toContain('emberflow/infrastructure.json');
|
|
34
|
+
expect(skillText).toMatch(/FIRST/);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "../node_modules/.tmp/tsconfig.bin.tsbuildinfo",
|
|
4
|
+
"target": "es2023",
|
|
5
|
+
"lib": ["ES2023", "DOM"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"module": "esnext",
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"erasableSyntaxOnly": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"allowJs": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["."],
|
|
22
|
+
"exclude": ["emberflow.mjs"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ParsedArgs {
|
|
2
|
+
command: string;
|
|
3
|
+
port?: number;
|
|
4
|
+
project?: string;
|
|
5
|
+
scenario?: string;
|
|
6
|
+
noSkills?: boolean;
|
|
7
|
+
scope?: 'global' | 'repo';
|
|
8
|
+
noLaunch?: boolean;
|
|
9
|
+
mock?: boolean;
|
|
10
|
+
js?: boolean;
|
|
11
|
+
ts?: boolean;
|
|
12
|
+
rest: string[];
|
|
13
|
+
}
|
|
14
|
+
/** How the bin decided to run — passed down from bin/emberflow.mjs (see
|
|
15
|
+
* bin/runtime.mjs). `runnerMode` selects `node dist/server/*.js` vs a tsx spawn;
|
|
16
|
+
* `packageRoot` is the real package root (bin/emberflow.mjs is never in dist, so
|
|
17
|
+
* it always resolves this correctly — unlike this module's own location, which
|
|
18
|
+
* is dist/bin/commands.js when compiled). */
|
|
19
|
+
export interface RuntimeContext {
|
|
20
|
+
runnerMode: 'node' | 'tsx';
|
|
21
|
+
packageRoot: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
24
|
+
export declare function runCommand(p: ParsedArgs, ctx?: RuntimeContext): Promise<number>;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { decideRuntime, resolveProjectDir } from './runtime.mjs';
|
|
8
|
+
export function parseArgs(argv) {
|
|
9
|
+
const [command = 'help', ...tail] = argv;
|
|
10
|
+
const out = { command, rest: [] };
|
|
11
|
+
for (let i = 0; i < tail.length; i++) {
|
|
12
|
+
const a = tail[i];
|
|
13
|
+
if (a === '--port')
|
|
14
|
+
out.port = Number(tail[++i]);
|
|
15
|
+
else if (a === '--project')
|
|
16
|
+
out.project = tail[++i];
|
|
17
|
+
else if (a === '--scenario')
|
|
18
|
+
out.scenario = tail[++i];
|
|
19
|
+
else if (a === '--no-skills')
|
|
20
|
+
out.noSkills = true;
|
|
21
|
+
else if (a === '--global')
|
|
22
|
+
out.scope = 'global';
|
|
23
|
+
else if (a === '--local')
|
|
24
|
+
out.scope = 'repo';
|
|
25
|
+
else if (a === '--no-launch')
|
|
26
|
+
out.noLaunch = true;
|
|
27
|
+
else if (a === '--mock')
|
|
28
|
+
out.mock = true;
|
|
29
|
+
else if (a === '--js')
|
|
30
|
+
out.js = true;
|
|
31
|
+
else if (a === '--ts')
|
|
32
|
+
out.ts = true;
|
|
33
|
+
else
|
|
34
|
+
out.rest.push(a);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** Ask the user whether to install skills globally or per-repo. Guarded on TTY so
|
|
39
|
+
* it never hangs a non-interactive run (tests, CI, piped input). */
|
|
40
|
+
function promptScope() {
|
|
41
|
+
if (!process.stdin.isTTY)
|
|
42
|
+
return Promise.resolve('repo');
|
|
43
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
44
|
+
return new Promise((res) => {
|
|
45
|
+
rl.question('Install Emberflow skills for [g]lobal (all projects) or [r]epo (this project)? [r] ', (answer) => {
|
|
46
|
+
rl.close();
|
|
47
|
+
res(answer.trim().toLowerCase().startsWith('g') ? 'global' : 'repo');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Ask the user which language to author their APIs/nodes in. Same TTY guard as
|
|
52
|
+
* promptScope — never hangs a non-interactive run (tests, CI, piped input),
|
|
53
|
+
* defaulting to javascript. */
|
|
54
|
+
function promptLanguage() {
|
|
55
|
+
if (!process.stdin.isTTY)
|
|
56
|
+
return Promise.resolve('javascript');
|
|
57
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
58
|
+
return new Promise((res) => {
|
|
59
|
+
rl.question('Author your APIs in [j]avascript or [t]ypescript? [j] ', (answer) => {
|
|
60
|
+
rl.close();
|
|
61
|
+
res(answer.trim().toLowerCase().startsWith('t') ? 'typescript' : 'javascript');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Fallback package root used when runCommand is called without a RuntimeContext
|
|
66
|
+
* (e.g. a direct unit test). In normal use bin/emberflow.mjs passes the real
|
|
67
|
+
* root — important because this module compiles to dist/bin/commands.js, whose
|
|
68
|
+
* own `..` is dist/, not the package root. */
|
|
69
|
+
const selfPkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
70
|
+
const defaultCtx = { runnerMode: 'tsx', packageRoot: selfPkgRoot };
|
|
71
|
+
/** Spawn `tsx <entry>` inheriting stdio, with extra env. Returns the child's exit code. */
|
|
72
|
+
function runTsx(ctx, entry, env, args = []) {
|
|
73
|
+
return new Promise((res) => {
|
|
74
|
+
const child = spawn('npx', ['tsx', join(ctx.packageRoot, entry), ...args], {
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
env: { ...process.env, ...env },
|
|
77
|
+
});
|
|
78
|
+
child.on('exit', (code) => res(code ?? 0));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** Spawn `node <entry>` inheriting stdio, with extra env. Used for JS consumers,
|
|
82
|
+
* where the compiled dist/server runs on plain Node (no tsx child — so it works
|
|
83
|
+
* inside the codex sandbox). */
|
|
84
|
+
function runNode(entry, env, args = []) {
|
|
85
|
+
return new Promise((res) => {
|
|
86
|
+
const child = spawn(process.execPath, [entry, ...args], {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
env: { ...process.env, ...env },
|
|
89
|
+
});
|
|
90
|
+
child.on('exit', (code) => res(code ?? 0));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** Launch a server entry ('index' or 'mcp') in the mode the bin decided: plain
|
|
94
|
+
* `node dist/server/<name>.js` for JS consumers, `tsx server/<name>.ts` otherwise. */
|
|
95
|
+
function launchServer(ctx, name, env, args = []) {
|
|
96
|
+
if (ctx.runnerMode === 'node') {
|
|
97
|
+
return runNode(join(ctx.packageRoot, 'dist', 'server', `${name}.js`), env, args);
|
|
98
|
+
}
|
|
99
|
+
return runTsx(ctx, join('server', `${name}.ts`), env, args);
|
|
100
|
+
}
|
|
101
|
+
/** Build (if needed) and launch the studio dev server. Shared by `dev` and by
|
|
102
|
+
* `init` (which launches after scaffolding unless --no-launch is passed). */
|
|
103
|
+
async function runDev(p, ctx) {
|
|
104
|
+
// Same precedence as bin/runtime.mjs's decideRuntime (EMBERFLOW_PROJECT env →
|
|
105
|
+
// --project flag → cwd) so the launched process boots the same project the
|
|
106
|
+
// runtime decision was made against — a bare --project flag no longer
|
|
107
|
+
// silently overwrites an already-set EMBERFLOW_PROJECT.
|
|
108
|
+
const project = resolveProjectDir(p.project ? ['--project', p.project] : [], process.env, process.cwd());
|
|
109
|
+
// From a consumer install the shipped studio-dist exists; from source it may not.
|
|
110
|
+
if (!existsSync(join(ctx.packageRoot, 'studio-dist'))) {
|
|
111
|
+
console.log('[emberflow] building studio…');
|
|
112
|
+
await runTsx(ctx, 'node_modules/.bin/vite', {}, ['build']).catch(() => 0);
|
|
113
|
+
}
|
|
114
|
+
return launchServer(ctx, 'index', {
|
|
115
|
+
EMBERFLOW_SERVE_STUDIO: '1',
|
|
116
|
+
EMBERFLOW_PROJECT: project,
|
|
117
|
+
// `dev` runs a single (non-watch) process, so opening the browser once
|
|
118
|
+
// is the intended UX. (The server default is off so this repo's watch-mode
|
|
119
|
+
// `npm run server` doesn't spawn a tab per restart — see server/index.ts.)
|
|
120
|
+
EMBERFLOW_OPEN_BROWSER: '1',
|
|
121
|
+
...(p.port ? { EMBERFLOW_RUNNER_PORT: String(p.port) } : {}),
|
|
122
|
+
...(p.mock ? { EMBERFLOW_MOCK: '1' } : {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Run the runner headless: API + operation routes, NO studio, NO browser.
|
|
126
|
+
* This is the production-ish API host — `dev` additionally serves the studio
|
|
127
|
+
* and opens a browser tab. */
|
|
128
|
+
function runServe(p, ctx) {
|
|
129
|
+
// See runDev's comment: same EMBERFLOW_PROJECT-first precedence as
|
|
130
|
+
// bin/runtime.mjs's resolveProjectDir.
|
|
131
|
+
const project = resolveProjectDir(p.project ? ['--project', p.project] : [], process.env, process.cwd());
|
|
132
|
+
return launchServer(ctx, 'index', {
|
|
133
|
+
EMBERFLOW_PROJECT: project,
|
|
134
|
+
...(p.port ? { EMBERFLOW_RUNNER_PORT: String(p.port) } : {}),
|
|
135
|
+
...(p.mock ? { EMBERFLOW_MOCK: '1' } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export async function runCommand(p, ctx = defaultCtx) {
|
|
139
|
+
const pkgRoot = ctx.packageRoot;
|
|
140
|
+
switch (p.command) {
|
|
141
|
+
case 'dev':
|
|
142
|
+
return runDev(p, ctx);
|
|
143
|
+
case 'serve':
|
|
144
|
+
return runServe(p, ctx);
|
|
145
|
+
case 'mcp':
|
|
146
|
+
return launchServer(ctx, 'mcp', {});
|
|
147
|
+
case 'run': {
|
|
148
|
+
// In-process via tsx's register() loader — NO `tsx` child spawn, so it
|
|
149
|
+
// works inside the codex sandbox (tsx's IPC pipe is blocked there).
|
|
150
|
+
const args = ['run', ...p.rest, ...(p.scenario ? ['--scenario', p.scenario] : [])];
|
|
151
|
+
return (await import('../server/cli.js')).runCli(args);
|
|
152
|
+
}
|
|
153
|
+
case 'list-nodes':
|
|
154
|
+
case 'node-schema':
|
|
155
|
+
case 'list-workflows':
|
|
156
|
+
case 'get-workflow':
|
|
157
|
+
case 'list-environments':
|
|
158
|
+
case 'login-environment':
|
|
159
|
+
case 'set-environment-auth':
|
|
160
|
+
case 'serving':
|
|
161
|
+
case 'validate':
|
|
162
|
+
case 'publish':
|
|
163
|
+
case 'save':
|
|
164
|
+
case 'samples':
|
|
165
|
+
case 'create':
|
|
166
|
+
case 'delete':
|
|
167
|
+
case 'rename': {
|
|
168
|
+
// Rich CLI operation commands, also in-process (no `tsx` child spawn).
|
|
169
|
+
return (await import('../server/cli.js')).runCli([p.command, ...p.rest]);
|
|
170
|
+
}
|
|
171
|
+
case 'test': {
|
|
172
|
+
// In-process scenario suite runner — NO runner process. Needs the
|
|
173
|
+
// project dir (server/cli.ts's `test` case reads EMBERFLOW_PROJECT),
|
|
174
|
+
// same convention runDev/runServe use for --project.
|
|
175
|
+
if (p.project)
|
|
176
|
+
process.env.EMBERFLOW_PROJECT = resolve(process.cwd(), p.project);
|
|
177
|
+
// --mock is parsed globally (dev/serve read it as EMBERFLOW_MOCK), so it
|
|
178
|
+
// never lands in p.rest — thread it through explicitly here.
|
|
179
|
+
return (await import('../server/cli.js')).runCli(['test', ...p.rest, ...(p.mock ? ['--mock'] : [])]);
|
|
180
|
+
}
|
|
181
|
+
case 'doctor': {
|
|
182
|
+
// In-process operation diagnostics — NO runner process, same
|
|
183
|
+
// EMBERFLOW_PROJECT convention as `test`.
|
|
184
|
+
if (p.project)
|
|
185
|
+
process.env.EMBERFLOW_PROJECT = resolve(process.cwd(), p.project);
|
|
186
|
+
return (await import('../server/cli.js')).runCli(['doctor', ...p.rest]);
|
|
187
|
+
}
|
|
188
|
+
case 'init': {
|
|
189
|
+
// bin/init compiles alongside this module (dist/bin/init.js when built),
|
|
190
|
+
// so resolve the guard against THIS module's own location + extension —
|
|
191
|
+
// init.ts under tsx source, init.js under plain-node dist.
|
|
192
|
+
const initExt = import.meta.url.endsWith('.js') ? '.js' : '.ts';
|
|
193
|
+
const initEntry = fileURLToPath(new URL(`./init${initExt}`, import.meta.url));
|
|
194
|
+
if (!existsSync(initEntry)) {
|
|
195
|
+
console.log('emberflow init lands in a later step');
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
if (p.js && p.ts) {
|
|
199
|
+
console.error('[emberflow] --js and --ts are mutually exclusive');
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const mod = (await import('./init.js'));
|
|
204
|
+
const scope = p.scope ?? (await promptScope());
|
|
205
|
+
const language = p.js ? 'javascript' : p.ts ? 'typescript' : await promptLanguage();
|
|
206
|
+
const code = await mod.runInit(process.cwd(), p.noSkills
|
|
207
|
+
? { skills: false, packageRoot: pkgRoot, language }
|
|
208
|
+
: { skills: { scope, home: homedir() }, packageRoot: pkgRoot, language });
|
|
209
|
+
if (code !== 0)
|
|
210
|
+
return code;
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log('[emberflow] next steps:');
|
|
213
|
+
console.log(' - run `npx emberflow dev` any time to relaunch the studio');
|
|
214
|
+
console.log(p.noSkills
|
|
215
|
+
? ' - skills were not installed (--no-skills)'
|
|
216
|
+
: ` - skills installed at ${scope === 'global' ? '~' : '.'}/.claude (and/or .codex) /skills`);
|
|
217
|
+
console.log(' - your operations live in emberflow/apis (open the studio to build them)');
|
|
218
|
+
console.log('');
|
|
219
|
+
if (p.noLaunch)
|
|
220
|
+
return 0;
|
|
221
|
+
// `ctx` reflects the runtime decision made at bin startup — BEFORE
|
|
222
|
+
// scaffolding existed. For a fresh TS project that decision was made with
|
|
223
|
+
// no config on disk (→ node mode), which then dies launching against the
|
|
224
|
+
// just-scaffolded emberflow.config.ts. Re-decide against the now-real
|
|
225
|
+
// project instead of trusting the stale context.
|
|
226
|
+
if (language === 'typescript') {
|
|
227
|
+
if (!mod.tsxResolvable(process.cwd())) {
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log('[emberflow] tsx is not installed — skipping auto-launch.');
|
|
230
|
+
console.log(' npm i -D tsx typescript');
|
|
231
|
+
console.log(' npx emberflow dev');
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
const decision = decideRuntime({ packageRoot: pkgRoot, projectDir: process.cwd(), env: process.env });
|
|
235
|
+
console.log('[emberflow] Starting Emberflow…');
|
|
236
|
+
return runDev(p, { runnerMode: decision.runnerMode, packageRoot: pkgRoot });
|
|
237
|
+
}
|
|
238
|
+
console.log('[emberflow] Starting Emberflow…');
|
|
239
|
+
return runDev(p, ctx);
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
console.error('[emberflow] init failed:', err instanceof Error ? err.message : err);
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
default:
|
|
247
|
+
console.log('Usage: emberflow <dev|serve|mcp|init|run|test|doctor|list-nodes|node-schema|list-workflows|get-workflow|list-environments|login-environment|set-environment-auth|serving|validate|publish|save|create|delete|rename|samples> [--port N] [--project DIR] [--scenario NAME] [--no-skills] [--global|--local] [--no-launch] [--mock] [--js|--ts]\n' +
|
|
248
|
+
' test [opId] [--environment NAME] [--json] Run scenario expectations in-process (no runner) — exit 0/1/2\n' +
|
|
249
|
+
' doctor [opId] [--fix] Diagnose operation(s) in-process (no runner); --fix seeds param defaults — exit 0/1/2');
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Whether `tsx` resolves from the project's own node_modules (peer dep — optional).
|
|
2
|
+
* Exported so bin/commands.ts can reuse it when deciding whether `init --ts` can
|
|
3
|
+
* launch dev immediately after scaffolding, or must defer to the user. */
|
|
4
|
+
export declare function tsxResolvable(cwd: string): boolean;
|
|
5
|
+
export declare function runInit(cwd: string, opts?: {
|
|
6
|
+
skills?: false | {
|
|
7
|
+
scope: 'repo' | 'global';
|
|
8
|
+
home: string;
|
|
9
|
+
};
|
|
10
|
+
/** Real package root, passed by the bin. Falls back to this module's own
|
|
11
|
+
* location — which is wrong when compiled to dist/bin/init.js (its `..` is
|
|
12
|
+
* dist/, not the package root that holds templates/), so the bin supplies it. */
|
|
13
|
+
packageRoot?: string;
|
|
14
|
+
/** Which language to scaffold the config/nodes in. Defaults to javascript
|
|
15
|
+
* (matches the non-TTY prompt default in bin/commands.ts). */
|
|
16
|
+
language?: 'javascript' | 'typescript';
|
|
17
|
+
}): Promise<number>;
|