attune-ai 2.0.0__py3-none-any.whl
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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Redis Bootstrap for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Automatically starts Redis if not running, with graceful fallback.
|
|
4
|
+
Supports:
|
|
5
|
+
- macOS: Homebrew
|
|
6
|
+
- Linux: systemd, direct
|
|
7
|
+
- Windows: Windows Service, Chocolatey, Scoop, WSL, direct
|
|
8
|
+
- All platforms: Docker
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from attune.memory.redis_bootstrap import ensure_redis
|
|
12
|
+
|
|
13
|
+
# Returns True if Redis is available (started or already running)
|
|
14
|
+
redis_available = ensure_redis()
|
|
15
|
+
|
|
16
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
17
|
+
Licensed under Fair Source 0.9
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import platform
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import time
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from enum import Enum
|
|
28
|
+
|
|
29
|
+
import structlog
|
|
30
|
+
|
|
31
|
+
logger = structlog.get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
# Detect platform
|
|
34
|
+
IS_WINDOWS = platform.system() == "Windows"
|
|
35
|
+
IS_MACOS = platform.system() == "Darwin"
|
|
36
|
+
IS_LINUX = platform.system() == "Linux"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RedisStartMethod(Enum):
|
|
40
|
+
"""Methods for starting Redis, in order of preference."""
|
|
41
|
+
|
|
42
|
+
ALREADY_RUNNING = "already_running"
|
|
43
|
+
HOMEBREW = "homebrew"
|
|
44
|
+
SYSTEMD = "systemd"
|
|
45
|
+
WINDOWS_SERVICE = "windows_service"
|
|
46
|
+
CHOCOLATEY = "chocolatey"
|
|
47
|
+
SCOOP = "scoop"
|
|
48
|
+
WSL = "wsl"
|
|
49
|
+
DOCKER = "docker"
|
|
50
|
+
DIRECT = "direct"
|
|
51
|
+
MOCK = "mock"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class RedisStatus:
|
|
56
|
+
"""Status of Redis connection/startup."""
|
|
57
|
+
|
|
58
|
+
available: bool
|
|
59
|
+
method: RedisStartMethod
|
|
60
|
+
host: str = "localhost"
|
|
61
|
+
port: int = 6379
|
|
62
|
+
message: str = ""
|
|
63
|
+
pid: int | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _check_redis_running(host: str = "localhost", port: int = 6379) -> bool:
|
|
67
|
+
"""Check if Redis is responding to ping."""
|
|
68
|
+
try:
|
|
69
|
+
import redis
|
|
70
|
+
|
|
71
|
+
client = redis.Redis(host=host, port=port, socket_connect_timeout=1)
|
|
72
|
+
return client.ping()
|
|
73
|
+
except Exception:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _find_command(cmd: str) -> str | None:
|
|
78
|
+
"""Find command in PATH."""
|
|
79
|
+
return shutil.which(cmd)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _run_silent(cmd: list[str], timeout: int = 5) -> tuple[bool, str]:
|
|
83
|
+
"""Run command silently, return (success, output)."""
|
|
84
|
+
try:
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
cmd,
|
|
87
|
+
check=False,
|
|
88
|
+
capture_output=True,
|
|
89
|
+
text=True,
|
|
90
|
+
timeout=timeout,
|
|
91
|
+
)
|
|
92
|
+
return result.returncode == 0, result.stdout + result.stderr
|
|
93
|
+
except subprocess.TimeoutExpired:
|
|
94
|
+
return False, "timeout"
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return False, str(e)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _start_via_homebrew() -> bool:
|
|
100
|
+
"""Try to start Redis via Homebrew (macOS)."""
|
|
101
|
+
if not _find_command("brew"):
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
# Check if redis is installed
|
|
105
|
+
success, output = _run_silent(["brew", "list", "redis"])
|
|
106
|
+
if not success:
|
|
107
|
+
logger.debug("redis_not_installed_via_homebrew")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
# Try to start
|
|
111
|
+
success, output = _run_silent(["brew", "services", "start", "redis"], timeout=10)
|
|
112
|
+
if success:
|
|
113
|
+
logger.info("redis_started_via_homebrew")
|
|
114
|
+
time.sleep(1) # Give it time to start
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
logger.debug("homebrew_start_failed", output=output)
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _start_via_systemd() -> bool:
|
|
122
|
+
"""Try to start Redis via systemd (Linux)."""
|
|
123
|
+
if not _find_command("systemctl"):
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
# Try to start (may require sudo, which we avoid)
|
|
127
|
+
success, output = _run_silent(["systemctl", "start", "redis"], timeout=10)
|
|
128
|
+
if success:
|
|
129
|
+
logger.info("redis_started_via_systemd")
|
|
130
|
+
time.sleep(1)
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
# Try redis-server service name variant
|
|
134
|
+
success, output = _run_silent(["systemctl", "start", "redis-server"], timeout=10)
|
|
135
|
+
if success:
|
|
136
|
+
logger.info("redis_started_via_systemd", service="redis-server")
|
|
137
|
+
time.sleep(1)
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
logger.debug("systemd_start_failed", output=output)
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _start_via_docker(port: int = 6379) -> bool:
|
|
145
|
+
"""Try to start Redis via Docker."""
|
|
146
|
+
if not _find_command("docker"):
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Check if Docker daemon is running
|
|
150
|
+
success, _ = _run_silent(["docker", "info"], timeout=5)
|
|
151
|
+
if not success:
|
|
152
|
+
logger.debug("docker_daemon_not_running")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
container_name = "empathy-redis"
|
|
156
|
+
|
|
157
|
+
# Check if container exists
|
|
158
|
+
success, output = _run_silent(
|
|
159
|
+
["docker", "ps", "-a", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if container_name in output:
|
|
163
|
+
# Container exists, try to start it
|
|
164
|
+
success, _ = _run_silent(["docker", "start", container_name], timeout=10)
|
|
165
|
+
if success:
|
|
166
|
+
logger.info("redis_started_via_docker", action="started_existing")
|
|
167
|
+
time.sleep(1)
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
# Create and start new container
|
|
171
|
+
success, output = _run_silent(
|
|
172
|
+
["docker", "run", "-d", "--name", container_name, "-p", f"{port}:6379", "redis:alpine"],
|
|
173
|
+
timeout=30,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if success:
|
|
177
|
+
logger.info("redis_started_via_docker", action="created_new")
|
|
178
|
+
time.sleep(2) # Give container time to start
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
logger.debug("docker_start_failed", output=output)
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _start_via_direct(port: int = 6379) -> bool:
|
|
186
|
+
"""Try to start redis-server directly in background."""
|
|
187
|
+
# On Windows, look for redis-server.exe
|
|
188
|
+
if IS_WINDOWS:
|
|
189
|
+
redis_server = _find_command("redis-server.exe") or _find_command("redis-server")
|
|
190
|
+
else:
|
|
191
|
+
redis_server = _find_command("redis-server")
|
|
192
|
+
|
|
193
|
+
if not redis_server:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
if IS_WINDOWS:
|
|
198
|
+
# Windows: Start without daemonize (run in background via subprocess)
|
|
199
|
+
process = subprocess.Popen(
|
|
200
|
+
[redis_server, "--port", str(port)],
|
|
201
|
+
stdout=subprocess.DEVNULL,
|
|
202
|
+
stderr=subprocess.DEVNULL,
|
|
203
|
+
creationflags=(
|
|
204
|
+
subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
# Don't wait - let it run in background
|
|
208
|
+
time.sleep(2)
|
|
209
|
+
if process.poll() is None: # Still running
|
|
210
|
+
logger.info("redis_started_directly_windows")
|
|
211
|
+
return True
|
|
212
|
+
else:
|
|
213
|
+
# Unix: Use daemonize
|
|
214
|
+
process = subprocess.Popen(
|
|
215
|
+
[redis_server, "--port", str(port), "--daemonize", "yes"],
|
|
216
|
+
stdout=subprocess.DEVNULL,
|
|
217
|
+
stderr=subprocess.DEVNULL,
|
|
218
|
+
)
|
|
219
|
+
process.wait(timeout=5)
|
|
220
|
+
|
|
221
|
+
if process.returncode == 0:
|
|
222
|
+
logger.info("redis_started_directly")
|
|
223
|
+
time.sleep(1)
|
|
224
|
+
return True
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.debug("direct_start_failed", error=str(e))
|
|
227
|
+
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _start_via_windows_service() -> bool:
|
|
232
|
+
"""Try to start Redis via Windows Service."""
|
|
233
|
+
if not IS_WINDOWS:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
# Try to start Redis service
|
|
238
|
+
success, output = _run_silent(["net", "start", "Redis"], timeout=10)
|
|
239
|
+
if success or "already been started" in output:
|
|
240
|
+
logger.info("redis_started_via_windows_service")
|
|
241
|
+
time.sleep(1)
|
|
242
|
+
return True
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.debug("windows_service_start_failed", error=str(e))
|
|
245
|
+
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _start_via_chocolatey() -> bool:
|
|
250
|
+
"""Try to start Redis installed via Chocolatey (Windows)."""
|
|
251
|
+
if not IS_WINDOWS:
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
choco = _find_command("choco")
|
|
255
|
+
if not choco:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
# Check if redis is installed via chocolatey
|
|
259
|
+
success, output = _run_silent(["choco", "list", "--local-only", "redis"])
|
|
260
|
+
if not success or "redis" not in output.lower():
|
|
261
|
+
logger.debug("redis_not_installed_via_chocolatey")
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
# Chocolatey installs Redis as a service, try to start it
|
|
265
|
+
return _start_via_windows_service()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _start_via_scoop() -> bool:
|
|
269
|
+
"""Try to start Redis installed via Scoop (Windows)."""
|
|
270
|
+
if not IS_WINDOWS:
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
scoop = _find_command("scoop")
|
|
274
|
+
if not scoop:
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
# Check if redis is installed via scoop
|
|
278
|
+
success, output = _run_silent(["scoop", "list", "redis"])
|
|
279
|
+
if not success or "redis" not in output.lower():
|
|
280
|
+
logger.debug("redis_not_installed_via_scoop")
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
# Scoop typically installs to ~/scoop/apps/redis/current/
|
|
284
|
+
scoop_redis = os.path.expanduser("~/scoop/apps/redis/current/redis-server.exe")
|
|
285
|
+
if os.path.exists(scoop_redis):
|
|
286
|
+
try:
|
|
287
|
+
process = subprocess.Popen(
|
|
288
|
+
[scoop_redis],
|
|
289
|
+
stdout=subprocess.DEVNULL,
|
|
290
|
+
stderr=subprocess.DEVNULL,
|
|
291
|
+
creationflags=(
|
|
292
|
+
subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
time.sleep(2)
|
|
296
|
+
if process.poll() is None:
|
|
297
|
+
logger.info("redis_started_via_scoop")
|
|
298
|
+
return True
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.debug("scoop_start_failed", error=str(e))
|
|
301
|
+
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _start_via_wsl() -> bool:
|
|
306
|
+
"""Try to start Redis via WSL (Windows Subsystem for Linux)."""
|
|
307
|
+
if not IS_WINDOWS:
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
wsl = _find_command("wsl")
|
|
311
|
+
if not wsl:
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# Check if Redis is installed in WSL
|
|
316
|
+
success, output = _run_silent(["wsl", "which", "redis-server"])
|
|
317
|
+
if not success:
|
|
318
|
+
logger.debug("redis_not_installed_in_wsl")
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
# Start Redis in WSL
|
|
322
|
+
process = subprocess.Popen(
|
|
323
|
+
["wsl", "redis-server", "--daemonize", "yes"],
|
|
324
|
+
stdout=subprocess.DEVNULL,
|
|
325
|
+
stderr=subprocess.DEVNULL,
|
|
326
|
+
)
|
|
327
|
+
process.wait(timeout=5)
|
|
328
|
+
|
|
329
|
+
if process.returncode == 0:
|
|
330
|
+
logger.info("redis_started_via_wsl")
|
|
331
|
+
time.sleep(1)
|
|
332
|
+
return True
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.debug("wsl_start_failed", error=str(e))
|
|
335
|
+
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def ensure_redis(
|
|
340
|
+
host: str = "localhost",
|
|
341
|
+
port: int = 6379,
|
|
342
|
+
auto_start: bool = True,
|
|
343
|
+
verbose: bool = True,
|
|
344
|
+
) -> RedisStatus:
|
|
345
|
+
"""Ensure Redis is available, starting it if necessary.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
host: Redis host
|
|
349
|
+
port: Redis port
|
|
350
|
+
auto_start: Attempt to start Redis if not running
|
|
351
|
+
verbose: Print status messages to console
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
RedisStatus with availability info
|
|
355
|
+
|
|
356
|
+
Example:
|
|
357
|
+
>>> status = ensure_redis()
|
|
358
|
+
>>> if status.available:
|
|
359
|
+
... print(f"Redis ready via {status.method.value}")
|
|
360
|
+
... else:
|
|
361
|
+
... print(f"Redis unavailable: {status.message}")
|
|
362
|
+
|
|
363
|
+
"""
|
|
364
|
+
# Check if already running
|
|
365
|
+
if _check_redis_running(host, port):
|
|
366
|
+
status = RedisStatus(
|
|
367
|
+
available=True,
|
|
368
|
+
method=RedisStartMethod.ALREADY_RUNNING,
|
|
369
|
+
host=host,
|
|
370
|
+
port=port,
|
|
371
|
+
message="Redis is running",
|
|
372
|
+
)
|
|
373
|
+
if verbose:
|
|
374
|
+
logger.info("redis_already_running", host=host, port=port)
|
|
375
|
+
return status
|
|
376
|
+
|
|
377
|
+
if not auto_start:
|
|
378
|
+
return RedisStatus(
|
|
379
|
+
available=False,
|
|
380
|
+
method=RedisStartMethod.MOCK,
|
|
381
|
+
host=host,
|
|
382
|
+
port=port,
|
|
383
|
+
message="Redis not running and auto_start=False",
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if verbose:
|
|
387
|
+
print("Redis not running. Attempting to start...")
|
|
388
|
+
|
|
389
|
+
# Build platform-specific method list
|
|
390
|
+
start_methods: list[tuple[RedisStartMethod, Callable[[], bool]]] = []
|
|
391
|
+
|
|
392
|
+
if IS_MACOS:
|
|
393
|
+
start_methods.append((RedisStartMethod.HOMEBREW, _start_via_homebrew))
|
|
394
|
+
elif IS_LINUX:
|
|
395
|
+
start_methods.append((RedisStartMethod.SYSTEMD, _start_via_systemd))
|
|
396
|
+
elif IS_WINDOWS:
|
|
397
|
+
# Windows-specific methods
|
|
398
|
+
start_methods.extend(
|
|
399
|
+
[
|
|
400
|
+
(RedisStartMethod.WINDOWS_SERVICE, _start_via_windows_service),
|
|
401
|
+
(RedisStartMethod.CHOCOLATEY, _start_via_chocolatey),
|
|
402
|
+
(RedisStartMethod.SCOOP, _start_via_scoop),
|
|
403
|
+
(RedisStartMethod.WSL, _start_via_wsl),
|
|
404
|
+
],
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Docker and direct work on all platforms
|
|
408
|
+
start_methods.extend(
|
|
409
|
+
[
|
|
410
|
+
(RedisStartMethod.DOCKER, lambda: _start_via_docker(port)),
|
|
411
|
+
(RedisStartMethod.DIRECT, lambda: _start_via_direct(port)),
|
|
412
|
+
],
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
for method, start_func in start_methods:
|
|
416
|
+
try:
|
|
417
|
+
if start_func():
|
|
418
|
+
# Verify it's actually running
|
|
419
|
+
if _check_redis_running(host, port):
|
|
420
|
+
status = RedisStatus(
|
|
421
|
+
available=True,
|
|
422
|
+
method=method,
|
|
423
|
+
host=host,
|
|
424
|
+
port=port,
|
|
425
|
+
message=f"Redis started via {method.value}",
|
|
426
|
+
)
|
|
427
|
+
if verbose:
|
|
428
|
+
print(f"✓ Redis started via {method.value}")
|
|
429
|
+
return status
|
|
430
|
+
except Exception as e:
|
|
431
|
+
logger.debug(f"{method.value}_failed", error=str(e))
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
# All methods failed - build platform-specific message
|
|
435
|
+
if IS_WINDOWS:
|
|
436
|
+
install_instructions = (
|
|
437
|
+
"Could not start Redis. For full functionality, install Redis:\n"
|
|
438
|
+
" Chocolatey: choco install redis-64\n"
|
|
439
|
+
" Scoop: scoop install redis\n"
|
|
440
|
+
" WSL: wsl sudo apt install redis-server\n"
|
|
441
|
+
" Docker: docker run -d -p 6379:6379 redis:alpine\n"
|
|
442
|
+
" Manual: Download from https://github.com/microsoftarchive/redis/releases"
|
|
443
|
+
)
|
|
444
|
+
elif IS_MACOS:
|
|
445
|
+
install_instructions = (
|
|
446
|
+
"Could not start Redis. For full functionality, install Redis:\n"
|
|
447
|
+
" Homebrew: brew install redis && brew services start redis\n"
|
|
448
|
+
" Docker: docker run -d -p 6379:6379 redis:alpine"
|
|
449
|
+
)
|
|
450
|
+
else: # Linux
|
|
451
|
+
install_instructions = (
|
|
452
|
+
"Could not start Redis. For full functionality, install Redis:\n"
|
|
453
|
+
" Ubuntu/Debian: sudo apt install redis-server\n"
|
|
454
|
+
" RHEL/CentOS: sudo yum install redis\n"
|
|
455
|
+
" Docker: docker run -d -p 6379:6379 redis:alpine"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
message = f"{install_instructions}\n\nFalling back to in-memory mock (single-process only)."
|
|
459
|
+
|
|
460
|
+
if verbose:
|
|
461
|
+
print(f"\n⚠ {message}")
|
|
462
|
+
|
|
463
|
+
return RedisStatus(
|
|
464
|
+
available=False,
|
|
465
|
+
method=RedisStartMethod.MOCK,
|
|
466
|
+
host=host,
|
|
467
|
+
port=port,
|
|
468
|
+
message=message,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def stop_redis(method: RedisStartMethod) -> bool:
|
|
473
|
+
"""Stop Redis if we started it.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
method: The method used to start Redis
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
True if stopped successfully
|
|
480
|
+
|
|
481
|
+
"""
|
|
482
|
+
if method == RedisStartMethod.HOMEBREW:
|
|
483
|
+
success, _ = _run_silent(["brew", "services", "stop", "redis"])
|
|
484
|
+
return success
|
|
485
|
+
|
|
486
|
+
if method == RedisStartMethod.SYSTEMD:
|
|
487
|
+
success, _ = _run_silent(["systemctl", "stop", "redis"])
|
|
488
|
+
if not success:
|
|
489
|
+
success, _ = _run_silent(["systemctl", "stop", "redis-server"])
|
|
490
|
+
return success
|
|
491
|
+
|
|
492
|
+
if method == RedisStartMethod.WINDOWS_SERVICE:
|
|
493
|
+
success, _ = _run_silent(["net", "stop", "Redis"])
|
|
494
|
+
return success
|
|
495
|
+
|
|
496
|
+
if method == RedisStartMethod.CHOCOLATEY:
|
|
497
|
+
# Chocolatey uses Windows Service
|
|
498
|
+
success, _ = _run_silent(["net", "stop", "Redis"])
|
|
499
|
+
return success
|
|
500
|
+
|
|
501
|
+
if method == RedisStartMethod.WSL:
|
|
502
|
+
success, _ = _run_silent(["wsl", "redis-cli", "shutdown", "nosave"])
|
|
503
|
+
return success
|
|
504
|
+
|
|
505
|
+
if method == RedisStartMethod.DOCKER:
|
|
506
|
+
success, _ = _run_silent(["docker", "stop", "empathy-redis"])
|
|
507
|
+
return success
|
|
508
|
+
|
|
509
|
+
if method == RedisStartMethod.DIRECT:
|
|
510
|
+
# Try redis-cli shutdown
|
|
511
|
+
if IS_WINDOWS:
|
|
512
|
+
redis_cli = _find_command("redis-cli.exe") or _find_command("redis-cli")
|
|
513
|
+
else:
|
|
514
|
+
redis_cli = _find_command("redis-cli")
|
|
515
|
+
|
|
516
|
+
if redis_cli:
|
|
517
|
+
success, _ = _run_silent([redis_cli, "shutdown", "nosave"])
|
|
518
|
+
return success
|
|
519
|
+
|
|
520
|
+
return False
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# Convenience function for simple usage
|
|
524
|
+
def get_redis_or_mock(host: str = "localhost", port: int = 6379):
|
|
525
|
+
"""Get a Redis connection, starting Redis if needed, or return mock.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
tuple: (RedisShortTermMemory instance, RedisStatus)
|
|
529
|
+
|
|
530
|
+
"""
|
|
531
|
+
from .short_term import RedisShortTermMemory
|
|
532
|
+
|
|
533
|
+
status = ensure_redis(host=host, port=port)
|
|
534
|
+
|
|
535
|
+
if status.available:
|
|
536
|
+
memory = RedisShortTermMemory(host=host, port=port, use_mock=False)
|
|
537
|
+
else:
|
|
538
|
+
memory = RedisShortTermMemory(use_mock=True)
|
|
539
|
+
|
|
540
|
+
return memory, status
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Security Module for Empathy Framework Memory
|
|
2
|
+
|
|
3
|
+
Provides enterprise-grade security controls including:
|
|
4
|
+
- PII scrubbing (GDPR, HIPAA, SOC2 compliant)
|
|
5
|
+
- Secrets detection (API keys, passwords, private keys)
|
|
6
|
+
- Audit logging (tamper-evident, SOC2/HIPAA compliant)
|
|
7
|
+
|
|
8
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
9
|
+
Licensed under Fair Source 0.9
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .audit_logger import AuditEvent, AuditLogger, SecurityViolation
|
|
13
|
+
from .pii_scrubber import PIIDetection, PIIPattern, PIIScrubber
|
|
14
|
+
from .secrets_detector import SecretDetection, SecretsDetector, SecretType, Severity, detect_secrets
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AuditEvent",
|
|
18
|
+
# Audit Logging
|
|
19
|
+
"AuditLogger",
|
|
20
|
+
"PIIDetection",
|
|
21
|
+
"PIIPattern",
|
|
22
|
+
# PII Scrubbing
|
|
23
|
+
"PIIScrubber",
|
|
24
|
+
"SecretDetection",
|
|
25
|
+
"SecretType",
|
|
26
|
+
# Secrets Detection
|
|
27
|
+
"SecretsDetector",
|
|
28
|
+
"SecurityViolation",
|
|
29
|
+
"Severity",
|
|
30
|
+
"detect_secrets",
|
|
31
|
+
]
|