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
attune/models/cli.py
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"""CLI for Multi-Model Configuration Inspection
|
|
2
|
+
|
|
3
|
+
Provides commands to:
|
|
4
|
+
- Print effective model registry
|
|
5
|
+
- Show task-to-tier mappings
|
|
6
|
+
- Validate configuration files
|
|
7
|
+
- Display cost estimates
|
|
8
|
+
- View telemetry and analytics
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python -m attune.models.cli registry
|
|
12
|
+
python -m attune.models.cli tasks
|
|
13
|
+
python -m attune.models.cli validate path/to/config.yaml
|
|
14
|
+
python -m attune.models.cli costs --input-tokens 10000 --output-tokens 2000
|
|
15
|
+
python -m attune.models.cli telemetry --summary
|
|
16
|
+
python -m attune.models.cli telemetry --costs
|
|
17
|
+
python -m attune.models.cli telemetry --providers
|
|
18
|
+
|
|
19
|
+
Copyright 2025 Smart-AI-Memory
|
|
20
|
+
Licensed under Fair Source License 0.9
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from datetime import datetime, timedelta
|
|
27
|
+
|
|
28
|
+
from .provider_config import (
|
|
29
|
+
configure_provider_cli,
|
|
30
|
+
configure_provider_interactive,
|
|
31
|
+
get_provider_config,
|
|
32
|
+
)
|
|
33
|
+
from .registry import get_all_models
|
|
34
|
+
from .tasks import get_all_tasks, get_tier_for_task
|
|
35
|
+
from .telemetry import TelemetryAnalytics, TelemetryStore
|
|
36
|
+
from .validation import validate_yaml_file
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_registry(provider: str | None = None, format: str = "table") -> None:
|
|
40
|
+
"""Print the model registry.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
provider: Optional provider to filter by
|
|
44
|
+
format: Output format ("table" or "json")
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
registry = get_all_models()
|
|
48
|
+
|
|
49
|
+
if provider:
|
|
50
|
+
if provider not in registry:
|
|
51
|
+
print(f"Error: Unknown provider '{provider}'")
|
|
52
|
+
print(f"Available providers: {', '.join(registry.keys())}")
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
registry = {provider: registry[provider]}
|
|
55
|
+
|
|
56
|
+
if format == "json":
|
|
57
|
+
# Convert to JSON-serializable format
|
|
58
|
+
output: dict[str, dict[str, dict[str, object]]] = {}
|
|
59
|
+
for prov, tiers in registry.items():
|
|
60
|
+
output[prov] = {}
|
|
61
|
+
for tier, info in tiers.items():
|
|
62
|
+
output[prov][tier] = {
|
|
63
|
+
"id": info.id,
|
|
64
|
+
"input_cost_per_million": info.input_cost_per_million,
|
|
65
|
+
"output_cost_per_million": info.output_cost_per_million,
|
|
66
|
+
"max_tokens": info.max_tokens,
|
|
67
|
+
"supports_vision": info.supports_vision,
|
|
68
|
+
"supports_tools": info.supports_tools,
|
|
69
|
+
}
|
|
70
|
+
print(json.dumps(output, indent=2))
|
|
71
|
+
else:
|
|
72
|
+
# Table format
|
|
73
|
+
print("\n" + "=" * 80)
|
|
74
|
+
print("MODEL REGISTRY")
|
|
75
|
+
print("=" * 80)
|
|
76
|
+
|
|
77
|
+
for prov, tiers in sorted(registry.items()):
|
|
78
|
+
print(f"\n[{prov.upper()}]")
|
|
79
|
+
print("-" * 60)
|
|
80
|
+
print(f"{'Tier':<10} {'Model ID':<35} {'Input/M':<10} {'Output/M':<10}")
|
|
81
|
+
print("-" * 60)
|
|
82
|
+
|
|
83
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
84
|
+
if tier in tiers:
|
|
85
|
+
info = tiers[tier]
|
|
86
|
+
print(
|
|
87
|
+
f"{tier:<10} {info.id:<35} "
|
|
88
|
+
f"${info.input_cost_per_million:<9.2f} "
|
|
89
|
+
f"${info.output_cost_per_million:<9.2f}",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
print("\n" + "=" * 80)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def print_tasks(tier: str | None = None, format: str = "table") -> None:
|
|
96
|
+
"""Print task-to-tier mappings.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
tier: Optional tier to filter by
|
|
100
|
+
format: Output format ("table" or "json")
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
all_tasks = get_all_tasks()
|
|
104
|
+
|
|
105
|
+
if tier:
|
|
106
|
+
if tier not in all_tasks:
|
|
107
|
+
print(f"Error: Unknown tier '{tier}'")
|
|
108
|
+
print(f"Available tiers: {', '.join(all_tasks.keys())}")
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
all_tasks = {tier: all_tasks[tier]}
|
|
111
|
+
|
|
112
|
+
if format == "json":
|
|
113
|
+
print(json.dumps(all_tasks, indent=2))
|
|
114
|
+
else:
|
|
115
|
+
print("\n" + "=" * 60)
|
|
116
|
+
print("TASK-TO-TIER MAPPINGS")
|
|
117
|
+
print("=" * 60)
|
|
118
|
+
|
|
119
|
+
for tier_name, tasks in sorted(all_tasks.items()):
|
|
120
|
+
print(f"\n[{tier_name.upper()}] - {len(tasks)} tasks")
|
|
121
|
+
print("-" * 40)
|
|
122
|
+
for task in sorted(tasks):
|
|
123
|
+
print(f" • {task}")
|
|
124
|
+
|
|
125
|
+
print("\n" + "=" * 60)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def print_costs(
|
|
129
|
+
input_tokens: int = 10000,
|
|
130
|
+
output_tokens: int = 2000,
|
|
131
|
+
provider: str | None = None,
|
|
132
|
+
format: str = "table",
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Print cost estimates for all tiers.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
input_tokens: Number of input tokens
|
|
138
|
+
output_tokens: Number of output tokens
|
|
139
|
+
provider: Optional provider to filter by
|
|
140
|
+
format: Output format
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
registry = get_all_models()
|
|
144
|
+
|
|
145
|
+
if provider:
|
|
146
|
+
if provider not in registry:
|
|
147
|
+
print(f"Error: Unknown provider '{provider}'")
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
providers = [provider]
|
|
150
|
+
else:
|
|
151
|
+
providers = list(registry.keys())
|
|
152
|
+
|
|
153
|
+
if format == "json":
|
|
154
|
+
output: dict[str, dict[str, dict[str, object]]] = {}
|
|
155
|
+
for prov in providers:
|
|
156
|
+
output[prov] = {}
|
|
157
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
158
|
+
if tier in registry[prov]:
|
|
159
|
+
info = registry[prov][tier]
|
|
160
|
+
input_cost = (input_tokens / 1_000_000) * info.input_cost_per_million
|
|
161
|
+
output_cost = (output_tokens / 1_000_000) * info.output_cost_per_million
|
|
162
|
+
output[prov][tier] = {
|
|
163
|
+
"input_cost": round(input_cost, 6),
|
|
164
|
+
"output_cost": round(output_cost, 6),
|
|
165
|
+
"total_cost": round(input_cost + output_cost, 6),
|
|
166
|
+
}
|
|
167
|
+
print(json.dumps(output, indent=2))
|
|
168
|
+
else:
|
|
169
|
+
print("\n" + "=" * 70)
|
|
170
|
+
print(f"COST ESTIMATES ({input_tokens:,} input / {output_tokens:,} output tokens)")
|
|
171
|
+
print("=" * 70)
|
|
172
|
+
|
|
173
|
+
for prov in sorted(providers):
|
|
174
|
+
print(f"\n[{prov.upper()}]")
|
|
175
|
+
print("-" * 50)
|
|
176
|
+
print(f"{'Tier':<10} {'Input':<12} {'Output':<12} {'Total':<12}")
|
|
177
|
+
print("-" * 50)
|
|
178
|
+
|
|
179
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
180
|
+
if tier in registry[prov]:
|
|
181
|
+
info = registry[prov][tier]
|
|
182
|
+
input_cost = (input_tokens / 1_000_000) * info.input_cost_per_million
|
|
183
|
+
output_cost = (output_tokens / 1_000_000) * info.output_cost_per_million
|
|
184
|
+
total = input_cost + output_cost
|
|
185
|
+
print(f"{tier:<10} ${input_cost:<11.4f} ${output_cost:<11.4f} ${total:<11.4f}")
|
|
186
|
+
|
|
187
|
+
print("\n" + "=" * 70)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def validate_file(file_path: str, format: str = "table") -> int:
|
|
191
|
+
"""Validate a configuration file.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
file_path: Path to YAML config file
|
|
195
|
+
format: Output format
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Exit code (0 = valid, 1 = errors)
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
result = validate_yaml_file(file_path)
|
|
202
|
+
|
|
203
|
+
if format == "json":
|
|
204
|
+
output = {
|
|
205
|
+
"valid": result.valid,
|
|
206
|
+
"errors": [{"path": e.path, "message": e.message} for e in result.errors],
|
|
207
|
+
"warnings": [{"path": w.path, "message": w.message} for w in result.warnings],
|
|
208
|
+
}
|
|
209
|
+
print(json.dumps(output, indent=2))
|
|
210
|
+
else:
|
|
211
|
+
print(f"\nValidating: {file_path}")
|
|
212
|
+
print("-" * 60)
|
|
213
|
+
print(result)
|
|
214
|
+
|
|
215
|
+
return 0 if result.valid else 1
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def print_effective_config(provider: str = "anthropic") -> None:
|
|
219
|
+
"""Print the effective configuration for a provider.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
provider: Provider to show config for
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
registry = get_all_models()
|
|
226
|
+
|
|
227
|
+
if provider not in registry:
|
|
228
|
+
print(f"Error: Unknown provider '{provider}'")
|
|
229
|
+
sys.exit(1)
|
|
230
|
+
|
|
231
|
+
print("\n" + "=" * 70)
|
|
232
|
+
print(f"EFFECTIVE CONFIGURATION: {provider.upper()}")
|
|
233
|
+
print("=" * 70)
|
|
234
|
+
|
|
235
|
+
# Provider models
|
|
236
|
+
print("\n[Models]")
|
|
237
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
238
|
+
if tier in registry[provider]:
|
|
239
|
+
info = registry[provider][tier]
|
|
240
|
+
print(f" {tier}: {info.id}")
|
|
241
|
+
|
|
242
|
+
# Task routing
|
|
243
|
+
print("\n[Task Routing Examples]")
|
|
244
|
+
example_tasks = ["summarize", "fix_bug", "coordinate"]
|
|
245
|
+
for task in example_tasks:
|
|
246
|
+
task_tier = get_tier_for_task(task)
|
|
247
|
+
model = registry[provider].get(task_tier.value)
|
|
248
|
+
model_id = model.id if model else "N/A"
|
|
249
|
+
print(f" {task} → {task_tier.value} → {model_id}")
|
|
250
|
+
|
|
251
|
+
# Default timeouts
|
|
252
|
+
print("\n[Default Timeouts]")
|
|
253
|
+
print(" cheap: 30,000 ms")
|
|
254
|
+
print(" capable: 60,000 ms")
|
|
255
|
+
print(" premium: 120,000 ms")
|
|
256
|
+
|
|
257
|
+
print("\n" + "=" * 70)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def print_telemetry_summary(
|
|
261
|
+
days: int = 7,
|
|
262
|
+
format: str = "table",
|
|
263
|
+
storage_dir: str = ".empathy",
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Print telemetry summary.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
days: Number of days to look back
|
|
269
|
+
format: Output format
|
|
270
|
+
storage_dir: Directory containing telemetry files
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
store = TelemetryStore(storage_dir)
|
|
274
|
+
analytics = TelemetryAnalytics(store)
|
|
275
|
+
|
|
276
|
+
since = datetime.now() - timedelta(days=days)
|
|
277
|
+
calls = store.get_calls(since=since, limit=10000)
|
|
278
|
+
workflows = store.get_workflows(since=since, limit=1000)
|
|
279
|
+
|
|
280
|
+
if format == "json":
|
|
281
|
+
output = {
|
|
282
|
+
"period_days": days,
|
|
283
|
+
"total_calls": len(calls),
|
|
284
|
+
"total_workflows": len(workflows),
|
|
285
|
+
"total_cost": sum(c.estimated_cost for c in calls),
|
|
286
|
+
"success_rate": sum(1 for c in calls if c.success) / len(calls) * 100 if calls else 0,
|
|
287
|
+
}
|
|
288
|
+
print(json.dumps(output, indent=2))
|
|
289
|
+
else:
|
|
290
|
+
print("\n" + "=" * 60)
|
|
291
|
+
print(f"TELEMETRY SUMMARY (Last {days} days)")
|
|
292
|
+
print("=" * 60)
|
|
293
|
+
|
|
294
|
+
print("\n[Overview]")
|
|
295
|
+
print(f" Total LLM calls: {len(calls):,}")
|
|
296
|
+
print(f" Total workflows: {len(workflows):,}")
|
|
297
|
+
total_cost = sum(c.estimated_cost for c in calls)
|
|
298
|
+
print(f" Total cost: ${total_cost:.4f}")
|
|
299
|
+
if calls:
|
|
300
|
+
success_rate = sum(1 for c in calls if c.success) / len(calls) * 100
|
|
301
|
+
print(f" Success rate: {success_rate:.1f}%")
|
|
302
|
+
|
|
303
|
+
# Tier breakdown
|
|
304
|
+
tier_dist = analytics.tier_distribution(since)
|
|
305
|
+
print("\n[Tier Distribution]")
|
|
306
|
+
for tier, stats in tier_dist.items():
|
|
307
|
+
print(f" {tier}: {stats['count']} calls ({stats['percent']:.1f}%)")
|
|
308
|
+
|
|
309
|
+
print("\n" + "=" * 60)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def print_telemetry_costs(
|
|
313
|
+
days: int = 30,
|
|
314
|
+
format: str = "table",
|
|
315
|
+
storage_dir: str = ".empathy",
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Print cost savings report.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
days: Number of days to look back
|
|
321
|
+
format: Output format
|
|
322
|
+
storage_dir: Directory containing telemetry files
|
|
323
|
+
|
|
324
|
+
"""
|
|
325
|
+
store = TelemetryStore(storage_dir)
|
|
326
|
+
analytics = TelemetryAnalytics(store)
|
|
327
|
+
|
|
328
|
+
since = datetime.now() - timedelta(days=days)
|
|
329
|
+
report = analytics.cost_savings_report(since)
|
|
330
|
+
|
|
331
|
+
if format == "json":
|
|
332
|
+
print(json.dumps(report, indent=2))
|
|
333
|
+
else:
|
|
334
|
+
print("\n" + "=" * 60)
|
|
335
|
+
print(f"COST SAVINGS REPORT (Last {days} days)")
|
|
336
|
+
print("=" * 60)
|
|
337
|
+
|
|
338
|
+
print("\n[Summary]")
|
|
339
|
+
print(f" Workflow runs: {report['workflow_count']}")
|
|
340
|
+
print(f" Actual cost: ${report['total_actual_cost']:.4f}")
|
|
341
|
+
print(f" Baseline cost (all premium): ${report['total_baseline_cost']:.4f}")
|
|
342
|
+
print(f" Total savings: ${report['total_savings']:.4f}")
|
|
343
|
+
print(f" Savings percent: {report['savings_percent']:.1f}%")
|
|
344
|
+
print(f" Avg cost per workflow: ${report['avg_cost_per_workflow']:.4f}")
|
|
345
|
+
|
|
346
|
+
# Top expensive workflows
|
|
347
|
+
top_wfs = analytics.top_expensive_workflows(n=5, since=since)
|
|
348
|
+
if top_wfs:
|
|
349
|
+
print("\n[Top Expensive Workflows]")
|
|
350
|
+
for wf in top_wfs:
|
|
351
|
+
print(f" {wf['workflow_name']}: ${wf['total_cost']:.4f} ({wf['run_count']} runs)")
|
|
352
|
+
|
|
353
|
+
print("\n" + "=" * 60)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def print_telemetry_providers(
|
|
357
|
+
days: int = 30,
|
|
358
|
+
format: str = "table",
|
|
359
|
+
storage_dir: str = ".empathy",
|
|
360
|
+
) -> None:
|
|
361
|
+
"""Print provider usage summary.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
days: Number of days to look back
|
|
365
|
+
format: Output format
|
|
366
|
+
storage_dir: Directory containing telemetry files
|
|
367
|
+
|
|
368
|
+
"""
|
|
369
|
+
store = TelemetryStore(storage_dir)
|
|
370
|
+
analytics = TelemetryAnalytics(store)
|
|
371
|
+
|
|
372
|
+
since = datetime.now() - timedelta(days=days)
|
|
373
|
+
summary = analytics.provider_usage_summary(since)
|
|
374
|
+
|
|
375
|
+
if format == "json":
|
|
376
|
+
print(json.dumps(summary, indent=2))
|
|
377
|
+
else:
|
|
378
|
+
print("\n" + "=" * 60)
|
|
379
|
+
print(f"PROVIDER USAGE (Last {days} days)")
|
|
380
|
+
print("=" * 60)
|
|
381
|
+
|
|
382
|
+
for provider, stats in sorted(summary.items()):
|
|
383
|
+
print(f"\n[{provider.upper()}]")
|
|
384
|
+
print(f" Calls: {stats['call_count']:,}")
|
|
385
|
+
print(f" Tokens: {stats['total_tokens']:,}")
|
|
386
|
+
print(f" Cost: ${stats['total_cost']:.4f}")
|
|
387
|
+
print(f" Errors: {stats['error_count']}")
|
|
388
|
+
print(f" By tier: {stats['by_tier']}")
|
|
389
|
+
|
|
390
|
+
print("\n" + "=" * 60)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def print_telemetry_fallbacks(
|
|
394
|
+
days: int = 30,
|
|
395
|
+
format: str = "table",
|
|
396
|
+
storage_dir: str = ".empathy",
|
|
397
|
+
) -> None:
|
|
398
|
+
"""Print fallback statistics.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
days: Number of days to look back
|
|
402
|
+
format: Output format
|
|
403
|
+
storage_dir: Directory containing telemetry files
|
|
404
|
+
|
|
405
|
+
"""
|
|
406
|
+
store = TelemetryStore(storage_dir)
|
|
407
|
+
analytics = TelemetryAnalytics(store)
|
|
408
|
+
|
|
409
|
+
since = datetime.now() - timedelta(days=days)
|
|
410
|
+
stats = analytics.fallback_stats(since)
|
|
411
|
+
|
|
412
|
+
if format == "json":
|
|
413
|
+
print(json.dumps(stats, indent=2))
|
|
414
|
+
else:
|
|
415
|
+
print("\n" + "=" * 60)
|
|
416
|
+
print(f"FALLBACK STATISTICS (Last {days} days)")
|
|
417
|
+
print("=" * 60)
|
|
418
|
+
|
|
419
|
+
print("\n[Summary]")
|
|
420
|
+
print(f" Total calls: {stats['total_calls']:,}")
|
|
421
|
+
print(f" Fallback count: {stats['fallback_count']:,}")
|
|
422
|
+
print(f" Fallback rate: {stats['fallback_percent']:.2f}%")
|
|
423
|
+
print(f" Error count: {stats['error_count']:,}")
|
|
424
|
+
print(f" Error rate: {stats['error_rate']:.2f}%")
|
|
425
|
+
|
|
426
|
+
if stats["by_original_provider"]:
|
|
427
|
+
print("\n[Fallbacks by Original Provider]")
|
|
428
|
+
for provider, count in stats["by_original_provider"].items():
|
|
429
|
+
print(f" {provider}: {count}")
|
|
430
|
+
|
|
431
|
+
print("\n" + "=" * 60)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def print_provider_config(format: str = "table") -> None:
|
|
435
|
+
"""Print current provider configuration.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
format: Output format ("table" or "json")
|
|
439
|
+
|
|
440
|
+
"""
|
|
441
|
+
config = get_provider_config()
|
|
442
|
+
|
|
443
|
+
if format == "json":
|
|
444
|
+
output = config.to_dict()
|
|
445
|
+
output["available_providers"] = config.available_providers
|
|
446
|
+
output["effective_models"] = {
|
|
447
|
+
tier: {"id": model.id, "provider": model.provider}
|
|
448
|
+
for tier, model in config.get_effective_registry().items()
|
|
449
|
+
if model
|
|
450
|
+
}
|
|
451
|
+
print(json.dumps(output, indent=2))
|
|
452
|
+
else:
|
|
453
|
+
print("\n" + "=" * 60)
|
|
454
|
+
print("PROVIDER CONFIGURATION")
|
|
455
|
+
print("=" * 60)
|
|
456
|
+
|
|
457
|
+
print("\n[Current Settings]")
|
|
458
|
+
print(f" Mode: {config.mode.value}")
|
|
459
|
+
print(f" Primary provider: {config.primary_provider}")
|
|
460
|
+
print(f" Cost optimization: {config.cost_optimization}")
|
|
461
|
+
print(f" Prefer local (Ollama): {config.prefer_local}")
|
|
462
|
+
|
|
463
|
+
print("\n[Available Providers]")
|
|
464
|
+
if config.available_providers:
|
|
465
|
+
for provider in config.available_providers:
|
|
466
|
+
print(f" - {provider}")
|
|
467
|
+
else:
|
|
468
|
+
print(" (No API keys detected)")
|
|
469
|
+
|
|
470
|
+
print("\n[Effective Model Mapping]")
|
|
471
|
+
effective = config.get_effective_registry()
|
|
472
|
+
for tier in ["cheap", "capable", "premium"]:
|
|
473
|
+
model = effective.get(tier)
|
|
474
|
+
if model:
|
|
475
|
+
print(f" {tier:<10} -> {model.id} ({model.provider})")
|
|
476
|
+
else:
|
|
477
|
+
print(f" {tier:<10} -> (not configured)")
|
|
478
|
+
|
|
479
|
+
if config.tier_providers:
|
|
480
|
+
print("\n[Custom Tier Providers]")
|
|
481
|
+
for tier, provider in config.tier_providers.items():
|
|
482
|
+
print(f" {tier}: {provider}")
|
|
483
|
+
|
|
484
|
+
print("\n" + "=" * 60)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def configure_provider(
|
|
488
|
+
provider: str | None = None,
|
|
489
|
+
mode: str | None = None,
|
|
490
|
+
interactive: bool = False,
|
|
491
|
+
) -> int:
|
|
492
|
+
"""Configure provider settings.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
provider: Provider to set (anthropic, openai, google, ollama, hybrid)
|
|
496
|
+
mode: Mode to set (single, hybrid)
|
|
497
|
+
interactive: Whether to run interactive configuration
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Exit code (0 for success)
|
|
501
|
+
|
|
502
|
+
"""
|
|
503
|
+
if interactive:
|
|
504
|
+
configure_provider_interactive()
|
|
505
|
+
return 0
|
|
506
|
+
|
|
507
|
+
if provider or mode:
|
|
508
|
+
config = configure_provider_cli(provider=provider, mode=mode)
|
|
509
|
+
config.save()
|
|
510
|
+
print("Provider configuration updated:")
|
|
511
|
+
print(f" Mode: {config.mode.value}")
|
|
512
|
+
print(f" Provider: {config.primary_provider}")
|
|
513
|
+
print("\nEffective models:")
|
|
514
|
+
for tier, model in config.get_effective_registry().items():
|
|
515
|
+
if model:
|
|
516
|
+
print(f" {tier}: {model.id}")
|
|
517
|
+
return 0
|
|
518
|
+
|
|
519
|
+
# Show current config
|
|
520
|
+
print_provider_config()
|
|
521
|
+
return 0
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def main() -> int:
|
|
525
|
+
"""Main CLI entry point."""
|
|
526
|
+
parser = argparse.ArgumentParser(
|
|
527
|
+
description="Multi-Model Configuration CLI",
|
|
528
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
529
|
+
epilog="""
|
|
530
|
+
Examples:
|
|
531
|
+
%(prog)s registry Show all models
|
|
532
|
+
%(prog)s registry --provider anthropic Show Anthropic models only
|
|
533
|
+
%(prog)s tasks Show task-to-tier mappings
|
|
534
|
+
%(prog)s costs --input 50000 Estimate costs for 50k input tokens
|
|
535
|
+
%(prog)s validate config.yaml Validate a config file
|
|
536
|
+
%(prog)s effective --provider openai Show effective config for OpenAI
|
|
537
|
+
%(prog)s telemetry Show telemetry summary
|
|
538
|
+
%(prog)s telemetry --costs Show cost savings report
|
|
539
|
+
%(prog)s telemetry --providers Show provider usage
|
|
540
|
+
%(prog)s telemetry --fallbacks Show fallback stats
|
|
541
|
+
%(prog)s provider Show current provider config
|
|
542
|
+
%(prog)s provider --set anthropic Set Anthropic as primary
|
|
543
|
+
%(prog)s provider --set hybrid Enable hybrid mode
|
|
544
|
+
%(prog)s provider --interactive Interactive setup workflow
|
|
545
|
+
""",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
549
|
+
|
|
550
|
+
# Registry command
|
|
551
|
+
reg_parser = subparsers.add_parser("registry", help="Show model registry")
|
|
552
|
+
reg_parser.add_argument("--provider", "-p", help="Filter by provider")
|
|
553
|
+
reg_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
554
|
+
|
|
555
|
+
# Tasks command
|
|
556
|
+
tasks_parser = subparsers.add_parser("tasks", help="Show task-to-tier mappings")
|
|
557
|
+
tasks_parser.add_argument("--tier", "-t", help="Filter by tier")
|
|
558
|
+
tasks_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
559
|
+
|
|
560
|
+
# Costs command
|
|
561
|
+
costs_parser = subparsers.add_parser("costs", help="Estimate costs")
|
|
562
|
+
costs_parser.add_argument("--input-tokens", "-i", type=int, default=10000, help="Input tokens")
|
|
563
|
+
costs_parser.add_argument("--output-tokens", "-o", type=int, default=2000, help="Output tokens")
|
|
564
|
+
costs_parser.add_argument("--provider", "-p", help="Filter by provider")
|
|
565
|
+
costs_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
566
|
+
|
|
567
|
+
# Validate command
|
|
568
|
+
val_parser = subparsers.add_parser("validate", help="Validate config file")
|
|
569
|
+
val_parser.add_argument("file", help="Path to YAML config file")
|
|
570
|
+
val_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
571
|
+
|
|
572
|
+
# Effective command
|
|
573
|
+
eff_parser = subparsers.add_parser("effective", help="Show effective config")
|
|
574
|
+
eff_parser.add_argument("--provider", "-p", default="anthropic", help="Provider to show")
|
|
575
|
+
|
|
576
|
+
# Telemetry command
|
|
577
|
+
tel_parser = subparsers.add_parser("telemetry", help="Show telemetry data")
|
|
578
|
+
tel_parser.add_argument("--days", "-d", type=int, default=7, help="Number of days to look back")
|
|
579
|
+
tel_parser.add_argument("--costs", action="store_true", help="Show cost savings report")
|
|
580
|
+
tel_parser.add_argument("--providers", action="store_true", help="Show provider usage")
|
|
581
|
+
tel_parser.add_argument("--fallbacks", action="store_true", help="Show fallback statistics")
|
|
582
|
+
tel_parser.add_argument("--storage-dir", default=".empathy", help="Telemetry storage directory")
|
|
583
|
+
tel_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
584
|
+
|
|
585
|
+
# Provider command
|
|
586
|
+
prov_parser = subparsers.add_parser("provider", help="Configure provider settings")
|
|
587
|
+
prov_parser.add_argument(
|
|
588
|
+
"--set",
|
|
589
|
+
"-s",
|
|
590
|
+
dest="provider_set",
|
|
591
|
+
help="Set provider (anthropic, openai, google, ollama, hybrid)",
|
|
592
|
+
)
|
|
593
|
+
prov_parser.add_argument(
|
|
594
|
+
"--mode",
|
|
595
|
+
"-m",
|
|
596
|
+
choices=["single", "hybrid"],
|
|
597
|
+
help="Set mode (single or hybrid)",
|
|
598
|
+
)
|
|
599
|
+
prov_parser.add_argument(
|
|
600
|
+
"--interactive",
|
|
601
|
+
"-i",
|
|
602
|
+
action="store_true",
|
|
603
|
+
help="Interactive configuration workflow",
|
|
604
|
+
)
|
|
605
|
+
prov_parser.add_argument("--format", "-f", choices=["table", "json"], default="table")
|
|
606
|
+
|
|
607
|
+
args = parser.parse_args()
|
|
608
|
+
|
|
609
|
+
if not args.command:
|
|
610
|
+
parser.print_help()
|
|
611
|
+
return 1
|
|
612
|
+
|
|
613
|
+
if args.command == "registry":
|
|
614
|
+
print_registry(args.provider, args.format)
|
|
615
|
+
return 0
|
|
616
|
+
|
|
617
|
+
if args.command == "tasks":
|
|
618
|
+
print_tasks(args.tier, args.format)
|
|
619
|
+
return 0
|
|
620
|
+
|
|
621
|
+
if args.command == "costs":
|
|
622
|
+
print_costs(args.input_tokens, args.output_tokens, args.provider, args.format)
|
|
623
|
+
return 0
|
|
624
|
+
|
|
625
|
+
if args.command == "validate":
|
|
626
|
+
return validate_file(args.file, args.format)
|
|
627
|
+
|
|
628
|
+
if args.command == "effective":
|
|
629
|
+
print_effective_config(args.provider)
|
|
630
|
+
return 0
|
|
631
|
+
|
|
632
|
+
if args.command == "telemetry":
|
|
633
|
+
if args.costs:
|
|
634
|
+
print_telemetry_costs(args.days, args.format, args.storage_dir)
|
|
635
|
+
elif args.providers:
|
|
636
|
+
print_telemetry_providers(args.days, args.format, args.storage_dir)
|
|
637
|
+
elif args.fallbacks:
|
|
638
|
+
print_telemetry_fallbacks(args.days, args.format, args.storage_dir)
|
|
639
|
+
else:
|
|
640
|
+
print_telemetry_summary(args.days, args.format, args.storage_dir)
|
|
641
|
+
return 0
|
|
642
|
+
|
|
643
|
+
if args.command == "provider":
|
|
644
|
+
if args.interactive:
|
|
645
|
+
return configure_provider(interactive=True)
|
|
646
|
+
if args.provider_set or args.mode:
|
|
647
|
+
return configure_provider(provider=args.provider_set, mode=args.mode)
|
|
648
|
+
print_provider_config(args.format)
|
|
649
|
+
return 0
|
|
650
|
+
|
|
651
|
+
return 1
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
if __name__ == "__main__":
|
|
655
|
+
sys.exit(main())
|