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
@@ -0,0 +1,103 @@
1
+ """
2
+ Installer detection (uv vs pip) and install execution.
3
+ """
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Literal
11
+
12
+ PACKAGE_NAME = "devsper"
13
+ INSTALL_TIMEOUT = 120
14
+
15
+
16
+ def detect_installer() -> Literal["uv", "pip"]:
17
+ """
18
+ Check in order: DEVSPER_INSTALLER env, uv-managed env, which uv, else pip.
19
+ """
20
+ env = os.environ.get("DEVSPER_INSTALLER", "").strip().lower()
21
+ if env in ("uv", "pip"):
22
+ return "uv" if env == "uv" else "pip"
23
+
24
+ # Check if we're in a uv-managed environment
25
+ cwd = Path.cwd()
26
+ home = Path.home()
27
+ while cwd != home and cwd != cwd.parent:
28
+ if (cwd / ".python-version").exists() or (cwd / "uv.lock").exists():
29
+ if shutil.which("uv"):
30
+ return "uv"
31
+ break
32
+ cwd = cwd.parent
33
+ if (home / ".python-version").exists() or (home / "uv.lock").exists():
34
+ if shutil.which("uv"):
35
+ return "uv"
36
+
37
+ # sys.executable in .venv or /uv/ often indicates uv
38
+ exe = sys.executable
39
+ if "/uv/" in exe or ".venv" in exe:
40
+ if shutil.which("uv"):
41
+ return "uv"
42
+
43
+ if shutil.which("uv"):
44
+ return "uv"
45
+ return "pip"
46
+
47
+
48
+ def get_install_command(
49
+ installer: Literal["uv", "pip"],
50
+ package: str = PACKAGE_NAME,
51
+ version: str | None = None,
52
+ ) -> list[str]:
53
+ """Build the install command list."""
54
+ spec = f"{package}=={version}" if version else package
55
+ if installer == "uv":
56
+ return ["uv", "pip", "install", "--upgrade", spec]
57
+ return [sys.executable, "-m", "pip", "install", "--upgrade", spec]
58
+
59
+
60
+ def perform_install(
61
+ installer: Literal["uv", "pip"], version: str | None = None
62
+ ) -> tuple[bool, str]:
63
+ """
64
+ Run the install command. Returns (success, output).
65
+ Timeout 120 seconds.
66
+ """
67
+ cmd = get_install_command(installer, version=version)
68
+ try:
69
+ result = subprocess.run(
70
+ cmd,
71
+ capture_output=True,
72
+ text=True,
73
+ timeout=INSTALL_TIMEOUT,
74
+ )
75
+ out = (result.stdout or "") + (result.stderr or "")
76
+ if result.returncode != 0:
77
+ return (False, out)
78
+ return (True, out)
79
+ except subprocess.TimeoutExpired:
80
+ return (False, "Installation timed out after 120 seconds.")
81
+ except Exception as e:
82
+ return (False, str(e))
83
+
84
+
85
+ def verify_installation(expected_version: str) -> bool:
86
+ """Run Python to get installed devsper version and compare to expected."""
87
+ try:
88
+ result = subprocess.run(
89
+ [
90
+ sys.executable,
91
+ "-c",
92
+ "import importlib.metadata; print(importlib.metadata.version('devsper'))",
93
+ ],
94
+ capture_output=True,
95
+ text=True,
96
+ timeout=10,
97
+ )
98
+ if result.returncode != 0:
99
+ return False
100
+ actual = result.stdout.strip()
101
+ return actual == expected_version
102
+ except Exception:
103
+ return False
@@ -0,0 +1,52 @@
1
+ """
2
+ Startup nag: print upgrade notice if behind, once per session; suppress during upgrade.
3
+ """
4
+
5
+ import time
6
+
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+
10
+ _notified = False
11
+ _suppress = False
12
+ _console = Console(stderr=True)
13
+
14
+
15
+ def suppress_notifications() -> None:
16
+ """Call when running 'devsper upgrade' so the nag does not appear."""
17
+ global _suppress
18
+ _suppress = True
19
+
20
+
21
+ def check_and_notify() -> None:
22
+ """
23
+ If an update is available, print a compact Rich notice once per session.
24
+ Non-blocking, try/except, aim for <100ms (cache read usually).
25
+ """
26
+ global _notified
27
+ if _suppress or _notified:
28
+ return
29
+ deadline = time.monotonic() + 0.1 # 100ms max
30
+ try:
31
+ from .version_check import is_update_available, get_version_diff_type
32
+
33
+ if time.monotonic() > deadline:
34
+ return
35
+ available, current, latest = is_update_available()
36
+ if not available or current == latest:
37
+ return
38
+ if time.monotonic() > deadline:
39
+ return
40
+ _notified = True
41
+ diff_type = get_version_diff_type(current, latest)
42
+ color: str = {"major": "red", "minor": "yellow", "patch": "green"}.get(
43
+ diff_type, "white"
44
+ )
45
+ msg = (
46
+ f"Update available: [bold]{current}[/bold] → [bold]{latest}[/bold] "
47
+ f"[{color}][{diff_type}][/{color}]\n"
48
+ "Run [bold]devsper upgrade[/bold] to update"
49
+ )
50
+ _console.print(Panel(msg, border_style="dim", padding=(0, 1)))
51
+ except Exception:
52
+ pass
@@ -0,0 +1,121 @@
1
+ """
2
+ Version checking: PyPI latest version, cache, semver comparison.
3
+ """
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Literal
8
+
9
+ import httpx
10
+
11
+ PYPI_URL = "https://pypi.org/pypi/devsper/json"
12
+ CACHE_FILE = Path("~/.config/devsper/update_check.json").expanduser()
13
+ CACHE_TTL_HOURS = 24
14
+ PACKAGE_NAME = "devsper"
15
+
16
+
17
+ def get_current_version() -> str:
18
+ """Read current installed version from importlib.metadata."""
19
+ import importlib.metadata
20
+
21
+ return importlib.metadata.version(PACKAGE_NAME)
22
+
23
+
24
+ def _cache_is_fresh() -> bool:
25
+ if not CACHE_FILE.is_file():
26
+ return False
27
+ try:
28
+ data = json.loads(CACHE_FILE.read_text())
29
+ checked_at = data.get("checked_at", "")
30
+ if not checked_at:
31
+ return False
32
+ from datetime import datetime, timezone
33
+
34
+ # ISO format with Z
35
+ dt = datetime.fromisoformat(checked_at.replace("Z", "+00:00"))
36
+ now = datetime.now(timezone.utc)
37
+ age_hours = (now - dt).total_seconds() / 3600
38
+ return age_hours < CACHE_TTL_HOURS
39
+ except (json.JSONDecodeError, ValueError, OSError):
40
+ return False
41
+
42
+
43
+ def _read_cached_version() -> str | None:
44
+ if not _cache_is_fresh():
45
+ return None
46
+ try:
47
+ data = json.loads(CACHE_FILE.read_text())
48
+ return data.get("latest")
49
+ except (json.JSONDecodeError, OSError):
50
+ return None
51
+
52
+
53
+ def _write_cache(latest: str) -> None:
54
+ from datetime import datetime, timezone
55
+
56
+ CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
57
+ data = {
58
+ "latest": latest,
59
+ "checked_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
60
+ }
61
+ CACHE_FILE.write_text(json.dumps(data, indent=2))
62
+ try:
63
+ CACHE_FILE.chmod(0o600)
64
+ except OSError:
65
+ pass
66
+
67
+
68
+ def get_latest_version() -> str:
69
+ """
70
+ Fetch latest version from PyPI with 24h cache.
71
+ On network error, return current version (fail silently).
72
+ """
73
+ cached = _read_cached_version()
74
+ if cached is not None:
75
+ return cached
76
+ try:
77
+ resp = httpx.get(PYPI_URL, timeout=5.0)
78
+ resp.raise_for_status()
79
+ info = resp.json().get("info") or {}
80
+ latest = info.get("version") or get_current_version()
81
+ _write_cache(latest)
82
+ return latest
83
+ except (httpx.HTTPError, json.JSONDecodeError, KeyError):
84
+ return get_current_version()
85
+
86
+
87
+ def parse_semver(version: str) -> tuple[int, int, int]:
88
+ """Parse '1.2.3' or '1.2.3.post1' into (1, 2, 3)."""
89
+ # Strip pre/post/dev suffixes for comparison
90
+ base = version.split("+")[0].split("-")[0]
91
+ parts = base.split(".")
92
+ major = int(parts[0]) if len(parts) > 0 else 0
93
+ minor = int(parts[1]) if len(parts) > 1 else 0
94
+ patch = int(parts[2]) if len(parts) > 2 else 0
95
+ return (major, minor, patch)
96
+
97
+
98
+ def get_version_diff_type(
99
+ current: str, latest: str
100
+ ) -> Literal["major", "minor", "patch"]:
101
+ """Compare semver tuples; return highest changed segment."""
102
+ c = parse_semver(current)
103
+ l = parse_semver(latest)
104
+ if l[0] != c[0]:
105
+ return "major"
106
+ if l[1] != c[1]:
107
+ return "minor"
108
+ return "patch"
109
+
110
+
111
+ def is_update_available() -> tuple[bool, str, str]:
112
+ """
113
+ Returns (available, current, latest).
114
+ Uses cache for latest; on network error latest may equal current.
115
+ """
116
+ current = get_current_version()
117
+ latest = get_latest_version()
118
+ c = parse_semver(current)
119
+ l = parse_semver(latest)
120
+ available = (l[0], l[1], l[2]) > (c[0], c[1], c[2])
121
+ return (available, current, latest)
@@ -0,0 +1,88 @@
1
+ import asyncio
2
+ import os
3
+ from datetime import datetime, timezone
4
+
5
+ from devsper.types.event import Event, events
6
+
7
+
8
+ # Map Event types to bus topics (v1.9). Unmapped events use event.<type_value>.
9
+ EVENT_TO_TOPIC = {
10
+ events.TASK_STARTED: "task.started",
11
+ events.TASK_COMPLETED: "task.completed",
12
+ events.TASK_FAILED: "task.failed",
13
+ events.TASK_CREATED: "task.ready",
14
+ events.AGENT_BROADCAST: "agent.broadcast",
15
+ events.SWARM_STARTED: "swarm.control",
16
+ events.SWARM_FINISHED: "swarm.control",
17
+ events.AGENT_STARTED: "agent.broadcast",
18
+ events.AGENT_FINISHED: "agent.broadcast",
19
+ events.EXECUTOR_STARTED: "swarm.control",
20
+ events.EXECUTOR_FINISHED: "swarm.control",
21
+ }
22
+
23
+
24
+ def _event_to_bus_topic(event_type: events) -> str:
25
+ return EVENT_TO_TOPIC.get(event_type, f"event.{event_type.value}")
26
+
27
+
28
+ class EventLog:
29
+ def __init__(
30
+ self,
31
+ events_folder_path: str = ".devsper/events",
32
+ bus: object = None,
33
+ run_id: str | None = None,
34
+ ):
35
+ os.makedirs(events_folder_path, exist_ok=True)
36
+ self.log_path = os.path.join(
37
+ events_folder_path, f"events_{datetime.now(timezone.utc)}.jsonl"
38
+ )
39
+ self._bus = bus
40
+ self._run_id = run_id
41
+
42
+ @property
43
+ def run_id(self) -> str:
44
+ """Identifier for this run (basename of log file without extension)."""
45
+ if self._run_id is not None:
46
+ return self._run_id
47
+ return os.path.basename(self.log_path).replace(".jsonl", "")
48
+
49
+ def append_event(self, event: Event) -> None:
50
+ with open(self.log_path, "a") as f:
51
+ f.write(event.model_dump_json() + "\n")
52
+ if self._bus is not None:
53
+ self._publish_to_bus(event)
54
+
55
+ def _publish_to_bus(self, event: Event) -> None:
56
+ try:
57
+ from devsper.bus.message import create_bus_message
58
+ topic = _event_to_bus_topic(event.type)
59
+ payload = event.to_dict()
60
+ msg = create_bus_message(
61
+ topic=topic,
62
+ payload=payload,
63
+ run_id=getattr(self, "run_id", "") or "",
64
+ )
65
+ loop = None
66
+ try:
67
+ loop = asyncio.get_running_loop()
68
+ except RuntimeError:
69
+ pass
70
+ if loop is not None:
71
+ loop.create_task(self._bus.publish(msg))
72
+ else:
73
+ try:
74
+ asyncio.run(self._bus.publish(msg))
75
+ except Exception:
76
+ pass
77
+ except Exception:
78
+ pass
79
+
80
+ def read_events(self) -> list[Event]:
81
+ if not os.path.exists(self.log_path):
82
+ return []
83
+ with open(self.log_path, "r") as f:
84
+ return [Event.model_validate_json(line) for line in f if line.strip()]
85
+
86
+ def clear(self) -> None:
87
+ if os.path.exists(self.log_path):
88
+ os.remove(self.log_path)
devsper/utils/http.py ADDED
@@ -0,0 +1,43 @@
1
+ """HTTP client helpers. Respects DEVSPER_SSL_VERIFY for corp/uni networks with SSL inspection."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+
7
+ def format_retry_after(response: Any) -> str:
8
+ """
9
+ Read Retry-After from response headers and return a short message for the user.
10
+ response: object with .headers (e.g. httpx.Response). Headers may be case-insensitive.
11
+ Returns e.g. " Back off: retry after 60s" or " Back off: retry after <date>" or "".
12
+ """
13
+ if response is None:
14
+ return ""
15
+ headers = getattr(response, "headers", None)
16
+ if headers is None:
17
+ return ""
18
+ try:
19
+ raw = headers.get("retry-after")
20
+ except Exception:
21
+ raw = None
22
+ if raw is None:
23
+ return ""
24
+ raw = str(raw).strip()
25
+ if not raw:
26
+ return ""
27
+ # Integer seconds
28
+ try:
29
+ sec = int(raw)
30
+ return f" Back off: retry after {sec}s"
31
+ except ValueError:
32
+ pass
33
+ # HTTP-date or other
34
+ return f" Back off: Retry-After {raw}"
35
+
36
+
37
+ def ssl_verify() -> bool:
38
+ """
39
+ Return False if DEVSPER_SSL_VERIFY is 'false' or '0' (e.g. university/corp WiFi with MITM).
40
+ Otherwise True. Use for httpx Client(verify=ssl_verify()).
41
+ """
42
+ v = os.environ.get("DEVSPER_SSL_VERIFY", "true").strip().lower()
43
+ return v not in ("false", "0", "no", "off")
@@ -0,0 +1,54 @@
1
+ """
2
+ Model invocation layer: generate(model_name, prompt) -> text.
3
+
4
+ Uses v2 LLMRouter when available (from config), else legacy ProviderRouter.
5
+ When model_name is "auto", use resolve_model(spec, task_type) first.
6
+ """
7
+
8
+ import asyncio
9
+
10
+ from devsper.providers.model_router import TaskType, select_model
11
+ from devsper.providers.router import get_router
12
+
13
+
14
+ def resolve_model(model_spec: str, task_type: TaskType) -> str:
15
+ """If model_spec is 'auto', return model_router.select_model(task_type); else return model_spec."""
16
+ if (model_spec or "").strip().lower() == "auto":
17
+ return select_model(task_type)
18
+ return model_spec or "mock"
19
+
20
+
21
+ def _run_async(coro):
22
+ """Run coroutine from sync context (new loop or current)."""
23
+ try:
24
+ loop = asyncio.get_running_loop()
25
+ except RuntimeError:
26
+ return asyncio.run(coro)
27
+ import concurrent.futures
28
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
29
+ future = pool.submit(asyncio.run, coro)
30
+ return future.result()
31
+
32
+
33
+ def generate(model_name: str, prompt: str, stream: bool = False):
34
+ """Call the model with the given prompt and return text output (or stream iterator if stream=True)."""
35
+ if not stream:
36
+ try:
37
+ from devsper.providers.router.factory import get_llm_router
38
+ from devsper.providers.router.base import LLMRequest
39
+ router = get_llm_router()
40
+ if router is not None:
41
+ req = LLMRequest(
42
+ model=model_name,
43
+ messages=[{"role": "user", "content": prompt}],
44
+ max_tokens=4096,
45
+ temperature=0.0,
46
+ tools=None,
47
+ stream=False,
48
+ )
49
+ resp = _run_async(router.route(req))
50
+ return resp.content
51
+ except Exception:
52
+ pass
53
+ provider = get_router().get_provider(model_name)
54
+ return provider.generate(model_name, prompt, stream=stream)
@@ -0,0 +1,5 @@
1
+ """DAG and run visualization."""
2
+
3
+ from devsper.visualization.dag_export import export_mermaid, export_graphviz, load_dag
4
+
5
+ __all__ = ["export_mermaid", "export_graphviz", "load_dag"]
@@ -0,0 +1,67 @@
1
+ """
2
+ Export task DAG to Mermaid or Graphviz format.
3
+
4
+ DAG is loaded from a JSON file written by the swarm run (run_id_dag.json).
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+
10
+
11
+ def _safe_id(node_id: str) -> str:
12
+ """Mermaid/Graphviz safe node id (no spaces or special chars)."""
13
+ return node_id.replace(" ", "_").replace("-", "_").replace(".", "_")
14
+
15
+
16
+ def load_dag(
17
+ events_dir: str | Path, run_id: str
18
+ ) -> tuple[list[dict], list[tuple[str, str]]]:
19
+ """
20
+ Load DAG from events_dir / {run_id}_dag.json.
21
+ Returns (nodes: [{"id", "description"}], edges: [(from_id, to_id)]).
22
+ """
23
+ path = Path(events_dir) / f"{run_id}_dag.json"
24
+ if not path.is_file():
25
+ return [], []
26
+ with open(path, "r", encoding="utf-8") as f:
27
+ data = json.load(f)
28
+ nodes = data.get("nodes", [])
29
+ edges = [tuple(e) for e in data.get("edges", [])]
30
+ return nodes, edges
31
+
32
+
33
+ def export_mermaid(nodes: list[dict], edges: list[tuple[str, str]]) -> str:
34
+ """Produce a Mermaid diagram string (flowchart)."""
35
+ lines = ["flowchart LR"]
36
+ for n in nodes:
37
+ nid = _safe_id(n.get("id", ""))
38
+ desc = (n.get("description", "") or nid).replace('"', "'")[:40]
39
+ lines.append(f' {nid}["{desc}"]')
40
+ for a, b in edges:
41
+ lines.append(f" {_safe_id(a)} --> {_safe_id(b)}")
42
+ return "\n".join(lines)
43
+
44
+
45
+ def export_graphviz(nodes: list[dict], edges: list[tuple[str, str]]) -> str:
46
+ """Produce a Graphviz DOT string."""
47
+ lines = ["digraph G {", " rankdir=LR;"]
48
+ for n in nodes:
49
+ nid = _safe_id(n.get("id", ""))
50
+ desc = (n.get("description", "") or nid).replace('"', '\\"')[:40]
51
+ lines.append(f' {nid} [label="{desc}"];')
52
+ for a, b in edges:
53
+ lines.append(f" {_safe_id(a)} -> {_safe_id(b)};")
54
+ lines.append("}")
55
+ return "\n".join(lines)
56
+
57
+
58
+ def list_run_ids(events_dir: str | Path) -> list[str]:
59
+ """List available run ids (from _dag.json files)."""
60
+ path = Path(events_dir)
61
+ if not path.is_dir():
62
+ return []
63
+ run_ids = []
64
+ for f in path.glob("*_dag.json"):
65
+ run_id = f.stem.replace("_dag", "")
66
+ run_ids.append(run_id)
67
+ return sorted(run_ids, reverse=True)
@@ -0,0 +1,18 @@
1
+ """Workflow definitions: load from workflow.devsper.toml and run by name (v1.4 pipeline engine)."""
2
+
3
+ from devsper.workflow.loader import load_workflow, list_workflows
4
+ from devsper.workflow.runner import WorkflowRunner, run_workflow, WorkflowStepError
5
+ from devsper.workflow.schema import WorkflowDefinition, WorkflowStep
6
+ from devsper.workflow.validator import ValidationReport, validate_workflow
7
+
8
+ __all__ = [
9
+ "load_workflow",
10
+ "list_workflows",
11
+ "run_workflow",
12
+ "WorkflowRunner",
13
+ "WorkflowDefinition",
14
+ "WorkflowStep",
15
+ "WorkflowStepError",
16
+ "ValidationReport",
17
+ "validate_workflow",
18
+ ]