enya-agent 0.1.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/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
//! Configuration Types - Core configuration structure
|
|
2
|
+
//!
|
|
3
|
+
//! This module defines the configuration structure that matches the TypeScript schema.
|
|
4
|
+
//! It's kept in sync with packages/enact-schemas/src/config.schemas.ts
|
|
5
|
+
|
|
6
|
+
use serde::{Deserialize, Serialize};
|
|
7
|
+
|
|
8
|
+
/// Runtime mode
|
|
9
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
|
10
|
+
pub enum RuntimeMode {
|
|
11
|
+
#[default]
|
|
12
|
+
#[serde(rename = "local")]
|
|
13
|
+
Local,
|
|
14
|
+
#[serde(rename = "airgapped")]
|
|
15
|
+
AirGapped,
|
|
16
|
+
#[serde(rename = "cloud")]
|
|
17
|
+
Cloud,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl RuntimeMode {
|
|
21
|
+
/// Parse a runtime mode string into the enum (defaults to Local)
|
|
22
|
+
pub fn from_str(value: &str) -> Self {
|
|
23
|
+
match value.to_ascii_lowercase().as_str() {
|
|
24
|
+
"airgapped" => RuntimeMode::AirGapped,
|
|
25
|
+
"cloud" => RuntimeMode::Cloud,
|
|
26
|
+
_ => RuntimeMode::Local,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
impl ToString for RuntimeMode {
|
|
33
|
+
fn to_string(&self) -> String {
|
|
34
|
+
match self {
|
|
35
|
+
RuntimeMode::Local => "local".to_string(),
|
|
36
|
+
RuntimeMode::AirGapped => "airgapped".to_string(),
|
|
37
|
+
RuntimeMode::Cloud => "cloud".to_string(),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Provider configuration
|
|
43
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
|
44
|
+
pub struct Providers {
|
|
45
|
+
pub azure: Option<AzureProvider>,
|
|
46
|
+
pub anthropic: Option<AnthropicProvider>,
|
|
47
|
+
pub openai: Option<OpenAIProvider>,
|
|
48
|
+
pub ollama: Option<OllamaProvider>,
|
|
49
|
+
pub google: Option<GoogleProvider>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
53
|
+
pub struct AzureProvider {
|
|
54
|
+
pub endpoint: Option<String>,
|
|
55
|
+
pub api_key: Option<String>,
|
|
56
|
+
pub deployment_name: Option<String>,
|
|
57
|
+
#[serde(default = "default_api_version")]
|
|
58
|
+
pub api_version: String,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn default_api_version() -> String {
|
|
62
|
+
"2024-02-15-preview".to_string()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
66
|
+
pub struct AnthropicProvider {
|
|
67
|
+
pub api_key: Option<String>,
|
|
68
|
+
#[serde(default = "default_anthropic_base_url")]
|
|
69
|
+
pub base_url: String,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn default_anthropic_base_url() -> String {
|
|
73
|
+
"https://api.anthropic.com".to_string()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
77
|
+
pub struct OpenAIProvider {
|
|
78
|
+
pub api_key: Option<String>,
|
|
79
|
+
#[serde(default = "default_openai_base_url")]
|
|
80
|
+
pub base_url: String,
|
|
81
|
+
pub organization: Option<String>,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn default_openai_base_url() -> String {
|
|
85
|
+
"https://api.openai.com/v1".to_string()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
89
|
+
pub struct OllamaProvider {
|
|
90
|
+
#[serde(default = "default_ollama_base_url")]
|
|
91
|
+
pub base_url: String,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn default_ollama_base_url() -> String {
|
|
95
|
+
"http://localhost:11434".to_string()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
99
|
+
pub struct GoogleProvider {
|
|
100
|
+
pub api_key: Option<String>,
|
|
101
|
+
#[serde(default = "default_google_base_url")]
|
|
102
|
+
pub base_url: String,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn default_google_base_url() -> String {
|
|
106
|
+
"https://generativelanguage.googleapis.com/v1".to_string()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Runtime configuration
|
|
110
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
111
|
+
pub struct Runtime {
|
|
112
|
+
#[serde(default)]
|
|
113
|
+
pub mode: RuntimeMode,
|
|
114
|
+
#[serde(default = "default_max_concurrent")]
|
|
115
|
+
pub max_concurrent_executions: u32,
|
|
116
|
+
#[serde(default = "default_timeout")]
|
|
117
|
+
pub default_timeout: u64,
|
|
118
|
+
#[serde(default = "default_true")]
|
|
119
|
+
pub enable_telemetry: bool,
|
|
120
|
+
#[serde(default = "default_true")]
|
|
121
|
+
pub allow_network: bool,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn default_max_concurrent() -> u32 {
|
|
125
|
+
10
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn default_timeout() -> u64 {
|
|
129
|
+
30000
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn default_true() -> bool {
|
|
133
|
+
true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
impl Default for Runtime {
|
|
137
|
+
fn default() -> Self {
|
|
138
|
+
Self {
|
|
139
|
+
mode: RuntimeMode::Local,
|
|
140
|
+
max_concurrent_executions: 10,
|
|
141
|
+
default_timeout: 30000,
|
|
142
|
+
enable_telemetry: true,
|
|
143
|
+
allow_network: true,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Storage configuration
|
|
149
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
|
150
|
+
pub struct Storage {
|
|
151
|
+
#[serde(default = "default_event_store")]
|
|
152
|
+
pub event_store: EventStore,
|
|
153
|
+
#[serde(default = "default_state_store")]
|
|
154
|
+
pub state_store: StateStore,
|
|
155
|
+
#[serde(default = "default_filesystem_store")]
|
|
156
|
+
pub artifact_store: ArtifactStore,
|
|
157
|
+
#[serde(default = "default_sqlite_vector_store")]
|
|
158
|
+
pub vector_store: VectorStore,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
162
|
+
pub struct EventStore {
|
|
163
|
+
#[serde(default = "default_sqlite")]
|
|
164
|
+
pub r#type: String,
|
|
165
|
+
pub path: Option<String>,
|
|
166
|
+
pub dsn: Option<String>,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn default_sqlite() -> String {
|
|
170
|
+
"sqlite".to_string()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl Default for EventStore {
|
|
174
|
+
fn default() -> Self {
|
|
175
|
+
Self {
|
|
176
|
+
r#type: "sqlite".to_string(),
|
|
177
|
+
path: None,
|
|
178
|
+
dsn: None,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn default_event_store() -> EventStore {
|
|
184
|
+
EventStore::default()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
impl Default for StateStore {
|
|
188
|
+
fn default() -> Self {
|
|
189
|
+
Self {
|
|
190
|
+
r#type: "sqlite".to_string(),
|
|
191
|
+
path: None,
|
|
192
|
+
dsn: None,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn default_state_store() -> StateStore {
|
|
198
|
+
StateStore::default()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
202
|
+
pub struct StateStore {
|
|
203
|
+
#[serde(default = "default_sqlite")]
|
|
204
|
+
pub r#type: String,
|
|
205
|
+
pub path: Option<String>,
|
|
206
|
+
pub dsn: Option<String>,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
210
|
+
pub struct ArtifactStore {
|
|
211
|
+
#[serde(default = "default_filesystem")]
|
|
212
|
+
pub r#type: String,
|
|
213
|
+
pub path: Option<String>,
|
|
214
|
+
#[serde(default = "default_zstd")]
|
|
215
|
+
pub compression: String,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn default_filesystem() -> String {
|
|
219
|
+
"filesystem".to_string()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fn default_zstd() -> String {
|
|
223
|
+
"zstd".to_string()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
impl Default for ArtifactStore {
|
|
227
|
+
fn default() -> Self {
|
|
228
|
+
Self {
|
|
229
|
+
r#type: "filesystem".to_string(),
|
|
230
|
+
path: None,
|
|
231
|
+
compression: "zstd".to_string(),
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn default_filesystem_store() -> ArtifactStore {
|
|
237
|
+
ArtifactStore::default()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
241
|
+
pub struct VectorStore {
|
|
242
|
+
#[serde(default = "default_sqlite")]
|
|
243
|
+
pub r#type: String,
|
|
244
|
+
pub url: Option<String>,
|
|
245
|
+
pub collection: Option<String>,
|
|
246
|
+
pub path: Option<String>,
|
|
247
|
+
pub dsn: Option<String>,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
impl Default for VectorStore {
|
|
251
|
+
fn default() -> Self {
|
|
252
|
+
Self {
|
|
253
|
+
r#type: "sqlite".to_string(),
|
|
254
|
+
url: None,
|
|
255
|
+
collection: None,
|
|
256
|
+
path: None,
|
|
257
|
+
dsn: None,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn default_sqlite_vector_store() -> VectorStore {
|
|
263
|
+
VectorStore::default()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Tools configuration
|
|
267
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
|
268
|
+
pub struct Tools {
|
|
269
|
+
#[serde(default)]
|
|
270
|
+
pub ingestion: IngestionTools,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
|
274
|
+
pub struct IngestionTools {
|
|
275
|
+
#[serde(default = "default_pdf")]
|
|
276
|
+
pub pdf: PdfIngestion,
|
|
277
|
+
#[serde(default = "default_ocr")]
|
|
278
|
+
pub ocr: OcrIngestion,
|
|
279
|
+
#[serde(default = "default_embeddings")]
|
|
280
|
+
pub embeddings: EmbeddingsIngestion,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
284
|
+
pub struct PdfIngestion {
|
|
285
|
+
#[serde(default = "default_pdfium")]
|
|
286
|
+
pub engine: String,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
impl Default for PdfIngestion {
|
|
290
|
+
fn default() -> Self {
|
|
291
|
+
Self {
|
|
292
|
+
engine: "pdfium".to_string(),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn default_pdfium() -> String {
|
|
298
|
+
"pdfium".to_string()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fn default_pdf() -> PdfIngestion {
|
|
302
|
+
PdfIngestion {
|
|
303
|
+
engine: "pdfium".to_string(),
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
308
|
+
pub struct OcrIngestion {
|
|
309
|
+
#[serde(default = "default_tesseract")]
|
|
310
|
+
pub engine: String,
|
|
311
|
+
#[serde(default = "default_languages")]
|
|
312
|
+
pub languages: Vec<String>,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
impl Default for OcrIngestion {
|
|
316
|
+
fn default() -> Self {
|
|
317
|
+
Self {
|
|
318
|
+
engine: "tesseract".to_string(),
|
|
319
|
+
languages: vec!["eng".to_string()],
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fn default_tesseract() -> String {
|
|
325
|
+
"tesseract".to_string()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
fn default_languages() -> Vec<String> {
|
|
329
|
+
vec!["eng".to_string()]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
fn default_ocr() -> OcrIngestion {
|
|
333
|
+
OcrIngestion {
|
|
334
|
+
engine: "tesseract".to_string(),
|
|
335
|
+
languages: vec!["eng".to_string()],
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
340
|
+
pub struct EmbeddingsIngestion {
|
|
341
|
+
#[serde(default = "default_fastembed")]
|
|
342
|
+
pub engine: String,
|
|
343
|
+
pub model: Option<String>,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
impl Default for EmbeddingsIngestion {
|
|
347
|
+
fn default() -> Self {
|
|
348
|
+
Self {
|
|
349
|
+
engine: "fastembed".to_string(),
|
|
350
|
+
model: None,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fn default_fastembed() -> String {
|
|
356
|
+
"fastembed".to_string()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fn default_embeddings() -> EmbeddingsIngestion {
|
|
360
|
+
EmbeddingsIngestion {
|
|
361
|
+
engine: "fastembed".to_string(),
|
|
362
|
+
model: None,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// Cloud configuration
|
|
367
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
368
|
+
pub struct Cloud {
|
|
369
|
+
pub api_url: Option<String>,
|
|
370
|
+
pub tenant_id: Option<String>,
|
|
371
|
+
#[serde(default)]
|
|
372
|
+
pub auto_sync: bool,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// Main configuration structure
|
|
376
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
377
|
+
pub struct Config {
|
|
378
|
+
#[serde(default)]
|
|
379
|
+
pub providers: Providers,
|
|
380
|
+
#[serde(default)]
|
|
381
|
+
pub runtime: Runtime,
|
|
382
|
+
#[serde(default)]
|
|
383
|
+
pub storage: Storage,
|
|
384
|
+
#[serde(default)]
|
|
385
|
+
pub tools: Tools,
|
|
386
|
+
pub cloud: Option<Cloud>,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
impl Default for Config {
|
|
390
|
+
fn default() -> Self {
|
|
391
|
+
Self {
|
|
392
|
+
providers: Providers::default(),
|
|
393
|
+
runtime: Runtime::default(),
|
|
394
|
+
storage: Storage::default(),
|
|
395
|
+
tools: Tools::default(),
|
|
396
|
+
cloud: None,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
//! Encrypted File Storage - Secure storage for non-sensitive settings
|
|
2
|
+
//!
|
|
3
|
+
//! Stores configuration in an encrypted file using AES-256-GCM.
|
|
4
|
+
//! The encryption key is derived from environment variables.
|
|
5
|
+
|
|
6
|
+
use aes_gcm::{
|
|
7
|
+
aead::{Aead, AeadCore, KeyInit, OsRng},
|
|
8
|
+
Aes256Gcm, Key, Nonce,
|
|
9
|
+
};
|
|
10
|
+
use anyhow::{Context, Result};
|
|
11
|
+
use serde::{Deserialize, Serialize};
|
|
12
|
+
use std::{
|
|
13
|
+
fs,
|
|
14
|
+
path::{Path, PathBuf},
|
|
15
|
+
};
|
|
16
|
+
use tracing::debug;
|
|
17
|
+
|
|
18
|
+
use crate::secrets::SecretManager;
|
|
19
|
+
|
|
20
|
+
const ENCRYPTION_KEY_NAME: &str = "enact.config.encryption_key";
|
|
21
|
+
const NONCE_SIZE: usize = 12; // 96 bits for GCM
|
|
22
|
+
|
|
23
|
+
/// Encrypted configuration store
|
|
24
|
+
pub struct EncryptedStore {
|
|
25
|
+
config_path: PathBuf,
|
|
26
|
+
secrets: SecretManager,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
30
|
+
struct EncryptedData {
|
|
31
|
+
nonce: Vec<u8>,
|
|
32
|
+
ciphertext: Vec<u8>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl EncryptedStore {
|
|
36
|
+
/// Create a new encrypted store
|
|
37
|
+
///
|
|
38
|
+
/// # Arguments
|
|
39
|
+
/// * `config_path` - Path to the encrypted config file
|
|
40
|
+
pub fn new(config_path: impl AsRef<Path>) -> Result<Self> {
|
|
41
|
+
// Always use SecretManager
|
|
42
|
+
let secrets = SecretManager::new();
|
|
43
|
+
Self::with_secrets(config_path, secrets)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Create a new encrypted store with a specific secret manager
|
|
47
|
+
///
|
|
48
|
+
/// # Arguments
|
|
49
|
+
/// * `config_path` - Path to the encrypted config file
|
|
50
|
+
/// * `secrets` - Secret manager to use
|
|
51
|
+
pub fn with_secrets(config_path: impl AsRef<Path>, secrets: SecretManager) -> Result<Self> {
|
|
52
|
+
let config_path = config_path.as_ref().to_path_buf();
|
|
53
|
+
|
|
54
|
+
// Ensure parent directory exists
|
|
55
|
+
if let Some(parent) = config_path.parent() {
|
|
56
|
+
fs::create_dir_all(parent).context("Failed to create config directory")?;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Ok(Self {
|
|
60
|
+
config_path,
|
|
61
|
+
secrets,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Get or create the encryption key
|
|
66
|
+
fn get_encryption_key(&self) -> Result<Key<Aes256Gcm>> {
|
|
67
|
+
// Try to get existing key from environment
|
|
68
|
+
if let Some(key_str) = self.secrets.get(ENCRYPTION_KEY_NAME)? {
|
|
69
|
+
// Decode hex string to key
|
|
70
|
+
let key_bytes = hex::decode(&key_str).context("Failed to decode encryption key")?;
|
|
71
|
+
if key_bytes.len() == 32 {
|
|
72
|
+
return Ok(*Key::<Aes256Gcm>::from_slice(&key_bytes));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Generate new key if not found
|
|
77
|
+
// Since we can't persist it to .env automatically, we must warn the user
|
|
78
|
+
let key = Aes256Gcm::generate_key(&mut OsRng);
|
|
79
|
+
let key_hex = hex::encode(key.as_slice());
|
|
80
|
+
|
|
81
|
+
// Try to store in secrets (works if mock store, fails/warns if .env)
|
|
82
|
+
if let Err(_) = self.secrets.set(ENCRYPTION_KEY_NAME, &key_hex) {
|
|
83
|
+
eprintln!("⚠️ WARNING: No encryption key found in environment.");
|
|
84
|
+
eprintln!(" Generated temporary key: {}", key_hex);
|
|
85
|
+
eprintln!(
|
|
86
|
+
" Set ENACT_CONFIG_ENCRYPTION_KEY={} in your .env file to persist configuration.",
|
|
87
|
+
key_hex
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
debug!("Generated and stored new encryption key (mock)");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
debug!("Using generated encryption key");
|
|
94
|
+
Ok(key)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Encrypt and store configuration
|
|
98
|
+
///
|
|
99
|
+
/// # Arguments
|
|
100
|
+
/// * `config` - The configuration to store (as JSON string)
|
|
101
|
+
pub fn save(&self, config: &str) -> Result<()> {
|
|
102
|
+
let key = self.get_encryption_key()?;
|
|
103
|
+
let cipher = Aes256Gcm::new(&key);
|
|
104
|
+
|
|
105
|
+
// Generate nonce
|
|
106
|
+
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
|
107
|
+
|
|
108
|
+
// Encrypt
|
|
109
|
+
let ciphertext = cipher
|
|
110
|
+
.encrypt(&nonce, config.as_bytes())
|
|
111
|
+
.map_err(|_| anyhow::anyhow!("Failed to encrypt configuration"))?;
|
|
112
|
+
|
|
113
|
+
// Store encrypted data
|
|
114
|
+
let encrypted_data = EncryptedData {
|
|
115
|
+
nonce: nonce.to_vec(),
|
|
116
|
+
ciphertext,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
let json = serde_json::to_string_pretty(&encrypted_data)
|
|
120
|
+
.context("Failed to serialize encrypted data")?;
|
|
121
|
+
|
|
122
|
+
fs::write(&self.config_path, json).context("Failed to write encrypted config file")?;
|
|
123
|
+
|
|
124
|
+
debug!("Saved encrypted configuration to {:?}", self.config_path);
|
|
125
|
+
Ok(())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Load and decrypt configuration
|
|
129
|
+
///
|
|
130
|
+
/// # Returns
|
|
131
|
+
/// * `Ok(Some(config))` if the config exists and was decrypted successfully
|
|
132
|
+
/// * `Ok(None)` if the config file doesn't exist
|
|
133
|
+
/// * `Err` if there was an error reading or decrypting
|
|
134
|
+
pub fn load(&self) -> Result<Option<String>> {
|
|
135
|
+
if !self.config_path.exists() {
|
|
136
|
+
debug!("Config file does not exist: {:?}", self.config_path);
|
|
137
|
+
return Ok(None);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let json = fs::read_to_string(&self.config_path)
|
|
141
|
+
.context("Failed to read encrypted config file")?;
|
|
142
|
+
|
|
143
|
+
let encrypted_data: EncryptedData =
|
|
144
|
+
serde_json::from_str(&json).context("Failed to parse encrypted config file")?;
|
|
145
|
+
|
|
146
|
+
let key = self.get_encryption_key()?;
|
|
147
|
+
let cipher = Aes256Gcm::new(&key);
|
|
148
|
+
|
|
149
|
+
// Reconstruct nonce
|
|
150
|
+
if encrypted_data.nonce.len() != NONCE_SIZE {
|
|
151
|
+
return Err(anyhow::anyhow!("Invalid nonce size"));
|
|
152
|
+
}
|
|
153
|
+
let nonce = Nonce::from_slice(&encrypted_data.nonce);
|
|
154
|
+
|
|
155
|
+
// Decrypt
|
|
156
|
+
let plaintext = cipher
|
|
157
|
+
.decrypt(nonce, encrypted_data.ciphertext.as_ref())
|
|
158
|
+
.map_err(|_| {
|
|
159
|
+
anyhow::anyhow!(
|
|
160
|
+
"Failed to decrypt configuration - Check your ENACT_CONFIG_ENCRYPTION_KEY"
|
|
161
|
+
)
|
|
162
|
+
})?;
|
|
163
|
+
|
|
164
|
+
let config = String::from_utf8(plaintext)
|
|
165
|
+
.context("Failed to decode decrypted configuration as UTF-8")?;
|
|
166
|
+
|
|
167
|
+
debug!("Loaded encrypted configuration from {:?}", self.config_path);
|
|
168
|
+
Ok(Some(config))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Get the config file path
|
|
172
|
+
pub fn config_path(&self) -> &Path {
|
|
173
|
+
&self.config_path
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Get the default config file path for the current platform
|
|
178
|
+
pub fn default_config_path() -> Result<PathBuf> {
|
|
179
|
+
let config_dir = dirs::config_dir()
|
|
180
|
+
.or_else(|| dirs::home_dir().map(|h| h.join(".config")))
|
|
181
|
+
.context("Failed to determine config directory")?;
|
|
182
|
+
|
|
183
|
+
Ok(config_dir.join("enact").join("config.encrypted"))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[cfg(test)]
|
|
187
|
+
mod tests {
|
|
188
|
+
use super::*;
|
|
189
|
+
use tempfile::TempDir;
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn test_encrypted_store() {
|
|
193
|
+
let temp_dir = TempDir::new().unwrap();
|
|
194
|
+
let config_path = temp_dir.path().join("test_config.encrypted");
|
|
195
|
+
// Use mock secrets for test to avoid stderr clutter and ensure key storage
|
|
196
|
+
let secrets = SecretManager::new_mock();
|
|
197
|
+
let store = EncryptedStore::with_secrets(&config_path, secrets).unwrap();
|
|
198
|
+
|
|
199
|
+
// Test save and load
|
|
200
|
+
let test_config = r#"{"test": "value", "number": 42}"#;
|
|
201
|
+
store.save(test_config).unwrap();
|
|
202
|
+
|
|
203
|
+
let loaded = store.load().unwrap();
|
|
204
|
+
assert_eq!(loaded, Some(test_config.to_string()));
|
|
205
|
+
|
|
206
|
+
// Test that it's actually encrypted
|
|
207
|
+
let file_contents = fs::read_to_string(&config_path).unwrap();
|
|
208
|
+
assert!(!file_contents.contains("test"));
|
|
209
|
+
assert!(!file_contents.contains("value"));
|
|
210
|
+
}
|
|
211
|
+
}
|