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,579 @@
|
|
|
1
|
+
"""Workflow commands for multi-model execution.
|
|
2
|
+
|
|
3
|
+
Copyright 2025 Smart-AI-Memory
|
|
4
|
+
Licensed under Fair Source License 0.9
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import inspect
|
|
9
|
+
import json as json_mod
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from attune.config import _validate_file_path
|
|
13
|
+
from attune.logging_config import get_logger
|
|
14
|
+
from attune.workflows import get_workflow
|
|
15
|
+
from attune.workflows import list_workflows as get_workflow_list
|
|
16
|
+
from attune.workflows.config import WorkflowConfig, create_example_config
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _extract_workflow_content(final_output):
|
|
22
|
+
"""Extract readable content from workflow final_output.
|
|
23
|
+
|
|
24
|
+
Workflows return their results in various formats - this extracts
|
|
25
|
+
the actual content users want to see.
|
|
26
|
+
"""
|
|
27
|
+
if final_output is None:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
# If it's already a string, return it
|
|
31
|
+
if isinstance(final_output, str):
|
|
32
|
+
return final_output
|
|
33
|
+
|
|
34
|
+
# If it's a dict, try to extract meaningful content
|
|
35
|
+
if isinstance(final_output, dict):
|
|
36
|
+
# Common keys that contain the main output
|
|
37
|
+
# formatted_report is first - preferred for security-audit and other formatted outputs
|
|
38
|
+
content_keys = [
|
|
39
|
+
"formatted_report", # Human-readable formatted output (security-audit, etc.)
|
|
40
|
+
"answer",
|
|
41
|
+
"synthesis",
|
|
42
|
+
"result",
|
|
43
|
+
"output",
|
|
44
|
+
"content",
|
|
45
|
+
"report",
|
|
46
|
+
"summary",
|
|
47
|
+
"analysis",
|
|
48
|
+
"review",
|
|
49
|
+
"documentation",
|
|
50
|
+
"response",
|
|
51
|
+
"recommendations",
|
|
52
|
+
"findings",
|
|
53
|
+
"tests",
|
|
54
|
+
"plan",
|
|
55
|
+
]
|
|
56
|
+
for key in content_keys:
|
|
57
|
+
if final_output.get(key):
|
|
58
|
+
val = final_output[key]
|
|
59
|
+
if isinstance(val, str):
|
|
60
|
+
return val
|
|
61
|
+
if isinstance(val, dict):
|
|
62
|
+
# Recursively extract
|
|
63
|
+
return _extract_workflow_content(val)
|
|
64
|
+
|
|
65
|
+
# If no common key found, try to format the dict nicely
|
|
66
|
+
# Look for any string value that's substantial
|
|
67
|
+
for _key, val in final_output.items():
|
|
68
|
+
if isinstance(val, str) and len(val) > 100:
|
|
69
|
+
return val
|
|
70
|
+
|
|
71
|
+
# Last resort: return a formatted version
|
|
72
|
+
return json_mod.dumps(final_output, indent=2)
|
|
73
|
+
|
|
74
|
+
# For lists or other types, convert to string
|
|
75
|
+
return str(final_output)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cmd_workflow(args):
|
|
79
|
+
"""Multi-model workflow management and execution.
|
|
80
|
+
|
|
81
|
+
Supports listing, describing, and running workflows with tier-based models.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
args: Namespace object from argparse with attributes:
|
|
85
|
+
- action (str): Action to perform ('list', 'describe', 'run').
|
|
86
|
+
- name (str | None): Workflow name (for describe/run).
|
|
87
|
+
- input (str | None): JSON input for workflow execution.
|
|
88
|
+
- provider (str | None): LLM provider override.
|
|
89
|
+
- json (bool): If True, output as JSON format.
|
|
90
|
+
- use_recommended_tier (bool): Enable tier fallback.
|
|
91
|
+
- write_tests (bool): For test-gen, write tests to files.
|
|
92
|
+
- output_dir (str | None): For test-gen, output directory.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
int | None: 0 on success, 1 on failure, None for list action.
|
|
96
|
+
"""
|
|
97
|
+
action = args.action
|
|
98
|
+
|
|
99
|
+
if action == "list":
|
|
100
|
+
# List available workflows
|
|
101
|
+
workflows = get_workflow_list()
|
|
102
|
+
|
|
103
|
+
if args.json:
|
|
104
|
+
print(json_mod.dumps(workflows, indent=2))
|
|
105
|
+
else:
|
|
106
|
+
print("\n" + "=" * 60)
|
|
107
|
+
print(" MULTI-MODEL WORKFLOWS")
|
|
108
|
+
print("=" * 60 + "\n")
|
|
109
|
+
|
|
110
|
+
for wf in workflows:
|
|
111
|
+
print(f" {wf['name']:15} {wf['description']}")
|
|
112
|
+
stages = " → ".join(f"{s}({wf['tier_map'][s]})" for s in wf["stages"])
|
|
113
|
+
print(f" Stages: {stages}")
|
|
114
|
+
print()
|
|
115
|
+
|
|
116
|
+
print("-" * 60)
|
|
117
|
+
print(" Use: empathy workflow describe <name>")
|
|
118
|
+
print(" Use: empathy workflow run <name> [--input JSON]")
|
|
119
|
+
print("=" * 60 + "\n")
|
|
120
|
+
|
|
121
|
+
elif action == "describe":
|
|
122
|
+
# Describe a specific workflow
|
|
123
|
+
name = args.name
|
|
124
|
+
if not name:
|
|
125
|
+
print("Error: workflow name required")
|
|
126
|
+
print("Usage: empathy workflow describe <name>")
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
workflow_cls = get_workflow(name)
|
|
131
|
+
provider = getattr(args, "provider", None)
|
|
132
|
+
workflow = workflow_cls(provider=provider)
|
|
133
|
+
|
|
134
|
+
# Get actual provider from workflow (may come from config)
|
|
135
|
+
actual_provider = getattr(workflow, "_provider_str", provider or "anthropic")
|
|
136
|
+
|
|
137
|
+
if args.json:
|
|
138
|
+
info = {
|
|
139
|
+
"name": workflow.name,
|
|
140
|
+
"description": workflow.description,
|
|
141
|
+
"provider": actual_provider,
|
|
142
|
+
"stages": workflow.stages,
|
|
143
|
+
"tier_map": {k: v.value for k, v in workflow.tier_map.items()},
|
|
144
|
+
"models": {
|
|
145
|
+
stage: workflow.get_model_for_tier(workflow.tier_map[stage])
|
|
146
|
+
for stage in workflow.stages
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
print(json_mod.dumps(info, indent=2))
|
|
150
|
+
else:
|
|
151
|
+
print(f"Provider: {actual_provider}")
|
|
152
|
+
print(workflow.describe())
|
|
153
|
+
|
|
154
|
+
except KeyError:
|
|
155
|
+
print(f"Error: Workflow '{name}' not found")
|
|
156
|
+
print("\nRun 'empathy workflow list' to see available workflows")
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
elif action == "run":
|
|
160
|
+
# Run a workflow
|
|
161
|
+
name = args.name
|
|
162
|
+
if not name:
|
|
163
|
+
print("Error: workflow name required")
|
|
164
|
+
print('Usage: empathy workflow run <name> --input \'{"key": "value"}\'')
|
|
165
|
+
return 1
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
workflow_cls = get_workflow(name)
|
|
169
|
+
|
|
170
|
+
# Get provider from CLI arg, or fall back to config's default_provider
|
|
171
|
+
if args.provider:
|
|
172
|
+
provider = args.provider
|
|
173
|
+
else:
|
|
174
|
+
wf_config = WorkflowConfig.load()
|
|
175
|
+
provider = wf_config.default_provider
|
|
176
|
+
|
|
177
|
+
# Initialize workflow with provider and optional tier fallback
|
|
178
|
+
# Note: Not all workflows support enable_tier_fallback, so we check first
|
|
179
|
+
use_tier_fallback = getattr(args, "use_recommended_tier", False)
|
|
180
|
+
|
|
181
|
+
# Get the workflow's __init__ signature to know what params it accepts
|
|
182
|
+
init_sig = inspect.signature(workflow_cls.__init__)
|
|
183
|
+
init_params = set(init_sig.parameters.keys())
|
|
184
|
+
|
|
185
|
+
workflow_kwargs = {}
|
|
186
|
+
|
|
187
|
+
# Add provider if supported
|
|
188
|
+
if "provider" in init_params:
|
|
189
|
+
workflow_kwargs["provider"] = provider
|
|
190
|
+
|
|
191
|
+
# Add enable_tier_fallback only if the workflow supports it
|
|
192
|
+
if "enable_tier_fallback" in init_params and use_tier_fallback:
|
|
193
|
+
workflow_kwargs["enable_tier_fallback"] = use_tier_fallback
|
|
194
|
+
|
|
195
|
+
# Add health-check specific parameters
|
|
196
|
+
if name == "health-check" and "health_score_threshold" in init_params:
|
|
197
|
+
health_score_threshold = getattr(args, "health_score_threshold", 100)
|
|
198
|
+
workflow_kwargs["health_score_threshold"] = health_score_threshold
|
|
199
|
+
|
|
200
|
+
workflow = workflow_cls(**workflow_kwargs)
|
|
201
|
+
|
|
202
|
+
# Parse input
|
|
203
|
+
input_data = {}
|
|
204
|
+
if args.input:
|
|
205
|
+
input_data = json_mod.loads(args.input)
|
|
206
|
+
|
|
207
|
+
# Add test-gen specific flags to input_data (only for test-gen workflow)
|
|
208
|
+
if name == "test-gen":
|
|
209
|
+
if getattr(args, "write_tests", False):
|
|
210
|
+
input_data["write_tests"] = True
|
|
211
|
+
if getattr(args, "output_dir", None):
|
|
212
|
+
input_data["output_dir"] = args.output_dir
|
|
213
|
+
|
|
214
|
+
# Only print header when not in JSON mode
|
|
215
|
+
if not args.json:
|
|
216
|
+
print(f"\n Running workflow: {name} (provider: {provider})")
|
|
217
|
+
print("=" * 50)
|
|
218
|
+
|
|
219
|
+
# Execute workflow
|
|
220
|
+
result = asyncio.run(workflow.execute(**input_data))
|
|
221
|
+
|
|
222
|
+
# Extract the actual content - handle different result types
|
|
223
|
+
if hasattr(result, "final_output"):
|
|
224
|
+
output_content = _extract_workflow_content(result.final_output)
|
|
225
|
+
elif hasattr(result, "metadata") and isinstance(result.metadata, dict):
|
|
226
|
+
# Check for formatted_report in metadata (e.g., HealthCheckResult)
|
|
227
|
+
output_content = result.metadata.get("formatted_report")
|
|
228
|
+
if not output_content and hasattr(result, "summary"):
|
|
229
|
+
output_content = result.summary
|
|
230
|
+
elif hasattr(result, "summary"):
|
|
231
|
+
output_content = result.summary
|
|
232
|
+
else:
|
|
233
|
+
output_content = str(result)
|
|
234
|
+
|
|
235
|
+
# Get timing - handle different attribute names
|
|
236
|
+
duration_ms = getattr(result, "total_duration_ms", None)
|
|
237
|
+
if duration_ms is None and hasattr(result, "duration_seconds"):
|
|
238
|
+
duration_ms = int(result.duration_seconds * 1000)
|
|
239
|
+
|
|
240
|
+
# Get cost info if available (check cost_report first, then direct cost attribute)
|
|
241
|
+
cost_report = getattr(result, "cost_report", None)
|
|
242
|
+
if cost_report and hasattr(cost_report, "total_cost"):
|
|
243
|
+
total_cost = cost_report.total_cost
|
|
244
|
+
savings = getattr(cost_report, "savings", 0.0)
|
|
245
|
+
else:
|
|
246
|
+
# Fall back to direct cost attribute (e.g., CodeReviewPipelineResult)
|
|
247
|
+
total_cost = getattr(result, "cost", 0.0)
|
|
248
|
+
savings = 0.0
|
|
249
|
+
|
|
250
|
+
if args.json:
|
|
251
|
+
# Extract error from various result types
|
|
252
|
+
error = getattr(result, "error", None)
|
|
253
|
+
is_successful = getattr(result, "success", getattr(result, "approved", True))
|
|
254
|
+
if not error and not is_successful:
|
|
255
|
+
blockers = getattr(result, "blockers", [])
|
|
256
|
+
if blockers:
|
|
257
|
+
error = "; ".join(blockers)
|
|
258
|
+
else:
|
|
259
|
+
metadata = getattr(result, "metadata", {})
|
|
260
|
+
error = metadata.get("error") if isinstance(metadata, dict) else None
|
|
261
|
+
|
|
262
|
+
# JSON output includes both content and metadata
|
|
263
|
+
# Include final_output for programmatic access (VSCode panels, etc.)
|
|
264
|
+
raw_final_output = getattr(result, "final_output", None)
|
|
265
|
+
if raw_final_output and isinstance(raw_final_output, dict):
|
|
266
|
+
# Make a copy to avoid modifying the original
|
|
267
|
+
final_output_serializable = {}
|
|
268
|
+
for k, v in raw_final_output.items():
|
|
269
|
+
# Skip non-serializable items
|
|
270
|
+
if isinstance(v, set):
|
|
271
|
+
final_output_serializable[k] = list(v)
|
|
272
|
+
elif v is None or isinstance(v, str | int | float | bool | list | dict):
|
|
273
|
+
final_output_serializable[k] = v
|
|
274
|
+
else:
|
|
275
|
+
try:
|
|
276
|
+
final_output_serializable[k] = str(v)
|
|
277
|
+
except Exception as e: # noqa: BLE001
|
|
278
|
+
# INTENTIONAL: Silently skip any non-serializable objects
|
|
279
|
+
# This is a best-effort serialization for JSON output
|
|
280
|
+
# We cannot predict all possible object types users might return
|
|
281
|
+
logger.debug(f"Cannot serialize field {k}: {e}")
|
|
282
|
+
pass
|
|
283
|
+
else:
|
|
284
|
+
final_output_serializable = None
|
|
285
|
+
|
|
286
|
+
output = {
|
|
287
|
+
"success": is_successful,
|
|
288
|
+
"output": output_content,
|
|
289
|
+
"final_output": final_output_serializable,
|
|
290
|
+
"cost": total_cost,
|
|
291
|
+
"savings": savings,
|
|
292
|
+
"duration_ms": duration_ms or 0,
|
|
293
|
+
"error": error,
|
|
294
|
+
}
|
|
295
|
+
print(json_mod.dumps(output, indent=2))
|
|
296
|
+
# Display the actual results - this is what users want to see
|
|
297
|
+
else:
|
|
298
|
+
# Show tier progression if tier fallback was used
|
|
299
|
+
if use_tier_fallback and hasattr(workflow, "_tier_progression"):
|
|
300
|
+
tier_progression = workflow._tier_progression
|
|
301
|
+
if tier_progression:
|
|
302
|
+
print("\n" + "=" * 60)
|
|
303
|
+
print(" TIER PROGRESSION (Intelligent Fallback)")
|
|
304
|
+
print("=" * 60)
|
|
305
|
+
|
|
306
|
+
# Group by stage
|
|
307
|
+
stage_tiers: dict[str, list[tuple[str, bool]]] = {}
|
|
308
|
+
for stage, tier, success in tier_progression:
|
|
309
|
+
if stage not in stage_tiers:
|
|
310
|
+
stage_tiers[stage] = []
|
|
311
|
+
stage_tiers[stage].append((tier, success))
|
|
312
|
+
|
|
313
|
+
# Display progression for each stage
|
|
314
|
+
for stage, attempts in stage_tiers.items():
|
|
315
|
+
status = "✓" if any(success for _, success in attempts) else "✗"
|
|
316
|
+
print(f"\n{status} Stage: {stage}")
|
|
317
|
+
|
|
318
|
+
for idx, (tier, success) in enumerate(attempts, 1):
|
|
319
|
+
attempt_status = "✓ SUCCESS" if success else "✗ FAILED"
|
|
320
|
+
if idx == 1:
|
|
321
|
+
print(f" Attempt {idx}: {tier.upper():8} → {attempt_status}")
|
|
322
|
+
else:
|
|
323
|
+
prev_tier = attempts[idx - 2][0]
|
|
324
|
+
print(
|
|
325
|
+
f" Attempt {idx}: {tier.upper():8} → {attempt_status} "
|
|
326
|
+
f"(upgraded from {prev_tier.upper()})"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Calculate cost savings (only if result has stages attribute)
|
|
330
|
+
if hasattr(result, "stages") and result.stages:
|
|
331
|
+
actual_cost = sum(stage.cost for stage in result.stages if stage.cost)
|
|
332
|
+
# Estimate what cost would be if all stages used PREMIUM
|
|
333
|
+
premium_cost = actual_cost * 3 # Conservative estimate
|
|
334
|
+
|
|
335
|
+
savings = premium_cost - actual_cost
|
|
336
|
+
savings_pct = (savings / premium_cost * 100) if premium_cost > 0 else 0
|
|
337
|
+
|
|
338
|
+
print("\n" + "-" * 60)
|
|
339
|
+
print("💰 Cost Savings:")
|
|
340
|
+
print(f" Actual cost: ${actual_cost:.4f}")
|
|
341
|
+
print(f" Premium cost: ${premium_cost:.4f} (if all PREMIUM)")
|
|
342
|
+
print(f" Savings: ${savings:.4f} ({savings_pct:.1f}%)")
|
|
343
|
+
print("=" * 60 + "\n")
|
|
344
|
+
|
|
345
|
+
# Display workflow result
|
|
346
|
+
# Handle different result types (success, approved, etc.)
|
|
347
|
+
is_successful = getattr(result, "success", getattr(result, "approved", True))
|
|
348
|
+
if is_successful:
|
|
349
|
+
if output_content:
|
|
350
|
+
print(f"\n{output_content}\n")
|
|
351
|
+
else:
|
|
352
|
+
print("\n✓ Workflow completed successfully.\n")
|
|
353
|
+
else:
|
|
354
|
+
# Extract error from various result types
|
|
355
|
+
error_msg = getattr(result, "error", None)
|
|
356
|
+
if not error_msg:
|
|
357
|
+
# Check for blockers (CodeReviewPipelineResult)
|
|
358
|
+
blockers = getattr(result, "blockers", [])
|
|
359
|
+
if blockers:
|
|
360
|
+
error_msg = "; ".join(blockers)
|
|
361
|
+
else:
|
|
362
|
+
# Check metadata for error
|
|
363
|
+
metadata = getattr(result, "metadata", {})
|
|
364
|
+
error_msg = (
|
|
365
|
+
metadata.get("error") if isinstance(metadata, dict) else None
|
|
366
|
+
)
|
|
367
|
+
error_msg = error_msg or "Unknown error"
|
|
368
|
+
print(f"\n✗ Workflow failed: {error_msg}\n")
|
|
369
|
+
|
|
370
|
+
except KeyError:
|
|
371
|
+
print(f"Error: Workflow '{name}' not found")
|
|
372
|
+
print("\nRun 'empathy workflow list' to see available workflows")
|
|
373
|
+
return 1
|
|
374
|
+
except json_mod.JSONDecodeError as e:
|
|
375
|
+
print(f"Error parsing input JSON: {e}")
|
|
376
|
+
print('\nExpected valid JSON, e.g.: --input \'{"key": "value"}\'')
|
|
377
|
+
print("Make sure to use single quotes around JSON and double quotes inside")
|
|
378
|
+
return 1
|
|
379
|
+
|
|
380
|
+
elif action == "config":
|
|
381
|
+
# Generate or show workflow configuration
|
|
382
|
+
config_path = Path(".attune/workflows.yaml")
|
|
383
|
+
|
|
384
|
+
if config_path.exists() and not getattr(args, "force", False):
|
|
385
|
+
print(f"Config already exists: {config_path}")
|
|
386
|
+
print("Use --force to overwrite")
|
|
387
|
+
print("\nCurrent configuration:")
|
|
388
|
+
print("-" * 40)
|
|
389
|
+
config = WorkflowConfig.load()
|
|
390
|
+
print(f" Default provider: {config.default_provider}")
|
|
391
|
+
if config.workflow_providers:
|
|
392
|
+
print(" Workflow providers:")
|
|
393
|
+
for wf, prov in config.workflow_providers.items():
|
|
394
|
+
print(f" {wf}: {prov}")
|
|
395
|
+
if config.custom_models:
|
|
396
|
+
print(" Custom models configured")
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
# Create config directory and file
|
|
400
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
401
|
+
validated_config_path = _validate_file_path(str(config_path))
|
|
402
|
+
validated_config_path.write_text(create_example_config())
|
|
403
|
+
print(f"✓ Created workflow config: {validated_config_path}")
|
|
404
|
+
print("\nEdit this file to customize:")
|
|
405
|
+
print(" - Default provider (anthropic, openai, ollama)")
|
|
406
|
+
print(" - Per-workflow provider overrides")
|
|
407
|
+
print(" - Custom model mappings")
|
|
408
|
+
print(" - Model pricing")
|
|
409
|
+
print("\nOr use environment variables:")
|
|
410
|
+
print(" EMPATHY_WORKFLOW_PROVIDER=openai")
|
|
411
|
+
print(" EMPATHY_MODEL_PREMIUM=gpt-5.2")
|
|
412
|
+
|
|
413
|
+
else:
|
|
414
|
+
print(f"Unknown action: {action}")
|
|
415
|
+
print("Available: list, describe, run, config")
|
|
416
|
+
return 1
|
|
417
|
+
|
|
418
|
+
return 0
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def cmd_workflow_legacy(args):
|
|
422
|
+
"""Interactive setup workflow (DEPRECATED).
|
|
423
|
+
|
|
424
|
+
DEPRECATED: This command is deprecated in favor of 'empathy init'.
|
|
425
|
+
It will be removed in version 5.0.
|
|
426
|
+
|
|
427
|
+
Guides user through initial framework configuration step by step.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
args: Namespace object from argparse (no additional attributes used).
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
None: Creates attune.config.yml with user's choices.
|
|
434
|
+
"""
|
|
435
|
+
import warnings
|
|
436
|
+
|
|
437
|
+
warnings.warn(
|
|
438
|
+
"The 'workflow-setup' command is deprecated. "
|
|
439
|
+
"Use 'empathy init' instead. "
|
|
440
|
+
"This command will be removed in version 5.0.",
|
|
441
|
+
DeprecationWarning,
|
|
442
|
+
stacklevel=2,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
print("⚠️ DEPRECATED: This command is being replaced by 'empathy init'")
|
|
446
|
+
print(" Please use 'empathy init' for interactive setup.")
|
|
447
|
+
print(" This command will be removed in version 5.0.\n")
|
|
448
|
+
print("=" * 60)
|
|
449
|
+
|
|
450
|
+
print("🧙 Empathy Framework Setup Workflow")
|
|
451
|
+
print("=" * 50)
|
|
452
|
+
print("\nI'll help you set up your Empathy Framework configuration.\n")
|
|
453
|
+
|
|
454
|
+
# Step 1: Use case
|
|
455
|
+
print("1. What's your primary use case?")
|
|
456
|
+
print(" [1] Software development")
|
|
457
|
+
print(" [2] Healthcare applications")
|
|
458
|
+
print(" [3] Customer support")
|
|
459
|
+
print(" [4] Other")
|
|
460
|
+
|
|
461
|
+
use_case_choice = input("\nYour choice (1-4): ").strip()
|
|
462
|
+
use_case_map = {
|
|
463
|
+
"1": "software_development",
|
|
464
|
+
"2": "healthcare",
|
|
465
|
+
"3": "customer_support",
|
|
466
|
+
"4": "general",
|
|
467
|
+
}
|
|
468
|
+
use_case = use_case_map.get(use_case_choice, "general")
|
|
469
|
+
|
|
470
|
+
# Step 2: Empathy level
|
|
471
|
+
print("\n2. What empathy level do you want to target?")
|
|
472
|
+
print(" [1] Level 1 - Reactive (basic Q&A)")
|
|
473
|
+
print(" [2] Level 2 - Guided (asks clarifying questions)")
|
|
474
|
+
print(" [3] Level 3 - Proactive (offers improvements)")
|
|
475
|
+
print(" [4] Level 4 - Anticipatory (predicts problems) ⭐ Recommended")
|
|
476
|
+
print(" [5] Level 5 - Transformative (reshapes workflows)")
|
|
477
|
+
|
|
478
|
+
level_choice = input("\nYour choice (1-5) [4]: ").strip() or "4"
|
|
479
|
+
target_level = int(level_choice) if level_choice in ["1", "2", "3", "4", "5"] else 4
|
|
480
|
+
|
|
481
|
+
# Step 3: LLM provider
|
|
482
|
+
print("\n3. Which LLM provider will you use?")
|
|
483
|
+
print(" [1] Anthropic Claude ⭐ Recommended")
|
|
484
|
+
print(" [2] OpenAI GPT-4")
|
|
485
|
+
print(" [3] Google Gemini (2M context)")
|
|
486
|
+
print(" [4] Local (Ollama)")
|
|
487
|
+
print(" [5] Hybrid (mix best models from each provider)")
|
|
488
|
+
print(" [6] Skip (configure later)")
|
|
489
|
+
|
|
490
|
+
llm_choice = input("\nYour choice (1-6) [1]: ").strip() or "1"
|
|
491
|
+
llm_map = {
|
|
492
|
+
"1": "anthropic",
|
|
493
|
+
"2": "openai",
|
|
494
|
+
"3": "google",
|
|
495
|
+
"4": "ollama",
|
|
496
|
+
"5": "hybrid",
|
|
497
|
+
"6": None,
|
|
498
|
+
}
|
|
499
|
+
llm_provider = llm_map.get(llm_choice, "anthropic")
|
|
500
|
+
|
|
501
|
+
# If hybrid selected, launch interactive tier selection
|
|
502
|
+
if llm_provider == "hybrid":
|
|
503
|
+
from attune.models.provider_config import configure_hybrid_interactive
|
|
504
|
+
|
|
505
|
+
configure_hybrid_interactive()
|
|
506
|
+
llm_provider = None # Already saved by hybrid config
|
|
507
|
+
|
|
508
|
+
# Step 4: User ID
|
|
509
|
+
print("\n4. What user ID should we use?")
|
|
510
|
+
user_id = input("User ID [default_user]: ").strip() or "default_user"
|
|
511
|
+
|
|
512
|
+
# Generate configuration
|
|
513
|
+
config = {
|
|
514
|
+
"user_id": user_id,
|
|
515
|
+
"target_level": target_level,
|
|
516
|
+
"confidence_threshold": 0.75,
|
|
517
|
+
"persistence_enabled": True,
|
|
518
|
+
"persistence_backend": "sqlite",
|
|
519
|
+
"persistence_path": ".empathy",
|
|
520
|
+
"metrics_enabled": True,
|
|
521
|
+
"use_case": use_case,
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if llm_provider:
|
|
525
|
+
config["llm_provider"] = llm_provider
|
|
526
|
+
|
|
527
|
+
# Save configuration
|
|
528
|
+
output_file = "attune.config.yml"
|
|
529
|
+
print(f"\n5. Creating configuration file: {output_file}")
|
|
530
|
+
|
|
531
|
+
# Write YAML config
|
|
532
|
+
yaml_content = f"""# Empathy Framework Configuration
|
|
533
|
+
# Generated by setup workflow
|
|
534
|
+
|
|
535
|
+
# Core settings
|
|
536
|
+
user_id: "{config["user_id"]}"
|
|
537
|
+
target_level: {config["target_level"]}
|
|
538
|
+
confidence_threshold: {config["confidence_threshold"]}
|
|
539
|
+
|
|
540
|
+
# Use case
|
|
541
|
+
use_case: "{config["use_case"]}"
|
|
542
|
+
|
|
543
|
+
# Persistence
|
|
544
|
+
persistence_enabled: {str(config["persistence_enabled"]).lower()}
|
|
545
|
+
persistence_backend: "{config["persistence_backend"]}"
|
|
546
|
+
persistence_path: "{config["persistence_path"]}"
|
|
547
|
+
|
|
548
|
+
# Metrics
|
|
549
|
+
metrics_enabled: {str(config["metrics_enabled"]).lower()}
|
|
550
|
+
"""
|
|
551
|
+
|
|
552
|
+
if llm_provider:
|
|
553
|
+
yaml_content += f"""
|
|
554
|
+
# LLM Provider
|
|
555
|
+
llm_provider: "{llm_provider}"
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
validated_output = _validate_file_path(output_file)
|
|
559
|
+
with open(validated_output, "w") as f:
|
|
560
|
+
f.write(yaml_content)
|
|
561
|
+
|
|
562
|
+
print(f" ✓ Created {validated_output}")
|
|
563
|
+
|
|
564
|
+
print("\n" + "=" * 50)
|
|
565
|
+
print("✅ Setup complete!")
|
|
566
|
+
print("\nNext steps:")
|
|
567
|
+
print(f" 1. Edit {output_file} to customize settings")
|
|
568
|
+
|
|
569
|
+
if llm_provider in ["anthropic", "openai", "google"]:
|
|
570
|
+
env_var_map = {
|
|
571
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
572
|
+
"openai": "OPENAI_API_KEY",
|
|
573
|
+
"google": "GOOGLE_API_KEY",
|
|
574
|
+
}
|
|
575
|
+
env_var = env_var_map.get(llm_provider, "API_KEY")
|
|
576
|
+
print(f" 2. Set {env_var} environment variable")
|
|
577
|
+
|
|
578
|
+
print(" 3. Run: empathy-framework run --config attune.config.yml")
|
|
579
|
+
print("\nHappy empathizing! 🧠✨\n")
|
attune/cli/core.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Core utilities for Empathy Framework CLI.
|
|
2
|
+
|
|
3
|
+
Shared utilities, console configuration, and helper functions used across
|
|
4
|
+
all CLI command modules.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import version as get_version
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
# Shared Rich console instance for all CLI commands
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_empathy_version() -> str:
|
|
20
|
+
"""Get the installed version of empathy-framework."""
|
|
21
|
+
try:
|
|
22
|
+
return get_version("empathy-framework")
|
|
23
|
+
except Exception: # noqa: BLE001
|
|
24
|
+
# INTENTIONAL: Version detection fallback for dev installs
|
|
25
|
+
return "dev"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def version_callback(value: bool) -> None:
|
|
29
|
+
"""Show version and exit."""
|
|
30
|
+
if value:
|
|
31
|
+
console.print(f"[bold blue]Empathy Framework[/bold blue] v{get_empathy_version()}")
|
|
32
|
+
raise typer.Exit()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""CLI parser registration.
|
|
2
|
+
|
|
3
|
+
This module coordinates parser registration for all CLI commands.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart-AI-Memory
|
|
6
|
+
Licensed under Fair Source License 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from . import (
|
|
10
|
+
batch,
|
|
11
|
+
cache,
|
|
12
|
+
help,
|
|
13
|
+
info,
|
|
14
|
+
inspect,
|
|
15
|
+
metrics,
|
|
16
|
+
orchestrate,
|
|
17
|
+
patterns,
|
|
18
|
+
provider,
|
|
19
|
+
routing,
|
|
20
|
+
setup,
|
|
21
|
+
status,
|
|
22
|
+
sync,
|
|
23
|
+
tier,
|
|
24
|
+
workflow,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register_all_parsers(subparsers):
|
|
29
|
+
"""Register all command parsers.
|
|
30
|
+
|
|
31
|
+
This function is called from the main CLI entry point to set up
|
|
32
|
+
all subcommands and their argument parsers.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
subparsers: ArgumentParser subparsers object from main parser
|
|
36
|
+
|
|
37
|
+
Note:
|
|
38
|
+
All 30 commands have been extracted from the monolithic cli.py
|
|
39
|
+
and organized into focused modules.
|
|
40
|
+
"""
|
|
41
|
+
# Core commands
|
|
42
|
+
help.register_parsers(subparsers)
|
|
43
|
+
tier.register_parsers(subparsers)
|
|
44
|
+
info.register_parsers(subparsers)
|
|
45
|
+
|
|
46
|
+
# Pattern and state management
|
|
47
|
+
patterns.register_parsers(subparsers)
|
|
48
|
+
status.register_parsers(subparsers)
|
|
49
|
+
|
|
50
|
+
# Workflow and execution
|
|
51
|
+
workflow.register_parsers(subparsers)
|
|
52
|
+
inspect.register_parsers(subparsers)
|
|
53
|
+
|
|
54
|
+
# Provider configuration
|
|
55
|
+
provider.register_parsers(subparsers)
|
|
56
|
+
|
|
57
|
+
# Orchestration and sync
|
|
58
|
+
orchestrate.register_parsers(subparsers)
|
|
59
|
+
sync.register_parsers(subparsers)
|
|
60
|
+
|
|
61
|
+
# Metrics and state
|
|
62
|
+
metrics.register_parsers(subparsers)
|
|
63
|
+
cache.register_parsers(subparsers) # Cache monitoring
|
|
64
|
+
batch.register_parsers(subparsers) # Batch processing (50% cost savings)
|
|
65
|
+
routing.register_parsers(subparsers) # Adaptive routing statistics
|
|
66
|
+
|
|
67
|
+
# Setup and initialization
|
|
68
|
+
setup.register_parsers(subparsers)
|