devsper 2.1.6__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.
- devsper/__init__.py +14 -0
- devsper/agents/a2a/__init__.py +27 -0
- devsper/agents/a2a/client.py +126 -0
- devsper/agents/a2a/discovery.py +24 -0
- devsper/agents/a2a/server.py +128 -0
- devsper/agents/a2a/tool_adapter.py +68 -0
- devsper/agents/a2a/types.py +49 -0
- devsper/agents/agent.py +602 -0
- devsper/agents/critic.py +80 -0
- devsper/agents/message_bus.py +124 -0
- devsper/agents/roles.py +181 -0
- devsper/agents/run_agent.py +78 -0
- devsper/analytics/__init__.py +5 -0
- devsper/analytics/tool_analytics.py +78 -0
- devsper/audit/__init__.py +5 -0
- devsper/audit/logger.py +214 -0
- devsper/bus/__init__.py +29 -0
- devsper/bus/backends/__init__.py +5 -0
- devsper/bus/backends/base.py +38 -0
- devsper/bus/backends/memory.py +55 -0
- devsper/bus/backends/redis.py +146 -0
- devsper/bus/message.py +56 -0
- devsper/bus/schema_version.py +3 -0
- devsper/bus/topics.py +19 -0
- devsper/cache/__init__.py +6 -0
- devsper/cache/embedding_index.py +98 -0
- devsper/cache/hashing.py +24 -0
- devsper/cache/store.py +153 -0
- devsper/cache/task_cache.py +191 -0
- devsper/cli/__init__.py +6 -0
- devsper/cli/commands/reg.py +733 -0
- devsper/cli/github_oauth.py +157 -0
- devsper/cli/init.py +637 -0
- devsper/cli/main.py +2956 -0
- devsper/cli/run_progress.py +103 -0
- devsper/cli/ui/__init__.py +65 -0
- devsper/cli/ui/components.py +94 -0
- devsper/cli/ui/errors.py +104 -0
- devsper/cli/ui/logging.py +120 -0
- devsper/cli/ui/onboarding.py +102 -0
- devsper/cli/ui/progress.py +43 -0
- devsper/cli/ui/run_view.py +308 -0
- devsper/cli/ui/theme.py +40 -0
- devsper/cluster/__init__.py +29 -0
- devsper/cluster/election.py +84 -0
- devsper/cluster/local.py +97 -0
- devsper/cluster/node_info.py +77 -0
- devsper/cluster/registry.py +71 -0
- devsper/cluster/router.py +117 -0
- devsper/cluster/state_backend.py +105 -0
- devsper/compliance/__init__.py +5 -0
- devsper/compliance/pii.py +147 -0
- devsper/config/__init__.py +52 -0
- devsper/config/config_loader.py +121 -0
- devsper/config/defaults.py +77 -0
- devsper/config/resolver.py +342 -0
- devsper/config/schema.py +237 -0
- devsper/credentials/__init__.py +19 -0
- devsper/credentials/cli.py +197 -0
- devsper/credentials/migration.py +124 -0
- devsper/credentials/store.py +142 -0
- devsper/dashboard/__init__.py +9 -0
- devsper/dashboard/dashboard.py +87 -0
- devsper/dev/__init__.py +25 -0
- devsper/dev/builder.py +195 -0
- devsper/dev/debugger.py +95 -0
- devsper/dev/repo_index.py +138 -0
- devsper/dev/sandbox.py +203 -0
- devsper/dev/scaffold.py +122 -0
- devsper/embeddings/__init__.py +5 -0
- devsper/embeddings/service.py +36 -0
- devsper/explainability/__init__.py +14 -0
- devsper/explainability/decision_tree.py +104 -0
- devsper/explainability/rationale.py +38 -0
- devsper/explainability/simulation.py +56 -0
- devsper/hitl/__init__.py +13 -0
- devsper/hitl/approval.py +160 -0
- devsper/hitl/escalation.py +95 -0
- devsper/intelligence/__init__.py +9 -0
- devsper/intelligence/adaptation.py +88 -0
- devsper/intelligence/analysis/__init__.py +19 -0
- devsper/intelligence/analysis/analyzer.py +71 -0
- devsper/intelligence/analysis/cost_estimator.py +66 -0
- devsper/intelligence/analysis/formatter.py +103 -0
- devsper/intelligence/analysis/run_report.py +402 -0
- devsper/intelligence/learning_engine.py +92 -0
- devsper/intelligence/strategies/__init__.py +23 -0
- devsper/intelligence/strategies/base.py +14 -0
- devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
- devsper/intelligence/strategies/data_science_strategy.py +33 -0
- devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
- devsper/intelligence/strategies/experiment_strategy.py +33 -0
- devsper/intelligence/strategies/research_strategy.py +34 -0
- devsper/intelligence/strategy_selector.py +84 -0
- devsper/intelligence/synthesis.py +132 -0
- devsper/intelligence/task_optimizer.py +92 -0
- devsper/knowledge/__init__.py +5 -0
- devsper/knowledge/extractor.py +204 -0
- devsper/knowledge/knowledge_graph.py +184 -0
- devsper/knowledge/query.py +285 -0
- devsper/memory/__init__.py +35 -0
- devsper/memory/consolidation.py +138 -0
- devsper/memory/embeddings.py +60 -0
- devsper/memory/memory_index.py +97 -0
- devsper/memory/memory_router.py +62 -0
- devsper/memory/memory_store.py +221 -0
- devsper/memory/memory_types.py +54 -0
- devsper/memory/namespaces.py +45 -0
- devsper/memory/scoring.py +77 -0
- devsper/memory/summarizer.py +52 -0
- devsper/nodes/__init__.py +5 -0
- devsper/nodes/controller.py +449 -0
- devsper/nodes/rpc.py +127 -0
- devsper/nodes/single.py +161 -0
- devsper/nodes/worker.py +506 -0
- devsper/orchestration/__init__.py +19 -0
- devsper/orchestration/meta_planner.py +239 -0
- devsper/orchestration/priority_queue.py +61 -0
- devsper/plugins/__init__.py +19 -0
- devsper/plugins/marketplace/__init__.py +0 -0
- devsper/plugins/plugin_loader.py +70 -0
- devsper/plugins/plugin_registry.py +34 -0
- devsper/plugins/registry.py +83 -0
- devsper/protocols/__init__.py +6 -0
- devsper/providers/__init__.py +17 -0
- devsper/providers/anthropic.py +84 -0
- devsper/providers/base.py +75 -0
- devsper/providers/complexity_router.py +94 -0
- devsper/providers/gemini.py +36 -0
- devsper/providers/github.py +180 -0
- devsper/providers/model_router.py +40 -0
- devsper/providers/openai.py +105 -0
- devsper/providers/router/__init__.py +21 -0
- devsper/providers/router/backends/__init__.py +19 -0
- devsper/providers/router/backends/anthropic_backend.py +111 -0
- devsper/providers/router/backends/custom_backend.py +138 -0
- devsper/providers/router/backends/gemini_backend.py +89 -0
- devsper/providers/router/backends/github_backend.py +165 -0
- devsper/providers/router/backends/ollama_backend.py +104 -0
- devsper/providers/router/backends/openai_backend.py +142 -0
- devsper/providers/router/backends/vllm_backend.py +35 -0
- devsper/providers/router/base.py +60 -0
- devsper/providers/router/factory.py +92 -0
- devsper/providers/router/legacy.py +101 -0
- devsper/providers/router/router.py +135 -0
- devsper/reasoning/__init__.py +12 -0
- devsper/reasoning/graph.py +59 -0
- devsper/reasoning/nodes.py +20 -0
- devsper/reasoning/store.py +67 -0
- devsper/runtime/__init__.py +12 -0
- devsper/runtime/health.py +88 -0
- devsper/runtime/replay.py +53 -0
- devsper/runtime/replay_engine.py +142 -0
- devsper/runtime/run_history.py +204 -0
- devsper/runtime/telemetry.py +116 -0
- devsper/runtime/visualize.py +58 -0
- devsper/sandbox/__init__.py +13 -0
- devsper/sandbox/sandbox.py +161 -0
- devsper/swarm/checkpointer.py +65 -0
- devsper/swarm/executor.py +558 -0
- devsper/swarm/map_reduce.py +44 -0
- devsper/swarm/planner.py +197 -0
- devsper/swarm/prefetcher.py +91 -0
- devsper/swarm/scheduler.py +153 -0
- devsper/swarm/speculation.py +47 -0
- devsper/swarm/swarm.py +562 -0
- devsper/tools/__init__.py +33 -0
- devsper/tools/base.py +29 -0
- devsper/tools/code_intelligence/__init__.py +13 -0
- devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
- devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
- devsper/tools/code_intelligence/codebase_indexer.py +71 -0
- devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
- devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
- devsper/tools/code_intelligence/large_function_detector.py +68 -0
- devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
- devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
- devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
- devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
- devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
- devsper/tools/coding/__init__.py +12 -0
- devsper/tools/coding/analyze_code_complexity.py +48 -0
- devsper/tools/coding/dependency_analyzer.py +42 -0
- devsper/tools/coding/extract_functions.py +38 -0
- devsper/tools/coding/format_python.py +50 -0
- devsper/tools/coding/generate_docstrings.py +40 -0
- devsper/tools/coding/generate_unit_tests.py +42 -0
- devsper/tools/coding/lint_python.py +51 -0
- devsper/tools/coding/refactor_function.py +41 -0
- devsper/tools/coding/repo_structure_map.py +54 -0
- devsper/tools/coding/run_python.py +53 -0
- devsper/tools/data/__init__.py +12 -0
- devsper/tools/data/column_type_detection.py +64 -0
- devsper/tools/data/csv_summary.py +52 -0
- devsper/tools/data/dataframe_filter.py +51 -0
- devsper/tools/data/dataframe_groupby.py +47 -0
- devsper/tools/data/dataframe_stats.py +38 -0
- devsper/tools/data/dataset_sampling.py +55 -0
- devsper/tools/data/dataset_schema.py +45 -0
- devsper/tools/data/json_pretty_print.py +37 -0
- devsper/tools/data/json_query.py +46 -0
- devsper/tools/data/missing_value_report.py +47 -0
- devsper/tools/data_science/__init__.py +13 -0
- devsper/tools/data_science/correlation_heatmap.py +72 -0
- devsper/tools/data_science/dataset_bias_detector.py +49 -0
- devsper/tools/data_science/dataset_distribution_report.py +64 -0
- devsper/tools/data_science/dataset_drift_detector.py +64 -0
- devsper/tools/data_science/dataset_outlier_detector.py +65 -0
- devsper/tools/data_science/dataset_profile.py +76 -0
- devsper/tools/data_science/distributed_dataset_processor.py +54 -0
- devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
- devsper/tools/data_science/feature_importance_estimator.py +82 -0
- devsper/tools/data_science/model_input_validator.py +59 -0
- devsper/tools/data_science/time_series_analyzer.py +57 -0
- devsper/tools/documents/__init__.py +11 -0
- devsper/tools/documents/_docproc.py +56 -0
- devsper/tools/documents/document_to_markdown.py +29 -0
- devsper/tools/documents/extract_document_images.py +39 -0
- devsper/tools/documents/extract_document_text.py +29 -0
- devsper/tools/documents/extract_equations.py +36 -0
- devsper/tools/documents/extract_tables.py +47 -0
- devsper/tools/documents/summarize_document.py +42 -0
- devsper/tools/documents/write_latex_document.py +133 -0
- devsper/tools/documents/write_markdown_document.py +89 -0
- devsper/tools/documents/write_word_document.py +149 -0
- devsper/tools/experiments/__init__.py +13 -0
- devsper/tools/experiments/bootstrap_estimator.py +54 -0
- devsper/tools/experiments/experiment_report_generator.py +50 -0
- devsper/tools/experiments/experiment_tracker.py +36 -0
- devsper/tools/experiments/grid_search_runner.py +50 -0
- devsper/tools/experiments/model_benchmark_runner.py +45 -0
- devsper/tools/experiments/monte_carlo_experiment.py +38 -0
- devsper/tools/experiments/parameter_sweep_runner.py +51 -0
- devsper/tools/experiments/result_comparator.py +58 -0
- devsper/tools/experiments/simulation_runner.py +43 -0
- devsper/tools/experiments/statistical_significance_test.py +56 -0
- devsper/tools/experiments/swarm_map_reduce.py +42 -0
- devsper/tools/filesystem/__init__.py +12 -0
- devsper/tools/filesystem/append_file.py +42 -0
- devsper/tools/filesystem/file_hash.py +40 -0
- devsper/tools/filesystem/file_line_count.py +36 -0
- devsper/tools/filesystem/file_metadata.py +38 -0
- devsper/tools/filesystem/file_preview.py +55 -0
- devsper/tools/filesystem/find_large_files.py +50 -0
- devsper/tools/filesystem/list_directory.py +39 -0
- devsper/tools/filesystem/read_file.py +35 -0
- devsper/tools/filesystem/search_files.py +60 -0
- devsper/tools/filesystem/write_file.py +41 -0
- devsper/tools/flagship/__init__.py +15 -0
- devsper/tools/flagship/distributed_document_analysis.py +77 -0
- devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
- devsper/tools/flagship/repository_semantic_map.py +99 -0
- devsper/tools/flagship/research_graph_builder.py +111 -0
- devsper/tools/flagship/swarm_experiment_runner.py +86 -0
- devsper/tools/knowledge/__init__.py +10 -0
- devsper/tools/knowledge/citation_graph_builder.py +69 -0
- devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
- devsper/tools/knowledge/corpus_builder.py +66 -0
- devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
- devsper/tools/knowledge/document_corpus_summary.py +68 -0
- devsper/tools/knowledge/document_topic_extractor.py +58 -0
- devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
- devsper/tools/knowledge/timeline_extractor.py +59 -0
- devsper/tools/math/__init__.py +12 -0
- devsper/tools/math/calculate_expression.py +52 -0
- devsper/tools/math/correlation.py +44 -0
- devsper/tools/math/distribution_summary.py +39 -0
- devsper/tools/math/histogram.py +53 -0
- devsper/tools/math/linear_regression.py +47 -0
- devsper/tools/math/matrix_multiply.py +38 -0
- devsper/tools/math/mean_std.py +35 -0
- devsper/tools/math/monte_carlo_simulation.py +43 -0
- devsper/tools/math/polynomial_fit.py +40 -0
- devsper/tools/math/random_sample.py +36 -0
- devsper/tools/mcp/__init__.py +23 -0
- devsper/tools/mcp/adapter.py +53 -0
- devsper/tools/mcp/client.py +235 -0
- devsper/tools/mcp/discovery.py +53 -0
- devsper/tools/memory/__init__.py +16 -0
- devsper/tools/memory/delete_memory.py +25 -0
- devsper/tools/memory/list_memory.py +34 -0
- devsper/tools/memory/search_memory.py +36 -0
- devsper/tools/memory/store_memory.py +47 -0
- devsper/tools/memory/summarize_memory.py +41 -0
- devsper/tools/memory/tag_memory.py +47 -0
- devsper/tools/pipelines.py +92 -0
- devsper/tools/registry.py +39 -0
- devsper/tools/research/__init__.py +12 -0
- devsper/tools/research/arxiv_download.py +55 -0
- devsper/tools/research/arxiv_search.py +58 -0
- devsper/tools/research/citation_extractor.py +35 -0
- devsper/tools/research/duckduckgo_search.py +42 -0
- devsper/tools/research/paper_metadata_extractor.py +45 -0
- devsper/tools/research/paper_summarizer.py +41 -0
- devsper/tools/research/research_question_generator.py +39 -0
- devsper/tools/research/topic_cluster.py +46 -0
- devsper/tools/research/web_search.py +47 -0
- devsper/tools/research/wikipedia_lookup.py +50 -0
- devsper/tools/research_advanced/__init__.py +14 -0
- devsper/tools/research_advanced/citation_context_extractor.py +60 -0
- devsper/tools/research_advanced/literature_review_generator.py +79 -0
- devsper/tools/research_advanced/methodology_extractor.py +58 -0
- devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
- devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
- devsper/tools/research_advanced/paper_method_comparator.py +62 -0
- devsper/tools/research_advanced/paper_similarity_search.py +69 -0
- devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
- devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
- devsper/tools/research_advanced/research_gap_finder.py +71 -0
- devsper/tools/research_advanced/research_topic_mapper.py +69 -0
- devsper/tools/research_advanced/swarm_literature_review.py +58 -0
- devsper/tools/scoring/__init__.py +52 -0
- devsper/tools/scoring/report.py +44 -0
- devsper/tools/scoring/scorer.py +39 -0
- devsper/tools/scoring/selector.py +61 -0
- devsper/tools/scoring/store.py +267 -0
- devsper/tools/selector.py +130 -0
- devsper/tools/system/__init__.py +12 -0
- devsper/tools/system/cpu_usage.py +22 -0
- devsper/tools/system/disk_usage.py +35 -0
- devsper/tools/system/environment_variables.py +29 -0
- devsper/tools/system/memory_usage.py +23 -0
- devsper/tools/system/pip_install.py +44 -0
- devsper/tools/system/pip_search.py +29 -0
- devsper/tools/system/process_list.py +34 -0
- devsper/tools/system/python_package_list.py +40 -0
- devsper/tools/system/run_shell_command.py +51 -0
- devsper/tools/system/system_info.py +26 -0
- devsper/tools/tool_runner.py +122 -0
- devsper/tui/__init__.py +5 -0
- devsper/tui/activity_feed_view.py +73 -0
- devsper/tui/adaptive_tasks_view.py +75 -0
- devsper/tui/agent_role_view.py +35 -0
- devsper/tui/app.py +395 -0
- devsper/tui/dashboard_screen.py +290 -0
- devsper/tui/dev_view.py +99 -0
- devsper/tui/inject_screen.py +73 -0
- devsper/tui/knowledge_graph_view.py +46 -0
- devsper/tui/layout.py +43 -0
- devsper/tui/logs_view.py +83 -0
- devsper/tui/memory_view.py +58 -0
- devsper/tui/performance_view.py +33 -0
- devsper/tui/reasoning_graph_view.py +39 -0
- devsper/tui/results_view.py +139 -0
- devsper/tui/swarm_view.py +37 -0
- devsper/tui/task_detail_screen.py +55 -0
- devsper/tui/task_view.py +103 -0
- devsper/types/event.py +97 -0
- devsper/types/exceptions.py +21 -0
- devsper/types/swarm.py +41 -0
- devsper/types/task.py +80 -0
- devsper/upgrade/__init__.py +21 -0
- devsper/upgrade/changelog.py +124 -0
- devsper/upgrade/cli.py +145 -0
- devsper/upgrade/installer.py +103 -0
- devsper/upgrade/notifier.py +52 -0
- devsper/upgrade/version_check.py +121 -0
- devsper/utils/event_logger.py +88 -0
- devsper/utils/http.py +43 -0
- devsper/utils/models.py +54 -0
- devsper/visualization/__init__.py +5 -0
- devsper/visualization/dag_export.py +67 -0
- devsper/workflow/__init__.py +18 -0
- devsper/workflow/conditions.py +157 -0
- devsper/workflow/context.py +108 -0
- devsper/workflow/loader.py +156 -0
- devsper/workflow/resolver.py +109 -0
- devsper/workflow/runner.py +562 -0
- devsper/workflow/schema.py +63 -0
- devsper/workflow/validator.py +128 -0
- devsper-2.1.6.dist-info/METADATA +346 -0
- devsper-2.1.6.dist-info/RECORD +375 -0
- devsper-2.1.6.dist-info/WHEEL +4 -0
- devsper-2.1.6.dist-info/entry_points.txt +3 -0
- devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
devsper/cli/init.py
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"""Developer onboarding: devsper init and devsper doctor."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Provider choices for interactive init: (id, display_name, env_var, planner_models, worker_models)
|
|
8
|
+
PROVIDER_REGISTRY: list[tuple[str, str, str | None, list[str], list[str]]] = [
|
|
9
|
+
(
|
|
10
|
+
"auto",
|
|
11
|
+
"Auto (detect from environment)",
|
|
12
|
+
None,
|
|
13
|
+
["auto"],
|
|
14
|
+
["auto"],
|
|
15
|
+
),
|
|
16
|
+
(
|
|
17
|
+
"openai",
|
|
18
|
+
"OpenAI (GPT-4o, GPT-4o-mini, etc.)",
|
|
19
|
+
"OPENAI_API_KEY",
|
|
20
|
+
["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1-preview", "o3-mini"],
|
|
21
|
+
["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1-preview", "o3-mini"],
|
|
22
|
+
),
|
|
23
|
+
(
|
|
24
|
+
"anthropic",
|
|
25
|
+
"Anthropic (Claude)",
|
|
26
|
+
"ANTHROPIC_API_KEY",
|
|
27
|
+
[
|
|
28
|
+
"claude-3-5-sonnet-20241022",
|
|
29
|
+
"claude-3-5-haiku-20241022",
|
|
30
|
+
"claude-3-opus-20240229",
|
|
31
|
+
"claude-3-haiku-20240307",
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"claude-3-5-sonnet-20241022",
|
|
35
|
+
"claude-3-5-haiku-20241022",
|
|
36
|
+
"claude-3-opus-20240229",
|
|
37
|
+
"claude-3-haiku-20240307",
|
|
38
|
+
],
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
"gemini",
|
|
42
|
+
"Google (Gemini)",
|
|
43
|
+
"GOOGLE_API_KEY",
|
|
44
|
+
["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-flash-exp"],
|
|
45
|
+
["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.0-flash-exp"],
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
"github",
|
|
49
|
+
"GitHub (Copilot Models)",
|
|
50
|
+
"GITHUB_TOKEN",
|
|
51
|
+
["github:copilot"],
|
|
52
|
+
["github:copilot"],
|
|
53
|
+
),
|
|
54
|
+
(
|
|
55
|
+
"azure_openai",
|
|
56
|
+
"Azure OpenAI",
|
|
57
|
+
"AZURE_OPENAI_ENDPOINT",
|
|
58
|
+
["gpt-4o", "gpt-4o-mini"],
|
|
59
|
+
["gpt-4o", "gpt-4o-mini"],
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
"mock",
|
|
63
|
+
"Mock (no API, for testing)",
|
|
64
|
+
None,
|
|
65
|
+
["mock"],
|
|
66
|
+
["mock"],
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _provider_choices_for_display() -> list[tuple[str, str]]:
|
|
72
|
+
"""Return [(id, display_line), ...] for menu."""
|
|
73
|
+
return [(pid, display) for pid, display, _env, _pl, _wk in PROVIDER_REGISTRY]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _select_option(prompt: str, choices: list[tuple[str, str]], default_index: int = 0) -> str:
|
|
77
|
+
"""Show numbered choices and return selected id. Uses 1-based input."""
|
|
78
|
+
from rich.console import Console
|
|
79
|
+
from rich.prompt import Prompt
|
|
80
|
+
|
|
81
|
+
console = Console()
|
|
82
|
+
for i, (_id, label) in enumerate(choices, start=1):
|
|
83
|
+
console.print(f" [cyan]{i}[/]. {label}")
|
|
84
|
+
default = default_index + 1
|
|
85
|
+
raw = Prompt.ask(prompt, default=str(default))
|
|
86
|
+
try:
|
|
87
|
+
idx = int(raw)
|
|
88
|
+
if 1 <= idx <= len(choices):
|
|
89
|
+
return choices[idx - 1][0]
|
|
90
|
+
except ValueError:
|
|
91
|
+
pass
|
|
92
|
+
return choices[default_index][0]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _select_model(prompt: str, model_ids: list[str], default_index: int = 0) -> str:
|
|
96
|
+
"""Show numbered model list and return selected model id."""
|
|
97
|
+
from rich.console import Console
|
|
98
|
+
from rich.prompt import Prompt
|
|
99
|
+
|
|
100
|
+
console = Console()
|
|
101
|
+
for i, mid in enumerate(model_ids, start=1):
|
|
102
|
+
console.print(f" [cyan]{i}[/]. {mid}")
|
|
103
|
+
default = default_index + 1
|
|
104
|
+
raw = Prompt.ask(prompt, default=str(default))
|
|
105
|
+
try:
|
|
106
|
+
idx = int(raw)
|
|
107
|
+
if 1 <= idx <= len(model_ids):
|
|
108
|
+
return model_ids[idx - 1]
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass
|
|
111
|
+
return model_ids[default_index]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _build_init_toml(
|
|
115
|
+
*,
|
|
116
|
+
workers: int = 4,
|
|
117
|
+
planner: str = "auto",
|
|
118
|
+
worker: str = "auto",
|
|
119
|
+
memory_enabled: bool = True,
|
|
120
|
+
tools_top_k: int = 12,
|
|
121
|
+
speculative_execution: bool = False,
|
|
122
|
+
cache_enabled: bool = False,
|
|
123
|
+
adaptive_planning: bool = False,
|
|
124
|
+
adaptive_execution: bool = False,
|
|
125
|
+
max_iterations: int = 10,
|
|
126
|
+
) -> str:
|
|
127
|
+
"""Build [swarm] [models] [memory] [tools] TOML snippet."""
|
|
128
|
+
return f"""[swarm]
|
|
129
|
+
workers = {workers}
|
|
130
|
+
speculative_execution = {str(speculative_execution).lower()}
|
|
131
|
+
cache_enabled = {str(cache_enabled).lower()}
|
|
132
|
+
adaptive_planning = {str(adaptive_planning).lower()}
|
|
133
|
+
adaptive_execution = {str(adaptive_execution).lower()}
|
|
134
|
+
max_iterations = {max_iterations}
|
|
135
|
+
|
|
136
|
+
[models]
|
|
137
|
+
planner = "{planner}"
|
|
138
|
+
worker = "{worker}"
|
|
139
|
+
|
|
140
|
+
[memory]
|
|
141
|
+
enabled = {str(memory_enabled).lower()}
|
|
142
|
+
|
|
143
|
+
[tools]
|
|
144
|
+
top_k = {tools_top_k}
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _run_init_interactive(cwd: Path) -> tuple[str, dict[str, str]]:
|
|
149
|
+
"""
|
|
150
|
+
CLI interactive flow: provider → GitHub device login or continue → planner/worker → options.
|
|
151
|
+
Returns (toml_content, api_keys to write to .env).
|
|
152
|
+
"""
|
|
153
|
+
from rich.console import Console
|
|
154
|
+
from rich.panel import Panel
|
|
155
|
+
from rich.prompt import Confirm, IntPrompt
|
|
156
|
+
|
|
157
|
+
console = Console()
|
|
158
|
+
api_keys: dict[str, str] = {}
|
|
159
|
+
|
|
160
|
+
console.print()
|
|
161
|
+
console.print(
|
|
162
|
+
Panel(
|
|
163
|
+
"[bold]devsper project setup[/]\n\nWe'll create [cyan]devsper.toml[/].",
|
|
164
|
+
title="Welcome",
|
|
165
|
+
border_style="green",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
console.print()
|
|
169
|
+
|
|
170
|
+
choices = _provider_choices_for_display()
|
|
171
|
+
provider_id = _select_option("Select model provider", choices, default_index=0)
|
|
172
|
+
console.print(f" [dim]Using provider: {provider_id}[/]\n")
|
|
173
|
+
|
|
174
|
+
# GitHub: device flow (show code, open browser, poll) if no token and client_id set
|
|
175
|
+
if provider_id == "github" and not os.environ.get("GITHUB_TOKEN"):
|
|
176
|
+
try:
|
|
177
|
+
from devsper.cli.github_oauth import (
|
|
178
|
+
GITHUB_DEVICE_CLIENT_ID,
|
|
179
|
+
GitHubDeviceFlowError,
|
|
180
|
+
run_device_flow_cli,
|
|
181
|
+
)
|
|
182
|
+
if GITHUB_DEVICE_CLIENT_ID:
|
|
183
|
+
token = run_device_flow_cli(client_id=GITHUB_DEVICE_CLIENT_ID, open_browser=True)
|
|
184
|
+
api_keys["GITHUB_TOKEN"] = token
|
|
185
|
+
os.environ["GITHUB_TOKEN"] = token
|
|
186
|
+
console.print("[green]✔[/] GitHub login successful.")
|
|
187
|
+
else:
|
|
188
|
+
console.print("[yellow]Paste a GitHub token (e.g. from https://github.com/settings/tokens)[/]")
|
|
189
|
+
from rich.prompt import Prompt
|
|
190
|
+
raw = Prompt.ask("GITHUB_TOKEN", password=True, default="")
|
|
191
|
+
if raw.strip():
|
|
192
|
+
api_keys["GITHUB_TOKEN"] = raw.strip()
|
|
193
|
+
os.environ["GITHUB_TOKEN"] = raw.strip()
|
|
194
|
+
except GitHubDeviceFlowError as e:
|
|
195
|
+
console.print(f"[red]GitHub login failed: {e}[/]")
|
|
196
|
+
from rich.prompt import Prompt
|
|
197
|
+
raw = Prompt.ask("Paste GITHUB_TOKEN (or leave blank)", password=True, default="")
|
|
198
|
+
if raw.strip():
|
|
199
|
+
api_keys["GITHUB_TOKEN"] = raw.strip()
|
|
200
|
+
os.environ["GITHUB_TOKEN"] = raw.strip()
|
|
201
|
+
|
|
202
|
+
planner_model = "auto"
|
|
203
|
+
worker_model = "auto"
|
|
204
|
+
for pid, _display, _env, planner_models, worker_models in PROVIDER_REGISTRY:
|
|
205
|
+
if pid != provider_id:
|
|
206
|
+
continue
|
|
207
|
+
if len(planner_models) > 1:
|
|
208
|
+
planner_model = _select_model(
|
|
209
|
+
"Planner model (for task decomposition)",
|
|
210
|
+
planner_models,
|
|
211
|
+
default_index=min(1, len(planner_models) - 1),
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
planner_model = planner_models[0]
|
|
215
|
+
if len(worker_models) > 1:
|
|
216
|
+
worker_model = _select_model(
|
|
217
|
+
"Worker model (for executing subtasks)",
|
|
218
|
+
worker_models,
|
|
219
|
+
default_index=min(1, len(worker_models) - 1),
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
worker_model = worker_models[0]
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
workers = 4
|
|
226
|
+
if Confirm.ask("Set number of workers?", default=True):
|
|
227
|
+
workers = IntPrompt.ask("Workers", default=4)
|
|
228
|
+
workers = max(1, min(32, workers))
|
|
229
|
+
|
|
230
|
+
memory_enabled = Confirm.ask("Enable memory (store results for context)?", default=True)
|
|
231
|
+
tools_top_k = 12
|
|
232
|
+
if Confirm.ask("Limit tools per task (top_k)?", default=True):
|
|
233
|
+
tools_top_k = IntPrompt.ask("top_k (0 = no limit)", default=12)
|
|
234
|
+
tools_top_k = max(0, min(50, tools_top_k))
|
|
235
|
+
|
|
236
|
+
speculative = Confirm.ask("Enable speculative execution?", default=False)
|
|
237
|
+
cache = Confirm.ask("Enable task cache?", default=False)
|
|
238
|
+
adaptive_planning = Confirm.ask("Enable adaptive planning?", default=False)
|
|
239
|
+
adaptive_execution = Confirm.ask("Enable adaptive execution?", default=False)
|
|
240
|
+
|
|
241
|
+
toml_content = _build_init_toml(
|
|
242
|
+
workers=workers,
|
|
243
|
+
planner=planner_model,
|
|
244
|
+
worker=worker_model,
|
|
245
|
+
memory_enabled=memory_enabled,
|
|
246
|
+
tools_top_k=tools_top_k,
|
|
247
|
+
speculative_execution=speculative,
|
|
248
|
+
cache_enabled=cache,
|
|
249
|
+
adaptive_planning=adaptive_planning,
|
|
250
|
+
adaptive_execution=adaptive_execution,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return toml_content, api_keys
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def run_init(interactive: bool = True) -> int:
|
|
257
|
+
"""
|
|
258
|
+
Set up a new project: create devsper.toml only (no dataset, no example workflow).
|
|
259
|
+
When interactive=True, CLI prompts for provider, models, workers, and options.
|
|
260
|
+
GitHub: device login (code + open browser) when no GITHUB_TOKEN.
|
|
261
|
+
"""
|
|
262
|
+
cwd = Path.cwd()
|
|
263
|
+
errors: list[str] = []
|
|
264
|
+
|
|
265
|
+
created_toml = False
|
|
266
|
+
toml_path = cwd / "devsper.toml"
|
|
267
|
+
api_keys: dict[str, str] = {}
|
|
268
|
+
|
|
269
|
+
if toml_path.exists():
|
|
270
|
+
errors.append("devsper.toml already exists")
|
|
271
|
+
else:
|
|
272
|
+
if interactive:
|
|
273
|
+
try:
|
|
274
|
+
toml_content, api_keys = _run_init_interactive(cwd)
|
|
275
|
+
try:
|
|
276
|
+
toml_path.write_text(toml_content, encoding="utf-8")
|
|
277
|
+
created_toml = True
|
|
278
|
+
except Exception as e:
|
|
279
|
+
errors.append(f"Failed to create devsper.toml: {e}")
|
|
280
|
+
if api_keys:
|
|
281
|
+
_write_env_file(cwd, api_keys)
|
|
282
|
+
except (KeyboardInterrupt, EOFError):
|
|
283
|
+
print("\nInit cancelled.", file=sys.stderr)
|
|
284
|
+
return 130
|
|
285
|
+
else:
|
|
286
|
+
toml_content = _build_init_toml(
|
|
287
|
+
workers=4,
|
|
288
|
+
planner="auto",
|
|
289
|
+
worker="auto",
|
|
290
|
+
memory_enabled=True,
|
|
291
|
+
tools_top_k=12,
|
|
292
|
+
)
|
|
293
|
+
try:
|
|
294
|
+
toml_path.write_text(toml_content, encoding="utf-8")
|
|
295
|
+
created_toml = True
|
|
296
|
+
except Exception as e:
|
|
297
|
+
errors.append(f"Failed to create devsper.toml: {e}")
|
|
298
|
+
|
|
299
|
+
env_ok = _check_env_quiet()
|
|
300
|
+
|
|
301
|
+
from rich.console import Console
|
|
302
|
+
|
|
303
|
+
console = Console()
|
|
304
|
+
if created_toml:
|
|
305
|
+
console.print("[green]✔[/] devsper.toml created")
|
|
306
|
+
if env_ok:
|
|
307
|
+
console.print("[green]✔[/] environment check passed")
|
|
308
|
+
elif not errors and created_toml:
|
|
309
|
+
console.print(
|
|
310
|
+
"[yellow]⚠[/] No API keys detected. Set OPENAI_API_KEY, ANTHROPIC_API_KEY, "
|
|
311
|
+
"GOOGLE_API_KEY, or GITHUB_TOKEN (see devsper doctor)."
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if errors:
|
|
315
|
+
for e in errors:
|
|
316
|
+
console.print(f"[red]✗[/] {e}", style="red")
|
|
317
|
+
return 1
|
|
318
|
+
|
|
319
|
+
# Offer to store API keys in credential store after setup
|
|
320
|
+
env_to_cred = {
|
|
321
|
+
"GITHUB_TOKEN": ("github", "token"),
|
|
322
|
+
"OPENAI_API_KEY": ("openai", "api_key"),
|
|
323
|
+
"ANTHROPIC_API_KEY": ("anthropic", "api_key"),
|
|
324
|
+
"GOOGLE_API_KEY": ("gemini", "api_key"),
|
|
325
|
+
"GEMINI_API_KEY": ("gemini", "api_key"),
|
|
326
|
+
"AZURE_OPENAI_API_KEY": ("azure", "api_key"),
|
|
327
|
+
"AZURE_OPENAI_ENDPOINT": ("azure", "endpoint"),
|
|
328
|
+
}
|
|
329
|
+
has_any_key = any(
|
|
330
|
+
(api_keys.get(env_var) or os.environ.get(env_var))
|
|
331
|
+
for env_var in env_to_cred
|
|
332
|
+
)
|
|
333
|
+
if interactive and created_toml and has_any_key:
|
|
334
|
+
try:
|
|
335
|
+
from rich.prompt import Confirm
|
|
336
|
+
|
|
337
|
+
if Confirm.ask("\nWould you like to store your API keys securely now?", default=True):
|
|
338
|
+
from devsper.credentials import set_credential
|
|
339
|
+
|
|
340
|
+
stored = 0
|
|
341
|
+
for env_var, (provider, key) in env_to_cred.items():
|
|
342
|
+
val = api_keys.get(env_var) or os.environ.get(env_var)
|
|
343
|
+
if val and str(val).strip():
|
|
344
|
+
set_credential(provider, key, str(val).strip())
|
|
345
|
+
stored += 1
|
|
346
|
+
if stored:
|
|
347
|
+
console.print(f"[green]✔[/] Stored {stored} credential(s) securely")
|
|
348
|
+
except (KeyboardInterrupt, EOFError):
|
|
349
|
+
pass
|
|
350
|
+
except Exception:
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
console.print()
|
|
354
|
+
console.print("Next steps:")
|
|
355
|
+
console.print(" 1) Set API keys if not already set (or use GitHub device login when choosing GitHub)")
|
|
356
|
+
console.print(" 2) Edit devsper.toml if needed")
|
|
357
|
+
console.print(' 3) Run: [cyan]devsper run "your task"[/]')
|
|
358
|
+
return 0
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _write_env_file(cwd: Path, api_keys: dict[str, str]) -> None:
|
|
362
|
+
"""Append new API keys to .env in cwd (skip keys already present)."""
|
|
363
|
+
if not api_keys:
|
|
364
|
+
return
|
|
365
|
+
env_path = cwd / ".env"
|
|
366
|
+
lines: list[str] = []
|
|
367
|
+
existing_keys: set[str] = set()
|
|
368
|
+
if env_path.exists():
|
|
369
|
+
lines = env_path.read_text(encoding="utf-8").rstrip().splitlines()
|
|
370
|
+
existing_keys = {ln.split("=", 1)[0].strip() for ln in lines if "=" in ln}
|
|
371
|
+
for k, v in api_keys.items():
|
|
372
|
+
if k not in existing_keys:
|
|
373
|
+
lines.append(f'{k}="{v}"')
|
|
374
|
+
env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _check_env_quiet() -> bool:
|
|
378
|
+
"""Return True if at least one provider env is set."""
|
|
379
|
+
return bool(
|
|
380
|
+
os.environ.get("OPENAI_API_KEY")
|
|
381
|
+
or os.environ.get("ANTHROPIC_API_KEY")
|
|
382
|
+
or os.environ.get("GITHUB_TOKEN")
|
|
383
|
+
or os.environ.get("GOOGLE_API_KEY")
|
|
384
|
+
or os.environ.get("GEMINI_API_KEY")
|
|
385
|
+
or (os.environ.get("AZURE_OPENAI_ENDPOINT") and os.environ.get("AZURE_OPENAI_API_KEY"))
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _check_plaintext_keys_in_toml(warnings: list[str]) -> None:
|
|
390
|
+
"""Warn (yellow) if API keys appear as plaintext in devsper.toml."""
|
|
391
|
+
from devsper.config.config_loader import project_config_paths
|
|
392
|
+
|
|
393
|
+
for p in project_config_paths():
|
|
394
|
+
if not p.is_file():
|
|
395
|
+
continue
|
|
396
|
+
try:
|
|
397
|
+
raw = p.read_text(encoding="utf-8")
|
|
398
|
+
except Exception:
|
|
399
|
+
continue
|
|
400
|
+
# Grep-style: look for credential-like values (sk-, ghp_, https://)
|
|
401
|
+
if '= "sk-' in raw or '="sk-' in raw:
|
|
402
|
+
warnings.append(
|
|
403
|
+
f"Possible API key (sk-...) found as plaintext in {p}; use 'devsper credentials set' and remove from TOML"
|
|
404
|
+
)
|
|
405
|
+
elif '= "ghp_' in raw or '="ghp_' in raw:
|
|
406
|
+
warnings.append(
|
|
407
|
+
f"Possible GitHub token found as plaintext in {p}; use 'devsper credentials set' and remove from TOML"
|
|
408
|
+
)
|
|
409
|
+
elif ('endpoint' in raw or 'api_key' in raw) and ('= "https://' in raw or '="https://' in raw):
|
|
410
|
+
warnings.append(
|
|
411
|
+
f"Possible endpoint/secret found as plaintext in {p}; use 'devsper credentials set' and remove from TOML"
|
|
412
|
+
)
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def run_doctor() -> int:
|
|
417
|
+
"""
|
|
418
|
+
Verify environment: GITHUB_TOKEN, OpenAI keys, config file, tool registry.
|
|
419
|
+
Warn if plaintext keys found in TOML.
|
|
420
|
+
"""
|
|
421
|
+
issues: list[str] = []
|
|
422
|
+
ok: list[str] = []
|
|
423
|
+
warnings: list[str] = []
|
|
424
|
+
mcp_ok: list[str] = []
|
|
425
|
+
mcp_warnings: list[str] = []
|
|
426
|
+
|
|
427
|
+
if os.environ.get("GITHUB_TOKEN"):
|
|
428
|
+
ok.append("GITHUB_TOKEN is set")
|
|
429
|
+
else:
|
|
430
|
+
issues.append("GITHUB_TOKEN not set (optional, for GitHub Models)")
|
|
431
|
+
|
|
432
|
+
if os.environ.get("OPENAI_API_KEY"):
|
|
433
|
+
ok.append("OPENAI_API_KEY is set")
|
|
434
|
+
else:
|
|
435
|
+
issues.append("OPENAI_API_KEY not set (optional)")
|
|
436
|
+
|
|
437
|
+
from devsper.config.config_loader import project_config_paths
|
|
438
|
+
|
|
439
|
+
found = False
|
|
440
|
+
for p in project_config_paths():
|
|
441
|
+
if p.is_file():
|
|
442
|
+
ok.append(f"Config file found: {p}")
|
|
443
|
+
found = True
|
|
444
|
+
break
|
|
445
|
+
if not found:
|
|
446
|
+
issues.append("No project config (devsper.toml or workflow.devsper.toml) in cwd or parent")
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
from devsper.tools.registry import list_tools
|
|
450
|
+
|
|
451
|
+
tools = list_tools()
|
|
452
|
+
ok.append(f"Tool registry: {len(tools)} tools loaded")
|
|
453
|
+
except Exception as e:
|
|
454
|
+
issues.append(f"Tool registry: {e}")
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
from devsper.tools.scoring import get_default_score_store
|
|
458
|
+
|
|
459
|
+
store = get_default_score_store()
|
|
460
|
+
n_records = store.result_count()
|
|
461
|
+
n_tools = store.tool_count()
|
|
462
|
+
ok.append(f"Tool scoring database: {n_records} records, {n_tools} tools tracked")
|
|
463
|
+
if n_tools > 0:
|
|
464
|
+
scores = store.get_all_scores()
|
|
465
|
+
with_10_plus = [s for s in scores if s.total_calls >= 10]
|
|
466
|
+
poor = [s for s in with_10_plus if s.composite_score < 0.40]
|
|
467
|
+
if with_10_plus and len(poor) / len(with_10_plus) > 0.20:
|
|
468
|
+
warnings.append(
|
|
469
|
+
f"Over 20% of tools (10+ calls) are in poor state ({len(poor)}/{len(with_10_plus)}). Consider reviewing with 'devsper tools --poor'."
|
|
470
|
+
)
|
|
471
|
+
dead = [s for s in with_10_plus if s.success_rate == 0.0]
|
|
472
|
+
for s in dead:
|
|
473
|
+
warnings.append(
|
|
474
|
+
f"Tool '{s.tool_name}' has 0% success with {s.total_calls} calls. Consider: devsper tools reset {s.tool_name}"
|
|
475
|
+
)
|
|
476
|
+
except Exception:
|
|
477
|
+
pass
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
from devsper.memory.memory_store import get_default_store
|
|
481
|
+
store = get_default_store()
|
|
482
|
+
all_records = store.list_memory(limit=10000, include_archived=True)
|
|
483
|
+
active = [r for r in all_records if not getattr(r, "archived", False)]
|
|
484
|
+
archived_count = len(all_records) - len(active)
|
|
485
|
+
ok.append(f"Memory: {len(active)} active, {archived_count} archived records")
|
|
486
|
+
if len(active) > 1000:
|
|
487
|
+
warnings.append(
|
|
488
|
+
"Memory store has > 1000 non-archived records. Consider running 'devsper memory consolidate'."
|
|
489
|
+
)
|
|
490
|
+
except Exception as e:
|
|
491
|
+
warnings.append(f"Memory store: {e}")
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
from devsper.knowledge.knowledge_graph import KnowledgeGraph
|
|
495
|
+
kg = KnowledgeGraph(store=get_default_store())
|
|
496
|
+
kg.load()
|
|
497
|
+
g = kg.graph
|
|
498
|
+
n_nodes = g.number_of_nodes()
|
|
499
|
+
n_edges = g.number_of_edges()
|
|
500
|
+
try:
|
|
501
|
+
from devsper.config import get_config
|
|
502
|
+
base = get_config().data_dir
|
|
503
|
+
except Exception:
|
|
504
|
+
base = os.environ.get("DEVSPER_DATA_DIR", ".devsper")
|
|
505
|
+
path = os.path.join(base, "knowledge_graph.json")
|
|
506
|
+
last_updated = "unknown"
|
|
507
|
+
if os.path.isfile(path):
|
|
508
|
+
from datetime import datetime, timezone
|
|
509
|
+
mtime = os.path.getmtime(path)
|
|
510
|
+
last_updated = datetime.fromtimestamp(mtime, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
511
|
+
ok.append(f"Knowledge graph: {n_nodes} nodes, {n_edges} edges, last updated {last_updated}")
|
|
512
|
+
except Exception as e:
|
|
513
|
+
warnings.append(f"Knowledge graph: {e}")
|
|
514
|
+
|
|
515
|
+
# v1.10.5: MCP and A2A
|
|
516
|
+
try:
|
|
517
|
+
from devsper.config import get_config
|
|
518
|
+
cfg = get_config()
|
|
519
|
+
mcp_servers = getattr(getattr(cfg, "mcp", None), "servers", None) or []
|
|
520
|
+
for s in mcp_servers:
|
|
521
|
+
sname = getattr(s, "name", "?")
|
|
522
|
+
try:
|
|
523
|
+
from devsper.tools.mcp import discover_mcp_tools
|
|
524
|
+
adapters = discover_mcp_tools(s)
|
|
525
|
+
mcp_ok.append(f" {sname}: {len(adapters)} tools")
|
|
526
|
+
except Exception as e:
|
|
527
|
+
mcp_warnings.append(f" {sname}: {e}")
|
|
528
|
+
if not mcp_servers:
|
|
529
|
+
mcp_ok.append(" (none configured — add [[mcp.servers]] to devsper.toml)")
|
|
530
|
+
a2a_agents = getattr(getattr(cfg, "a2a", None), "agents", None) or []
|
|
531
|
+
for a in a2a_agents:
|
|
532
|
+
aname = getattr(a, "name", "?")
|
|
533
|
+
url = getattr(a, "url", "")
|
|
534
|
+
if not url:
|
|
535
|
+
continue
|
|
536
|
+
try:
|
|
537
|
+
from devsper.agents.a2a.client import A2AClient
|
|
538
|
+
import asyncio
|
|
539
|
+
client = A2AClient()
|
|
540
|
+
card = asyncio.run(client.get_agent_card(url))
|
|
541
|
+
ok.append(f"A2A agent '{aname or card.name}': {len(card.skills)} skills")
|
|
542
|
+
except Exception as e:
|
|
543
|
+
warnings.append(f"A2A agent '{aname}': {e}")
|
|
544
|
+
except Exception:
|
|
545
|
+
pass
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
from devsper.config import get_config
|
|
549
|
+
cfg = get_config()
|
|
550
|
+
nodes_mode = getattr(getattr(cfg, "nodes", None), "mode", "single")
|
|
551
|
+
if nodes_mode == "distributed":
|
|
552
|
+
try:
|
|
553
|
+
import redis
|
|
554
|
+
r = redis.from_url(getattr(getattr(cfg, "bus", None), "redis_url", "redis://localhost:6379"))
|
|
555
|
+
r.ping()
|
|
556
|
+
ok.append("Redis reachable (distributed mode)")
|
|
557
|
+
except ImportError:
|
|
558
|
+
issues.append("Distributed mode requires: pip install devsper[distributed]")
|
|
559
|
+
except Exception as e:
|
|
560
|
+
issues.append(f"Redis not reachable: {e}")
|
|
561
|
+
try:
|
|
562
|
+
import fastapi
|
|
563
|
+
import uvicorn
|
|
564
|
+
ok.append("fastapi + uvicorn installed (RPC)")
|
|
565
|
+
except ImportError:
|
|
566
|
+
warnings.append("RPC endpoints need: pip install devsper[distributed]")
|
|
567
|
+
if not getattr(getattr(cfg, "nodes", None), "rpc_token", None):
|
|
568
|
+
warnings.append("nodes.rpc_token not set — RPC endpoints are unauthenticated")
|
|
569
|
+
else:
|
|
570
|
+
ok.append("Running in single-node mode — no cluster checks needed")
|
|
571
|
+
except Exception:
|
|
572
|
+
pass
|
|
573
|
+
|
|
574
|
+
_check_plaintext_keys_in_toml(warnings)
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
from devsper.runtime.run_history import RunHistory, HISTORY_DB
|
|
578
|
+
history = RunHistory()
|
|
579
|
+
stats = history.get_stats()
|
|
580
|
+
n = stats.get("total_runs", 0)
|
|
581
|
+
total_cost = stats.get("total_estimated_cost_usd", 0.0)
|
|
582
|
+
if HISTORY_DB.exists():
|
|
583
|
+
ok.append(f"Run history: {n} runs recorded, ${total_cost:.4f} total estimated spend")
|
|
584
|
+
else:
|
|
585
|
+
ok.append("Run history: DB not yet created (will be created on first run)")
|
|
586
|
+
if n > 0:
|
|
587
|
+
from datetime import datetime, timezone, timedelta
|
|
588
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
|
589
|
+
rows = history.list_runs(limit=50)
|
|
590
|
+
for r in rows:
|
|
591
|
+
started = getattr(r, "started_at", "") or ""
|
|
592
|
+
if started < cutoff:
|
|
593
|
+
continue
|
|
594
|
+
total = getattr(r, "total_tasks", 0) or 0
|
|
595
|
+
failed = getattr(r, "failed_tasks", 0) or 0
|
|
596
|
+
if total > 0 and (failed / total) > 0.50:
|
|
597
|
+
warnings.append(
|
|
598
|
+
f"Run {getattr(r, 'run_id', '?')[:24]}... had >50% task failure ({failed}/{total}) in last 7 days"
|
|
599
|
+
)
|
|
600
|
+
except Exception as e:
|
|
601
|
+
warnings.append(f"Run history: {e}")
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
from devsper.cli.ui import console, devsperHeader, SectionHeader
|
|
605
|
+
import devsper
|
|
606
|
+
ver = getattr(devsper, "__version__", "?")
|
|
607
|
+
console.print(devsperHeader(version=ver))
|
|
608
|
+
console.print(SectionHeader("Health check"))
|
|
609
|
+
for s in ok:
|
|
610
|
+
console.print(f" [hive.success]✓[/] {s}")
|
|
611
|
+
for s in issues:
|
|
612
|
+
console.print(f" [hive.error]✗[/] {s}")
|
|
613
|
+
for s in warnings:
|
|
614
|
+
console.print(f" [hive.warning]⚠[/] {s}")
|
|
615
|
+
console.print(SectionHeader("MCP Servers"))
|
|
616
|
+
for s in mcp_ok:
|
|
617
|
+
console.print(f" [hive.success]✓[/] {s}")
|
|
618
|
+
for s in mcp_warnings:
|
|
619
|
+
console.print(f" [hive.warning]⚠[/] {s}")
|
|
620
|
+
if warnings or issues:
|
|
621
|
+
console.print()
|
|
622
|
+
console.print(f" [hive.muted]{len(warnings)} warnings · {len(issues)} errors[/]")
|
|
623
|
+
except ImportError:
|
|
624
|
+
from rich.console import Console
|
|
625
|
+
c = Console()
|
|
626
|
+
for s in ok:
|
|
627
|
+
c.print(f"[green]✔[/] {s}")
|
|
628
|
+
for s in issues:
|
|
629
|
+
c.print(f"[red]✗[/] {s}", style="red")
|
|
630
|
+
for s in warnings:
|
|
631
|
+
c.print(f"[yellow]⚠[/] {s}", style="yellow")
|
|
632
|
+
c.print("\nMCP Servers:")
|
|
633
|
+
for s in mcp_ok:
|
|
634
|
+
c.print(f" [green]✔[/] {s}")
|
|
635
|
+
for s in mcp_warnings:
|
|
636
|
+
c.print(f" [yellow]⚠[/] {s}", style="yellow")
|
|
637
|
+
return 0 if not issues else 1
|