py-env-studio 2.1.2__tar.gz → 2.1.4__tar.gz

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 (155) hide show
  1. {py_env_studio-2.1.2/py_env_studio.egg-info → py_env_studio-2.1.4}/PKG-INFO +3 -3
  2. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/PYPI_README.md +2 -2
  3. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/README.md +18 -21
  4. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/commands.py +36 -0
  5. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/config.ini +1 -1
  6. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/__init__.py +9 -9
  7. py_env_studio-2.1.4/py_env_studio/core/runtime_mismatch.py +133 -0
  8. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/runtime_toggle.py +79 -0
  9. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/engine.py +20 -19
  10. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/models.py +1 -0
  11. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/main_window.py +123 -4
  12. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/vulneribility_scanner.py +1 -1
  13. {py_env_studio-2.1.2 → py_env_studio-2.1.4/py_env_studio.egg-info}/PKG-INFO +3 -3
  14. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio.egg-info/SOURCES.txt +2 -0
  15. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/pyproject.toml +1 -1
  16. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_app_updates.py +3 -3
  17. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_cli_commands.py +40 -0
  18. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_mcp_consistency.py +13 -13
  19. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_mcp_server.py +26 -26
  20. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_project_intelligence.py +7 -7
  21. py_env_studio-2.1.4/tests/test_runtime_mismatch.py +155 -0
  22. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_template_engine.py +62 -1
  23. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/LICENSE +0 -0
  24. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/__init__.py +0 -0
  25. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/__main__.py +0 -0
  26. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/__init__.py +0 -0
  27. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/app_updates.py +0 -0
  28. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/auto_resolve.py +0 -0
  29. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/bootstrap.py +0 -0
  30. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/configuration.py +0 -0
  31. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/database.py +0 -0
  32. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/dependency_preview.py +0 -0
  33. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/env_manager.py +0 -0
  34. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/environment_lock.py +0 -0
  35. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/integration.py +0 -0
  36. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/__init__.py +0 -0
  37. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/config.py +0 -0
  38. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/context.py +0 -0
  39. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/errors.py +0 -0
  40. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/schemas/__init__.py +0 -0
  41. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/schemas/responses.py +0 -0
  42. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/server.py +0 -0
  43. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/analysis.py +0 -0
  44. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/environments.py +0 -0
  45. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/packages.py +0 -0
  46. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/project.py +0 -0
  47. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/mcp/tools/security.py +0 -0
  48. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/package_manager.py +0 -0
  49. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/package_update_monitor.py +0 -0
  50. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/pip_tools.py +0 -0
  51. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/plugins/__init__.py +0 -0
  52. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/plugins/base.py +0 -0
  53. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/plugins/exceptions.py +0 -0
  54. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/plugins/manager.py +0 -0
  55. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_contract/__init__.py +0 -0
  56. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_contract/models.py +0 -0
  57. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_contract/repository.py +0 -0
  58. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_contract/service.py +0 -0
  59. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_intelligence/__init__.py +0 -0
  60. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_intelligence/models.py +0 -0
  61. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_intelligence/service.py +0 -0
  62. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/project_launcher.py +0 -0
  63. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/py_tonic.py +0 -0
  64. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/runtime.py +0 -0
  65. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/runtime_cache.py +0 -0
  66. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/runtime_providers.py +0 -0
  67. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/__init__.py +0 -0
  68. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/environment_locks.sql +0 -0
  69. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/environments.sql +0 -0
  70. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/migration.sql +0 -0
  71. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/package_updates.sql +0 -0
  72. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/project_contract.sql +0 -0
  73. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/runtime_cache.sql +0 -0
  74. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/schema_core.sql +0 -0
  75. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/schema_runtime_cache.sql +0 -0
  76. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/schema/vulnerability.sql +0 -0
  77. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/setup_state.py +0 -0
  78. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/strategies.py +0 -0
  79. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/__init__.py +0 -0
  80. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/builtin_templates.py +0 -0
  81. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/community.py +0 -0
  82. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/content_filters.py +0 -0
  83. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/github_import.py +0 -0
  84. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/registry.py +0 -0
  85. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/user_template_store.py +0 -0
  86. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/validator.py +0 -0
  87. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/templates/workflow.py +0 -0
  88. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/tool_probe.py +0 -0
  89. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/tools.py +0 -0
  90. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/uv_tools.py +0 -0
  91. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/core/windows_apps.py +0 -0
  92. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/main.py +0 -0
  93. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/__init__.py +0 -0
  94. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/__init__.py +0 -0
  95. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/about.png +0 -0
  96. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/activate-env.png +0 -0
  97. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/available-env.png +0 -0
  98. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/create-env.png +0 -0
  99. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/delete-env.png +0 -0
  100. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/export.png +0 -0
  101. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/install.png +0 -0
  102. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/list-env.png +0 -0
  103. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/logo.png +0 -0
  104. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/packages.png +0 -0
  105. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/pes-default-transparrent.png +0 -0
  106. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/pes-icon-default.ico +0 -0
  107. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/pes-icon-default.png +0 -0
  108. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/pes-transparrent-icon-default.ico +0 -0
  109. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/requirements.png +0 -0
  110. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/selected-env.png +0 -0
  111. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/uninstall.png +0 -0
  112. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/static/icons/update.png +0 -0
  113. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/status_bar.py +0 -0
  114. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/ui/ui_tasks.py +0 -0
  115. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/__init__.py +0 -0
  116. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/app_icon.py +0 -0
  117. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/app_logging.py +0 -0
  118. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/db_status.py +0 -0
  119. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/handlers.py +0 -0
  120. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/progress.py +0 -0
  121. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/version_utils.py +0 -0
  122. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio/utils/vulneribility_insights.py +0 -0
  123. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio.egg-info/dependency_links.txt +0 -0
  124. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio.egg-info/entry_points.txt +0 -0
  125. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio.egg-info/requires.txt +0 -0
  126. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/py_env_studio.egg-info/top_level.txt +0 -0
  127. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/setup.cfg +0 -0
  128. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_all_features.py +0 -0
  129. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_app_logging.py +0 -0
  130. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_cli_progress.py +0 -0
  131. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_community_templates.py +0 -0
  132. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_configuration_service.py +0 -0
  133. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_dependency_preview.py +0 -0
  134. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_environment_lock.py +0 -0
  135. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_github_template_import.py +0 -0
  136. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_package_update_monitor.py +0 -0
  137. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_plugin_events.py +0 -0
  138. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_plugin_import.py +0 -0
  139. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_plugin_load.py +0 -0
  140. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_plugin_manager.py +0 -0
  141. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_project_contract.py +0 -0
  142. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_project_openers.py +0 -0
  143. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_project_runtime.py +0 -0
  144. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_runtime_cache.py +0 -0
  145. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_runtime_providers.py +0 -0
  146. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_schema_templates.py +0 -0
  147. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_status_progress.py +0 -0
  148. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_template_registry.py +0 -0
  149. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_template_workflow.py +0 -0
  150. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_ui_backend.py +0 -0
  151. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_user_template_store.py +0 -0
  152. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_uv_packages.py +0 -0
  153. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_vulnerability_insights_charts.py +0 -0
  154. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_vulnerability_insights_update.py +0 -0
  155. {py_env_studio-2.1.2 → py_env_studio-2.1.4}/tests/test_window_icon.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-env-studio
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: A comprehensive cross-platform GUI and CLI tool for managing Python virtual environments and packages with advanced features including environment lifecycle management, package operations, vulnerability scanning, security insights, project templates, Py-Tonic interactive learning mode, an extensible plugin system for custom functionality, and a local read-only MCP control plane (beta) for AI coding agents.
5
5
  Author-email: Py Env Studio Team <contact.shaikh.wasim@gmail.com>
6
6
  License: MIT License
@@ -109,7 +109,7 @@ Perfect for:
109
109
  - 🔌 Extensible **plugin system** with 18 lifecycle hooks
110
110
  - 📟 Full CLI with progress gauge and `-v` / `-q` / `--no-progress` controls
111
111
  - 🧪 **Beta:** local, read-only **MCP control plane** for AI coding agents
112
- (stdio transport, 8 `pyenv_*` tools, opt-out via `enabled = false`)
112
+ (stdio transport, 8 `pes_*` tools, opt-out via `enabled = false`)
113
113
 
114
114
  ## 📥 Installation
115
115
 
@@ -161,7 +161,7 @@ Setup steps for VS Code and other clients:
161
161
 
162
162
  - Documentation home: [https://py-env-studio.readthedocs.io/](https://py-env-studio.readthedocs.io/)
163
163
  - Usage manual: [https://py-env-studio.readthedocs.io/en/latest/getting-started/usage.html](https://py-env-studio.readthedocs.io/en/latest/getting-started/usage.html)
164
- - Release notes: [https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.2.html](https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.2.html)
164
+ - Release notes: [https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.4.html](https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.4.html)
165
165
  - Source code: [https://github.com/pyenvstudio/py-env-studio](https://github.com/pyenvstudio/py-env-studio)
166
166
  - Issue tracker: [https://github.com/pyenvstudio/py-env-studio/issues](https://github.com/pyenvstudio/py-env-studio/issues)
167
167
 
@@ -44,7 +44,7 @@ Perfect for:
44
44
  - 🔌 Extensible **plugin system** with 18 lifecycle hooks
45
45
  - 📟 Full CLI with progress gauge and `-v` / `-q` / `--no-progress` controls
46
46
  - 🧪 **Beta:** local, read-only **MCP control plane** for AI coding agents
47
- (stdio transport, 8 `pyenv_*` tools, opt-out via `enabled = false`)
47
+ (stdio transport, 8 `pes_*` tools, opt-out via `enabled = false`)
48
48
 
49
49
  ## 📥 Installation
50
50
 
@@ -96,7 +96,7 @@ Setup steps for VS Code and other clients:
96
96
 
97
97
  - Documentation home: [https://py-env-studio.readthedocs.io/](https://py-env-studio.readthedocs.io/)
98
98
  - Usage manual: [https://py-env-studio.readthedocs.io/en/latest/getting-started/usage.html](https://py-env-studio.readthedocs.io/en/latest/getting-started/usage.html)
99
- - Release notes: [https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.2.html](https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.2.html)
99
+ - Release notes: [https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.4.html](https://py-env-studio.readthedocs.io/en/latest/releases/v2.1.4.html)
100
100
  - Source code: [https://github.com/pyenvstudio/py-env-studio](https://github.com/pyenvstudio/py-env-studio)
101
101
  - Issue tracker: [https://github.com/pyenvstudio/py-env-studio/issues](https://github.com/pyenvstudio/py-env-studio/issues)
102
102
 
@@ -29,26 +29,23 @@ Perfect for:
29
29
 
30
30
  ---
31
31
 
32
- ## 🆕 What's new in v2.1.2
33
-
34
- - 🧪 **MCP control plane (beta)** — a local, read-only MCP server lets AI coding
35
- agents (VS Code Copilot and other MCP clients) read authoritative
36
- environment, package, dependency, security, and project state.
37
- → [Setup steps](docs/reference/mcp.md)
38
- - 🧩 **Project templates** — built-in Python Script / CLI / Package templates,
39
- your own templates from a local folder or GitHub, and GitHub
40
- **Community Templates** discovery.
41
- - ⚙️ **Configuration center** (Tools → Configuration) — default venv path,
42
- interpreter/runtime, package manager, project tool, template defaults,
43
- appearance, and UI scaling in one validated dialog.
44
- - 🐍 **Official Python Install Manager integration** — detect, list, install,
45
- and select official runtimes, with System/Custom providers alongside.
46
- - 🛡️ **Remediate from the dashboard** — `Update Now` per vulnerability and
47
- `Upgrade all Packages` per environment, with partial-failure reporting.
48
- - 📊 **Native dashboard charts** (`tkinter-dash`, no matplotlib), 📶 a CLI
49
- progress gauge and GUI status gauge, and consistent application icons.
50
-
51
- → [Full v2.1.2 release notes](docs/releases/v2.1.2.md)
32
+ ## 🆕 What's new in v2.1.4
33
+
34
+ - 🔗 **Project ↔ Environment association** — creating an environment from a
35
+ project template now registers it with the project by default, with an
36
+ explicit opt-out that creates an unregistered environment.
37
+ - 🗂️ **Project environment panel** (Project menu) — view the environment
38
+ associated with a project, switch the association to another PES
39
+ environment, or unregister it without deleting the environment.
40
+ - 🛡️ **Runtime mismatch guard** — PES compares the project's registered
41
+ environment with the runtime it can reliably observe, stays silent on a
42
+ match, and surfaces mismatches through `pes status`, `pes on`, and `pes run`
43
+ (warning once per unchanged mismatch).
44
+ - 👁️ **Conservative runtime detection** — PES only reports runtime information
45
+ it can observe; it does not infer which interpreter VS Code or PyCharm
46
+ selected, does not switch IDE interpreters, and needs no IDE extension.
47
+
48
+ → [Full v2.1.4 release notes](docs/releases/v2.1.4.md)
52
49
 
53
50
  ---
54
51
 
@@ -105,7 +102,7 @@ PyEnvStudio now features a powerful plugin system that allows developers to exte
105
102
  - **Easy Development** - Simple plugin API with lifecycle management
106
103
  - **Examples Included** - Full working sample plugin with documentation
107
104
 
108
- See the [v2.1.2 release notes](docs/releases/v2.1.2.md) and the [plugin development guide](docs/plugins/development.md).
105
+ See the [v2.1.4 release notes](docs/releases/v2.1.4.md) and the [plugin development guide](docs/plugins/development.md).
109
106
 
110
107
  ### Additional implemented capabilities
111
108
 
@@ -20,6 +20,12 @@ from py_env_studio.core.runtime_toggle import (
20
20
  init_project,
21
21
  list_registered_projects,
22
22
  )
23
+ from py_env_studio.core.runtime_mismatch import (
24
+ RuntimeMatchState,
25
+ clear_mismatch_notification,
26
+ detect_runtime_mismatch,
27
+ notify_mismatch_once,
28
+ )
23
29
  from py_env_studio.utils.app_logging import configure_logging
24
30
  from py_env_studio.utils.progress import TaskProgress
25
31
 
@@ -147,6 +153,7 @@ def handle_init(_: argparse.Namespace) -> None:
147
153
 
148
154
 
149
155
  def handle_on(_: argparse.Namespace) -> None:
156
+ _notify_runtime_mismatch()
150
157
  _print_runtime_result(enable_runtime())
151
158
 
152
159
 
@@ -166,6 +173,34 @@ def handle_status(_: argparse.Namespace) -> None:
166
173
  print(f"Environment Path: {status.get('environment_path', 'N/A')}")
167
174
  print(f"Python Version: {status['python_version']}")
168
175
 
176
+ root = Path(status["project_root"]) if status.get("project_root") else None
177
+ runtime = detect_runtime_mismatch(root)
178
+ if runtime.registered_environment_id:
179
+ print("Registered environment:")
180
+ print(f" {runtime.registered_environment_id}")
181
+ print(f" Python {runtime.registered_python_version or 'Unknown'}")
182
+ else:
183
+ print("Registered environment: None")
184
+ if runtime.current_python_executable:
185
+ print("Current runtime:")
186
+ print(f" {runtime.current_python_executable}")
187
+ print(f" Python {runtime.current_python_version or 'Unknown'}")
188
+ state_label = (
189
+ "ENVIRONMENT_MISMATCH"
190
+ if runtime.state is RuntimeMatchState.MISMATCH
191
+ else runtime.state.value
192
+ )
193
+ print(f"Status: {state_label}")
194
+
195
+
196
+ def _notify_runtime_mismatch() -> None:
197
+ runtime = detect_runtime_mismatch()
198
+ if runtime.state is RuntimeMatchState.MISMATCH:
199
+ if notify_mismatch_once(runtime):
200
+ print("Environment mismatch detected")
201
+ elif runtime.state is RuntimeMatchState.MATCH and runtime.project_path:
202
+ clear_mismatch_notification(runtime.project_path)
203
+
169
204
 
170
205
  def handle_list_projects(_: argparse.Namespace) -> None:
171
206
  projects = list_registered_projects()
@@ -188,6 +223,7 @@ def handle_run(args: argparse.Namespace) -> None:
188
223
 
189
224
  # No gauge here: the child process owns the terminal and its output must
190
225
  # not be interleaved with redrawn status lines.
226
+ _notify_runtime_mismatch()
191
227
  logger.info("Running %s in the managed environment", list(args.args))
192
228
  result = execute_in_managed_env(Path.cwd(), list(args.args))
193
229
  if result:
@@ -1,5 +1,5 @@
1
1
  [project]
2
- version = 2.1.2
2
+ version = 2.1.4
3
3
 
4
4
  [settings]
5
5
  venv_dir = ~/.py_env_studio/venvs
@@ -6,7 +6,7 @@ from . import analysis, environments, packages, project, security
6
6
 
7
7
  TOOL_DEFINITIONS = [
8
8
  {
9
- "name": "pyenv_list_environments",
9
+ "name": "pes_list_environments",
10
10
  "description": "List Python environments known to Py Env Studio.",
11
11
  "inputSchema": {
12
12
  "type": "object",
@@ -16,7 +16,7 @@ TOOL_DEFINITIONS = [
16
16
  "handler": environments.list_environments,
17
17
  },
18
18
  {
19
- "name": "pyenv_get_environment",
19
+ "name": "pes_get_environment",
20
20
  "description": "Return detailed information about one environment.",
21
21
  "inputSchema": {
22
22
  "type": "object",
@@ -27,7 +27,7 @@ TOOL_DEFINITIONS = [
27
27
  "handler": environments.get_environment,
28
28
  },
29
29
  {
30
- "name": "pyenv_list_packages",
30
+ "name": "pes_list_packages",
31
31
  "description": "List installed packages in an environment (pip/uv aware).",
32
32
  "inputSchema": {
33
33
  "type": "object",
@@ -38,7 +38,7 @@ TOOL_DEFINITIONS = [
38
38
  "handler": packages.list_packages,
39
39
  },
40
40
  {
41
- "name": "pyenv_get_project_context",
41
+ "name": "pes_get_project_context",
42
42
  "description": "Authoritative project/environment/runtime context for the current project.",
43
43
  "inputSchema": {
44
44
  "type": "object",
@@ -48,7 +48,7 @@ TOOL_DEFINITIONS = [
48
48
  "handler": project.get_project_context,
49
49
  },
50
50
  {
51
- "name": "pyenv_get_environment_status",
51
+ "name": "pes_get_environment_status",
52
52
  "description": "Concise machine-readable environment status.",
53
53
  "inputSchema": {
54
54
  "type": "object",
@@ -59,7 +59,7 @@ TOOL_DEFINITIONS = [
59
59
  "handler": environments.get_environment_status,
60
60
  },
61
61
  {
62
- "name": "pyenv_scan_vulnerabilities",
62
+ "name": "pes_scan_vulnerabilities",
63
63
  "description": "Return cached vulnerability findings for an environment (no network scan).",
64
64
  "inputSchema": {
65
65
  "type": "object",
@@ -70,7 +70,7 @@ TOOL_DEFINITIONS = [
70
70
  "handler": security.scan_vulnerabilities,
71
71
  },
72
72
  {
73
- "name": "pyenv_get_dependency_information",
73
+ "name": "pes_get_dependency_information",
74
74
  "description": "Expose local dependency information (pip show based, offline).",
75
75
  "inputSchema": {
76
76
  "type": "object",
@@ -84,7 +84,7 @@ TOOL_DEFINITIONS = [
84
84
  "handler": project.get_dependency_information,
85
85
  },
86
86
  {
87
- "name": "pyenv_analyze_project",
87
+ "name": "pes_analyze_project",
88
88
  "description": "Analyze a Python project using Py Env Studio and return authoritative environment, Python runtime, package manager, dependency, outdated-package, vulnerability, runtime, and PES project configuration information. This operation is read-only and does not modify the project or environment.",
89
89
  "inputSchema": {
90
90
  "type": "object",
@@ -94,7 +94,7 @@ TOOL_DEFINITIONS = [
94
94
  "handler": analysis.analyze_project,
95
95
  },
96
96
  {
97
- "name": "pyenv_check_consistency",
97
+ "name": "pes_check_consistency",
98
98
  "description": "Before modifying any Python project's dependencies or running its tests, call this to determine which interpreter, environment, lockfile, and manifest the project actually uses, and whether they agree.",
99
99
  "inputSchema": {
100
100
  "type": "object",
@@ -0,0 +1,133 @@
1
+ """Conservative comparison of a project's PES environment and a known runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from hashlib import sha256
8
+ from pathlib import Path
9
+ import os
10
+ import sys
11
+
12
+ from . import env_manager, runtime_toggle
13
+
14
+
15
+ class RuntimeMatchState(str, Enum):
16
+ MATCH = "MATCH"
17
+ MISMATCH = "MISMATCH"
18
+ REGISTERED_ENVIRONMENT_UNAVAILABLE = "REGISTERED_ENVIRONMENT_UNAVAILABLE"
19
+ NO_PROJECT_ASSOCIATION = "NO_PROJECT_ASSOCIATION"
20
+ UNKNOWN = "UNKNOWN"
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class DetectedRuntime:
25
+ """Runtime facts supplied by PES or a future integration adapter."""
26
+
27
+ executable: str | None
28
+ python_version: str | None
29
+ reliable: bool = True
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class RuntimeMismatch:
34
+ project_path: str | None
35
+ project_name: str | None
36
+ registered_environment_id: str | None
37
+ registered_python_executable: str | None
38
+ registered_python_version: str | None
39
+ current_python_executable: str | None
40
+ current_python_version: str | None
41
+ state: RuntimeMatchState
42
+
43
+
44
+ def _process_runtime() -> DetectedRuntime:
45
+ """Return the interpreter running PES, not an IDE's selected interpreter."""
46
+ version = sys.version_info
47
+ return DetectedRuntime(
48
+ executable=sys.executable or None,
49
+ python_version=f"{version.major}.{version.minor}.{version.micro}",
50
+ reliable=bool(sys.executable),
51
+ )
52
+
53
+
54
+ def _canonical_path(path: str) -> str:
55
+ return os.path.normcase(str(Path(path).resolve()))
56
+
57
+
58
+ def detect_runtime_mismatch(
59
+ project_root: Path | str | None = None,
60
+ current_runtime: DetectedRuntime | None = None,
61
+ ) -> RuntimeMismatch:
62
+ """Compare registered and current executables without inferring IDE state."""
63
+ root = Path(project_root).resolve() if project_root is not None else runtime_toggle.get_project_root()
64
+ metadata = runtime_toggle.load_project_metadata(root) if root else {}
65
+ runtime = current_runtime if current_runtime is not None else _process_runtime()
66
+ env_id = metadata.get("environment_id") or None
67
+
68
+ registered_executable = None
69
+ registered_version = None
70
+ state = RuntimeMatchState.UNKNOWN
71
+ if not env_id:
72
+ state = RuntimeMatchState.NO_PROJECT_ASSOCIATION
73
+ else:
74
+ info = env_manager.get_environment_info(str(env_id))
75
+ if info is None:
76
+ state = RuntimeMatchState.REGISTERED_ENVIRONMENT_UNAVAILABLE
77
+ else:
78
+ registered_executable = str(info.get("python_executable") or "") or None
79
+ registered_version = info.get("python_version") or metadata.get("python_version") or None
80
+ if not registered_executable or not Path(registered_executable).is_file():
81
+ state = RuntimeMatchState.REGISTERED_ENVIRONMENT_UNAVAILABLE
82
+ elif not runtime.reliable or not runtime.executable or not Path(runtime.executable).is_file():
83
+ state = RuntimeMatchState.UNKNOWN
84
+ else:
85
+ same_executable = _canonical_path(registered_executable) == _canonical_path(runtime.executable)
86
+ if same_executable:
87
+ if runtime.python_version and registered_version and runtime.python_version != registered_version:
88
+ state = RuntimeMatchState.MISMATCH
89
+ elif runtime.python_version:
90
+ state = RuntimeMatchState.MATCH
91
+ else:
92
+ state = RuntimeMatchState.MISMATCH
93
+
94
+ return RuntimeMismatch(
95
+ project_path=str(root) if root else None,
96
+ project_name=metadata.get("project_name") if metadata else (root.name if root else None),
97
+ registered_environment_id=str(env_id) if env_id else None,
98
+ registered_python_executable=registered_executable,
99
+ registered_python_version=str(registered_version) if registered_version else None,
100
+ current_python_executable=runtime.executable,
101
+ current_python_version=runtime.python_version,
102
+ state=state,
103
+ )
104
+
105
+
106
+ def notify_mismatch_once(result: RuntimeMismatch) -> bool:
107
+ """Persist a per-project mismatch signature and report only new mismatches."""
108
+ if result.state is not RuntimeMatchState.MISMATCH or not result.project_path:
109
+ return False
110
+
111
+ signature_data = "\0".join(
112
+ str(value or "")
113
+ for value in (
114
+ result.registered_environment_id,
115
+ result.registered_python_executable,
116
+ result.registered_python_version,
117
+ result.current_python_executable,
118
+ result.current_python_version,
119
+ )
120
+ )
121
+ signature = sha256(signature_data.encode("utf-8")).hexdigest()
122
+ root = Path(result.project_path)
123
+ if runtime_toggle.get_last_mismatch_signature(root) == signature:
124
+ return False
125
+ runtime_toggle.set_last_mismatch_signature(root, signature)
126
+ return True
127
+
128
+
129
+ def clear_mismatch_notification(project_root: Path | str) -> None:
130
+ """Allow a later recurrence to notify after a project returns to a match."""
131
+ root = Path(project_root).resolve()
132
+ if runtime_toggle.get_last_mismatch_signature(root):
133
+ runtime_toggle.set_last_mismatch_signature(root, "")
@@ -104,6 +104,7 @@ def save_project_metadata(project_root: Path, metadata: dict) -> None:
104
104
  parser["runtime"] = {
105
105
  "enabled": str(metadata.get("runtime_enabled", False)).lower(),
106
106
  "auto_init": str(metadata.get("auto_init", True)).lower(),
107
+ "last_mismatch_signature": metadata.get("last_mismatch_signature", ""),
107
108
  }
108
109
  with open(metadata_path, "w", encoding="utf-8") as f:
109
110
  parser.write(f)
@@ -112,6 +113,20 @@ def save_project_metadata(project_root: Path, metadata: dict) -> None:
112
113
  raise
113
114
 
114
115
 
116
+ def get_last_mismatch_signature(project_root: Path) -> str:
117
+ """Read notification suppression state without expanding public metadata."""
118
+ parser = ConfigParser()
119
+ parser.read(get_project_metadata_path(project_root), encoding="utf-8")
120
+ return parser.get("runtime", "last_mismatch_signature", fallback="")
121
+
122
+
123
+ def set_last_mismatch_signature(project_root: Path, signature: str) -> None:
124
+ """Persist a mismatch notification signature in the existing project file."""
125
+ metadata = load_project_metadata(project_root)
126
+ metadata["last_mismatch_signature"] = signature
127
+ save_project_metadata(project_root, metadata)
128
+
129
+
115
130
  def load_registry() -> dict:
116
131
  """Load the global registry."""
117
132
  import json
@@ -142,6 +157,70 @@ def save_registry(registry: dict) -> None:
142
157
  raise
143
158
 
144
159
 
160
+ def associate_environment_with_project(
161
+ project_root: Path,
162
+ environment_id: str,
163
+ ) -> None:
164
+ """Associate an existing PES environment with a project."""
165
+ from . import env_manager
166
+
167
+ project_root = Path(project_root).resolve()
168
+ env_info = env_manager.get_environment_info(environment_id)
169
+ if env_info is None:
170
+ raise FileNotFoundError(f"Environment '{environment_id}' is unavailable")
171
+
172
+ metadata = load_project_metadata(project_root)
173
+ metadata.update(
174
+ {
175
+ "project_name": metadata.get("project_name", project_root.name),
176
+ "project_root": str(project_root),
177
+ "environment_id": environment_id,
178
+ "environment_path": env_info.get("path", ""),
179
+ "python_version": env_info.get("python_version") or "",
180
+ "package_manager": env_info.get("package_manager", "pip"),
181
+ "runtime_enabled": metadata.get("runtime_enabled", True),
182
+ "auto_init": metadata.get("auto_init", True),
183
+ }
184
+ )
185
+ save_project_metadata(project_root, metadata)
186
+
187
+ registry = load_registry()
188
+ project_name = metadata["project_name"]
189
+ project_entry = registry.setdefault(project_name, {})
190
+ project_entry.update(
191
+ {
192
+ "project_root": str(project_root),
193
+ "environment_id": environment_id,
194
+ "environment_path": metadata["environment_path"],
195
+ "python_version": metadata["python_version"],
196
+ "runtime_enabled": metadata["runtime_enabled"],
197
+ }
198
+ )
199
+ save_registry(registry)
200
+
201
+
202
+ def unregister_project_environment(project_root: Path) -> None:
203
+ """Remove a project's environment association without deleting the environment."""
204
+ project_root = Path(project_root).resolve()
205
+ metadata = load_project_metadata(project_root)
206
+ metadata.update(
207
+ {
208
+ "project_name": metadata.get("project_name", project_root.name),
209
+ "project_root": str(project_root),
210
+ "environment_id": "",
211
+ "environment_path": "",
212
+ "last_mismatch_signature": "",
213
+ }
214
+ )
215
+ save_project_metadata(project_root, metadata)
216
+
217
+ registry = load_registry()
218
+ project_entry = registry.get(metadata["project_name"])
219
+ if project_entry and Path(project_entry.get("project_root", "")).resolve() == project_root:
220
+ project_entry.update({"environment_id": "", "environment_path": "", "python_version": ""})
221
+ save_registry(registry)
222
+
223
+
145
224
  def generate_environment_id(project_name: str) -> str:
146
225
  """Generate a unique environment ID for a project."""
147
226
  import hashlib
@@ -37,6 +37,11 @@ class TemplateEngine:
37
37
  def __init__(self, registry: TemplateRegistry | None = None) -> None:
38
38
  self.registry = registry or get_default_registry()
39
39
 
40
+ @staticmethod
41
+ def environment_name_for_project(project_name: str) -> str:
42
+ """Return the generated environment name for a project name."""
43
+ return f"{project_name.strip().lower().replace(' ', '-')}-env".replace("_", "-")[:45]
44
+
40
45
  def list_templates(self):
41
46
  return self.registry.list_all()
42
47
 
@@ -98,6 +103,7 @@ class TemplateEngine:
98
103
  project_path=project_path,
99
104
  template=template,
100
105
  log_callback=log_callback,
106
+ associate_with_project=request.associate_environment_with_project,
101
107
  )
102
108
  deps = list(template.runtime_dependencies)
103
109
  if request.install_dev_dependencies:
@@ -214,9 +220,10 @@ class TemplateEngine:
214
220
  self._log(log_callback, "Initializing git repository")
215
221
  subprocess.run(["git", "init"], cwd=project_path, check=False, capture_output=True, text=True)
216
222
 
217
- def _create_and_configure_environment(self, request, project_path, template, log_callback):
218
- env_name = f"{request.project_name.strip().lower().replace(' ', '-')}-env"
219
- env_name = env_name.replace("_", "-")[:45]
223
+ def _create_and_configure_environment(
224
+ self, request, project_path, template, log_callback, associate_with_project=True
225
+ ):
226
+ env_name = self.environment_name_for_project(request.project_name)
220
227
  self._log(log_callback, f"Creating virtual environment: {env_name}")
221
228
 
222
229
  python_path = self._resolve_python_interpreter(request.python_version)
@@ -233,22 +240,16 @@ class TemplateEngine:
233
240
  log_callback=log_callback,
234
241
  )
235
242
 
236
- from ..runtime_toggle import save_project_metadata
237
-
238
- env_path = str(Path(env_manager.VENV_DIR) / env_name)
239
- save_project_metadata(
240
- Path(project_path),
241
- {
242
- "project_name": request.project_name,
243
- "environment_id": env_name,
244
- "environment_path": env_path,
245
- "python_version": request.python_version,
246
- "package_manager": env_manager.get_preferred_package_manager(),
247
- "runtime_enabled": True,
248
- "auto_init": True,
249
- },
250
- )
251
- self._log(log_callback, "Configured project interpreter metadata")
243
+ if associate_with_project:
244
+ from ..runtime_toggle import associate_environment_with_project
245
+
246
+ associate_environment_with_project(
247
+ Path(project_path),
248
+ env_name,
249
+ )
250
+ self._log(log_callback, "Associated environment with project")
251
+ else:
252
+ self._log(log_callback, "Created environment without associating it with the project")
252
253
  return env_name
253
254
 
254
255
  def _resolve_python_interpreter(self, requested_version: str) -> str | None:
@@ -55,6 +55,7 @@ class TemplateCreationRequest:
55
55
  author: Optional[str] = None
56
56
  license_name: Optional[str] = None
57
57
  install_dev_dependencies: bool = True
58
+ associate_environment_with_project: bool = True
58
59
 
59
60
 
60
61
  @dataclass