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.
Files changed (375) hide show
  1. devsper/__init__.py +14 -0
  2. devsper/agents/a2a/__init__.py +27 -0
  3. devsper/agents/a2a/client.py +126 -0
  4. devsper/agents/a2a/discovery.py +24 -0
  5. devsper/agents/a2a/server.py +128 -0
  6. devsper/agents/a2a/tool_adapter.py +68 -0
  7. devsper/agents/a2a/types.py +49 -0
  8. devsper/agents/agent.py +602 -0
  9. devsper/agents/critic.py +80 -0
  10. devsper/agents/message_bus.py +124 -0
  11. devsper/agents/roles.py +181 -0
  12. devsper/agents/run_agent.py +78 -0
  13. devsper/analytics/__init__.py +5 -0
  14. devsper/analytics/tool_analytics.py +78 -0
  15. devsper/audit/__init__.py +5 -0
  16. devsper/audit/logger.py +214 -0
  17. devsper/bus/__init__.py +29 -0
  18. devsper/bus/backends/__init__.py +5 -0
  19. devsper/bus/backends/base.py +38 -0
  20. devsper/bus/backends/memory.py +55 -0
  21. devsper/bus/backends/redis.py +146 -0
  22. devsper/bus/message.py +56 -0
  23. devsper/bus/schema_version.py +3 -0
  24. devsper/bus/topics.py +19 -0
  25. devsper/cache/__init__.py +6 -0
  26. devsper/cache/embedding_index.py +98 -0
  27. devsper/cache/hashing.py +24 -0
  28. devsper/cache/store.py +153 -0
  29. devsper/cache/task_cache.py +191 -0
  30. devsper/cli/__init__.py +6 -0
  31. devsper/cli/commands/reg.py +733 -0
  32. devsper/cli/github_oauth.py +157 -0
  33. devsper/cli/init.py +637 -0
  34. devsper/cli/main.py +2956 -0
  35. devsper/cli/run_progress.py +103 -0
  36. devsper/cli/ui/__init__.py +65 -0
  37. devsper/cli/ui/components.py +94 -0
  38. devsper/cli/ui/errors.py +104 -0
  39. devsper/cli/ui/logging.py +120 -0
  40. devsper/cli/ui/onboarding.py +102 -0
  41. devsper/cli/ui/progress.py +43 -0
  42. devsper/cli/ui/run_view.py +308 -0
  43. devsper/cli/ui/theme.py +40 -0
  44. devsper/cluster/__init__.py +29 -0
  45. devsper/cluster/election.py +84 -0
  46. devsper/cluster/local.py +97 -0
  47. devsper/cluster/node_info.py +77 -0
  48. devsper/cluster/registry.py +71 -0
  49. devsper/cluster/router.py +117 -0
  50. devsper/cluster/state_backend.py +105 -0
  51. devsper/compliance/__init__.py +5 -0
  52. devsper/compliance/pii.py +147 -0
  53. devsper/config/__init__.py +52 -0
  54. devsper/config/config_loader.py +121 -0
  55. devsper/config/defaults.py +77 -0
  56. devsper/config/resolver.py +342 -0
  57. devsper/config/schema.py +237 -0
  58. devsper/credentials/__init__.py +19 -0
  59. devsper/credentials/cli.py +197 -0
  60. devsper/credentials/migration.py +124 -0
  61. devsper/credentials/store.py +142 -0
  62. devsper/dashboard/__init__.py +9 -0
  63. devsper/dashboard/dashboard.py +87 -0
  64. devsper/dev/__init__.py +25 -0
  65. devsper/dev/builder.py +195 -0
  66. devsper/dev/debugger.py +95 -0
  67. devsper/dev/repo_index.py +138 -0
  68. devsper/dev/sandbox.py +203 -0
  69. devsper/dev/scaffold.py +122 -0
  70. devsper/embeddings/__init__.py +5 -0
  71. devsper/embeddings/service.py +36 -0
  72. devsper/explainability/__init__.py +14 -0
  73. devsper/explainability/decision_tree.py +104 -0
  74. devsper/explainability/rationale.py +38 -0
  75. devsper/explainability/simulation.py +56 -0
  76. devsper/hitl/__init__.py +13 -0
  77. devsper/hitl/approval.py +160 -0
  78. devsper/hitl/escalation.py +95 -0
  79. devsper/intelligence/__init__.py +9 -0
  80. devsper/intelligence/adaptation.py +88 -0
  81. devsper/intelligence/analysis/__init__.py +19 -0
  82. devsper/intelligence/analysis/analyzer.py +71 -0
  83. devsper/intelligence/analysis/cost_estimator.py +66 -0
  84. devsper/intelligence/analysis/formatter.py +103 -0
  85. devsper/intelligence/analysis/run_report.py +402 -0
  86. devsper/intelligence/learning_engine.py +92 -0
  87. devsper/intelligence/strategies/__init__.py +23 -0
  88. devsper/intelligence/strategies/base.py +14 -0
  89. devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
  90. devsper/intelligence/strategies/data_science_strategy.py +33 -0
  91. devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
  92. devsper/intelligence/strategies/experiment_strategy.py +33 -0
  93. devsper/intelligence/strategies/research_strategy.py +34 -0
  94. devsper/intelligence/strategy_selector.py +84 -0
  95. devsper/intelligence/synthesis.py +132 -0
  96. devsper/intelligence/task_optimizer.py +92 -0
  97. devsper/knowledge/__init__.py +5 -0
  98. devsper/knowledge/extractor.py +204 -0
  99. devsper/knowledge/knowledge_graph.py +184 -0
  100. devsper/knowledge/query.py +285 -0
  101. devsper/memory/__init__.py +35 -0
  102. devsper/memory/consolidation.py +138 -0
  103. devsper/memory/embeddings.py +60 -0
  104. devsper/memory/memory_index.py +97 -0
  105. devsper/memory/memory_router.py +62 -0
  106. devsper/memory/memory_store.py +221 -0
  107. devsper/memory/memory_types.py +54 -0
  108. devsper/memory/namespaces.py +45 -0
  109. devsper/memory/scoring.py +77 -0
  110. devsper/memory/summarizer.py +52 -0
  111. devsper/nodes/__init__.py +5 -0
  112. devsper/nodes/controller.py +449 -0
  113. devsper/nodes/rpc.py +127 -0
  114. devsper/nodes/single.py +161 -0
  115. devsper/nodes/worker.py +506 -0
  116. devsper/orchestration/__init__.py +19 -0
  117. devsper/orchestration/meta_planner.py +239 -0
  118. devsper/orchestration/priority_queue.py +61 -0
  119. devsper/plugins/__init__.py +19 -0
  120. devsper/plugins/marketplace/__init__.py +0 -0
  121. devsper/plugins/plugin_loader.py +70 -0
  122. devsper/plugins/plugin_registry.py +34 -0
  123. devsper/plugins/registry.py +83 -0
  124. devsper/protocols/__init__.py +6 -0
  125. devsper/providers/__init__.py +17 -0
  126. devsper/providers/anthropic.py +84 -0
  127. devsper/providers/base.py +75 -0
  128. devsper/providers/complexity_router.py +94 -0
  129. devsper/providers/gemini.py +36 -0
  130. devsper/providers/github.py +180 -0
  131. devsper/providers/model_router.py +40 -0
  132. devsper/providers/openai.py +105 -0
  133. devsper/providers/router/__init__.py +21 -0
  134. devsper/providers/router/backends/__init__.py +19 -0
  135. devsper/providers/router/backends/anthropic_backend.py +111 -0
  136. devsper/providers/router/backends/custom_backend.py +138 -0
  137. devsper/providers/router/backends/gemini_backend.py +89 -0
  138. devsper/providers/router/backends/github_backend.py +165 -0
  139. devsper/providers/router/backends/ollama_backend.py +104 -0
  140. devsper/providers/router/backends/openai_backend.py +142 -0
  141. devsper/providers/router/backends/vllm_backend.py +35 -0
  142. devsper/providers/router/base.py +60 -0
  143. devsper/providers/router/factory.py +92 -0
  144. devsper/providers/router/legacy.py +101 -0
  145. devsper/providers/router/router.py +135 -0
  146. devsper/reasoning/__init__.py +12 -0
  147. devsper/reasoning/graph.py +59 -0
  148. devsper/reasoning/nodes.py +20 -0
  149. devsper/reasoning/store.py +67 -0
  150. devsper/runtime/__init__.py +12 -0
  151. devsper/runtime/health.py +88 -0
  152. devsper/runtime/replay.py +53 -0
  153. devsper/runtime/replay_engine.py +142 -0
  154. devsper/runtime/run_history.py +204 -0
  155. devsper/runtime/telemetry.py +116 -0
  156. devsper/runtime/visualize.py +58 -0
  157. devsper/sandbox/__init__.py +13 -0
  158. devsper/sandbox/sandbox.py +161 -0
  159. devsper/swarm/checkpointer.py +65 -0
  160. devsper/swarm/executor.py +558 -0
  161. devsper/swarm/map_reduce.py +44 -0
  162. devsper/swarm/planner.py +197 -0
  163. devsper/swarm/prefetcher.py +91 -0
  164. devsper/swarm/scheduler.py +153 -0
  165. devsper/swarm/speculation.py +47 -0
  166. devsper/swarm/swarm.py +562 -0
  167. devsper/tools/__init__.py +33 -0
  168. devsper/tools/base.py +29 -0
  169. devsper/tools/code_intelligence/__init__.py +13 -0
  170. devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
  171. devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
  172. devsper/tools/code_intelligence/codebase_indexer.py +71 -0
  173. devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
  174. devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
  175. devsper/tools/code_intelligence/large_function_detector.py +68 -0
  176. devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
  177. devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
  178. devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
  179. devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
  180. devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
  181. devsper/tools/coding/__init__.py +12 -0
  182. devsper/tools/coding/analyze_code_complexity.py +48 -0
  183. devsper/tools/coding/dependency_analyzer.py +42 -0
  184. devsper/tools/coding/extract_functions.py +38 -0
  185. devsper/tools/coding/format_python.py +50 -0
  186. devsper/tools/coding/generate_docstrings.py +40 -0
  187. devsper/tools/coding/generate_unit_tests.py +42 -0
  188. devsper/tools/coding/lint_python.py +51 -0
  189. devsper/tools/coding/refactor_function.py +41 -0
  190. devsper/tools/coding/repo_structure_map.py +54 -0
  191. devsper/tools/coding/run_python.py +53 -0
  192. devsper/tools/data/__init__.py +12 -0
  193. devsper/tools/data/column_type_detection.py +64 -0
  194. devsper/tools/data/csv_summary.py +52 -0
  195. devsper/tools/data/dataframe_filter.py +51 -0
  196. devsper/tools/data/dataframe_groupby.py +47 -0
  197. devsper/tools/data/dataframe_stats.py +38 -0
  198. devsper/tools/data/dataset_sampling.py +55 -0
  199. devsper/tools/data/dataset_schema.py +45 -0
  200. devsper/tools/data/json_pretty_print.py +37 -0
  201. devsper/tools/data/json_query.py +46 -0
  202. devsper/tools/data/missing_value_report.py +47 -0
  203. devsper/tools/data_science/__init__.py +13 -0
  204. devsper/tools/data_science/correlation_heatmap.py +72 -0
  205. devsper/tools/data_science/dataset_bias_detector.py +49 -0
  206. devsper/tools/data_science/dataset_distribution_report.py +64 -0
  207. devsper/tools/data_science/dataset_drift_detector.py +64 -0
  208. devsper/tools/data_science/dataset_outlier_detector.py +65 -0
  209. devsper/tools/data_science/dataset_profile.py +76 -0
  210. devsper/tools/data_science/distributed_dataset_processor.py +54 -0
  211. devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
  212. devsper/tools/data_science/feature_importance_estimator.py +82 -0
  213. devsper/tools/data_science/model_input_validator.py +59 -0
  214. devsper/tools/data_science/time_series_analyzer.py +57 -0
  215. devsper/tools/documents/__init__.py +11 -0
  216. devsper/tools/documents/_docproc.py +56 -0
  217. devsper/tools/documents/document_to_markdown.py +29 -0
  218. devsper/tools/documents/extract_document_images.py +39 -0
  219. devsper/tools/documents/extract_document_text.py +29 -0
  220. devsper/tools/documents/extract_equations.py +36 -0
  221. devsper/tools/documents/extract_tables.py +47 -0
  222. devsper/tools/documents/summarize_document.py +42 -0
  223. devsper/tools/documents/write_latex_document.py +133 -0
  224. devsper/tools/documents/write_markdown_document.py +89 -0
  225. devsper/tools/documents/write_word_document.py +149 -0
  226. devsper/tools/experiments/__init__.py +13 -0
  227. devsper/tools/experiments/bootstrap_estimator.py +54 -0
  228. devsper/tools/experiments/experiment_report_generator.py +50 -0
  229. devsper/tools/experiments/experiment_tracker.py +36 -0
  230. devsper/tools/experiments/grid_search_runner.py +50 -0
  231. devsper/tools/experiments/model_benchmark_runner.py +45 -0
  232. devsper/tools/experiments/monte_carlo_experiment.py +38 -0
  233. devsper/tools/experiments/parameter_sweep_runner.py +51 -0
  234. devsper/tools/experiments/result_comparator.py +58 -0
  235. devsper/tools/experiments/simulation_runner.py +43 -0
  236. devsper/tools/experiments/statistical_significance_test.py +56 -0
  237. devsper/tools/experiments/swarm_map_reduce.py +42 -0
  238. devsper/tools/filesystem/__init__.py +12 -0
  239. devsper/tools/filesystem/append_file.py +42 -0
  240. devsper/tools/filesystem/file_hash.py +40 -0
  241. devsper/tools/filesystem/file_line_count.py +36 -0
  242. devsper/tools/filesystem/file_metadata.py +38 -0
  243. devsper/tools/filesystem/file_preview.py +55 -0
  244. devsper/tools/filesystem/find_large_files.py +50 -0
  245. devsper/tools/filesystem/list_directory.py +39 -0
  246. devsper/tools/filesystem/read_file.py +35 -0
  247. devsper/tools/filesystem/search_files.py +60 -0
  248. devsper/tools/filesystem/write_file.py +41 -0
  249. devsper/tools/flagship/__init__.py +15 -0
  250. devsper/tools/flagship/distributed_document_analysis.py +77 -0
  251. devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
  252. devsper/tools/flagship/repository_semantic_map.py +99 -0
  253. devsper/tools/flagship/research_graph_builder.py +111 -0
  254. devsper/tools/flagship/swarm_experiment_runner.py +86 -0
  255. devsper/tools/knowledge/__init__.py +10 -0
  256. devsper/tools/knowledge/citation_graph_builder.py +69 -0
  257. devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
  258. devsper/tools/knowledge/corpus_builder.py +66 -0
  259. devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
  260. devsper/tools/knowledge/document_corpus_summary.py +68 -0
  261. devsper/tools/knowledge/document_topic_extractor.py +58 -0
  262. devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
  263. devsper/tools/knowledge/timeline_extractor.py +59 -0
  264. devsper/tools/math/__init__.py +12 -0
  265. devsper/tools/math/calculate_expression.py +52 -0
  266. devsper/tools/math/correlation.py +44 -0
  267. devsper/tools/math/distribution_summary.py +39 -0
  268. devsper/tools/math/histogram.py +53 -0
  269. devsper/tools/math/linear_regression.py +47 -0
  270. devsper/tools/math/matrix_multiply.py +38 -0
  271. devsper/tools/math/mean_std.py +35 -0
  272. devsper/tools/math/monte_carlo_simulation.py +43 -0
  273. devsper/tools/math/polynomial_fit.py +40 -0
  274. devsper/tools/math/random_sample.py +36 -0
  275. devsper/tools/mcp/__init__.py +23 -0
  276. devsper/tools/mcp/adapter.py +53 -0
  277. devsper/tools/mcp/client.py +235 -0
  278. devsper/tools/mcp/discovery.py +53 -0
  279. devsper/tools/memory/__init__.py +16 -0
  280. devsper/tools/memory/delete_memory.py +25 -0
  281. devsper/tools/memory/list_memory.py +34 -0
  282. devsper/tools/memory/search_memory.py +36 -0
  283. devsper/tools/memory/store_memory.py +47 -0
  284. devsper/tools/memory/summarize_memory.py +41 -0
  285. devsper/tools/memory/tag_memory.py +47 -0
  286. devsper/tools/pipelines.py +92 -0
  287. devsper/tools/registry.py +39 -0
  288. devsper/tools/research/__init__.py +12 -0
  289. devsper/tools/research/arxiv_download.py +55 -0
  290. devsper/tools/research/arxiv_search.py +58 -0
  291. devsper/tools/research/citation_extractor.py +35 -0
  292. devsper/tools/research/duckduckgo_search.py +42 -0
  293. devsper/tools/research/paper_metadata_extractor.py +45 -0
  294. devsper/tools/research/paper_summarizer.py +41 -0
  295. devsper/tools/research/research_question_generator.py +39 -0
  296. devsper/tools/research/topic_cluster.py +46 -0
  297. devsper/tools/research/web_search.py +47 -0
  298. devsper/tools/research/wikipedia_lookup.py +50 -0
  299. devsper/tools/research_advanced/__init__.py +14 -0
  300. devsper/tools/research_advanced/citation_context_extractor.py +60 -0
  301. devsper/tools/research_advanced/literature_review_generator.py +79 -0
  302. devsper/tools/research_advanced/methodology_extractor.py +58 -0
  303. devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
  304. devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
  305. devsper/tools/research_advanced/paper_method_comparator.py +62 -0
  306. devsper/tools/research_advanced/paper_similarity_search.py +69 -0
  307. devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
  308. devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
  309. devsper/tools/research_advanced/research_gap_finder.py +71 -0
  310. devsper/tools/research_advanced/research_topic_mapper.py +69 -0
  311. devsper/tools/research_advanced/swarm_literature_review.py +58 -0
  312. devsper/tools/scoring/__init__.py +52 -0
  313. devsper/tools/scoring/report.py +44 -0
  314. devsper/tools/scoring/scorer.py +39 -0
  315. devsper/tools/scoring/selector.py +61 -0
  316. devsper/tools/scoring/store.py +267 -0
  317. devsper/tools/selector.py +130 -0
  318. devsper/tools/system/__init__.py +12 -0
  319. devsper/tools/system/cpu_usage.py +22 -0
  320. devsper/tools/system/disk_usage.py +35 -0
  321. devsper/tools/system/environment_variables.py +29 -0
  322. devsper/tools/system/memory_usage.py +23 -0
  323. devsper/tools/system/pip_install.py +44 -0
  324. devsper/tools/system/pip_search.py +29 -0
  325. devsper/tools/system/process_list.py +34 -0
  326. devsper/tools/system/python_package_list.py +40 -0
  327. devsper/tools/system/run_shell_command.py +51 -0
  328. devsper/tools/system/system_info.py +26 -0
  329. devsper/tools/tool_runner.py +122 -0
  330. devsper/tui/__init__.py +5 -0
  331. devsper/tui/activity_feed_view.py +73 -0
  332. devsper/tui/adaptive_tasks_view.py +75 -0
  333. devsper/tui/agent_role_view.py +35 -0
  334. devsper/tui/app.py +395 -0
  335. devsper/tui/dashboard_screen.py +290 -0
  336. devsper/tui/dev_view.py +99 -0
  337. devsper/tui/inject_screen.py +73 -0
  338. devsper/tui/knowledge_graph_view.py +46 -0
  339. devsper/tui/layout.py +43 -0
  340. devsper/tui/logs_view.py +83 -0
  341. devsper/tui/memory_view.py +58 -0
  342. devsper/tui/performance_view.py +33 -0
  343. devsper/tui/reasoning_graph_view.py +39 -0
  344. devsper/tui/results_view.py +139 -0
  345. devsper/tui/swarm_view.py +37 -0
  346. devsper/tui/task_detail_screen.py +55 -0
  347. devsper/tui/task_view.py +103 -0
  348. devsper/types/event.py +97 -0
  349. devsper/types/exceptions.py +21 -0
  350. devsper/types/swarm.py +41 -0
  351. devsper/types/task.py +80 -0
  352. devsper/upgrade/__init__.py +21 -0
  353. devsper/upgrade/changelog.py +124 -0
  354. devsper/upgrade/cli.py +145 -0
  355. devsper/upgrade/installer.py +103 -0
  356. devsper/upgrade/notifier.py +52 -0
  357. devsper/upgrade/version_check.py +121 -0
  358. devsper/utils/event_logger.py +88 -0
  359. devsper/utils/http.py +43 -0
  360. devsper/utils/models.py +54 -0
  361. devsper/visualization/__init__.py +5 -0
  362. devsper/visualization/dag_export.py +67 -0
  363. devsper/workflow/__init__.py +18 -0
  364. devsper/workflow/conditions.py +157 -0
  365. devsper/workflow/context.py +108 -0
  366. devsper/workflow/loader.py +156 -0
  367. devsper/workflow/resolver.py +109 -0
  368. devsper/workflow/runner.py +562 -0
  369. devsper/workflow/schema.py +63 -0
  370. devsper/workflow/validator.py +128 -0
  371. devsper-2.1.6.dist-info/METADATA +346 -0
  372. devsper-2.1.6.dist-info/RECORD +375 -0
  373. devsper-2.1.6.dist-info/WHEEL +4 -0
  374. devsper-2.1.6.dist-info/entry_points.txt +3 -0
  375. 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