meshcode 2.11.9__tar.gz → 2.11.10__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 (219) hide show
  1. {meshcode-2.11.9 → meshcode-2.11.10}/PKG-INFO +1 -1
  2. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/comms_v4.py +51 -2
  4. meshcode-2.11.10/meshcode/doctor.py +277 -0
  5. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/run_agent.py +62 -8
  6. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/SOURCES.txt +2 -0
  8. {meshcode-2.11.9 → meshcode-2.11.10}/pyproject.toml +1 -1
  9. meshcode-2.11.10/tests/test_doctor.py +123 -0
  10. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_install_guard.py +68 -10
  11. {meshcode-2.11.9 → meshcode-2.11.10}/README.md +0 -0
  12. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/_stop_hook_template.py +0 -0
  13. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/ascii_art.py +0 -0
  14. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/cli.py +0 -0
  15. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/compat.py +0 -0
  16. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/daemon.py +0 -0
  17. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/error_hints.py +0 -0
  18. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/exceptions.py +0 -0
  19. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/invites.py +0 -0
  20. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/launcher.py +0 -0
  21. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/launcher_install.py +0 -0
  22. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/__init__.py +0 -0
  23. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/__main__.py +0 -0
  24. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/backend.py +0 -0
  25. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/realtime.py +0 -0
  26. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/server.py +0 -0
  27. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  28. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_backend.py +0 -0
  29. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  30. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  31. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  32. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  33. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  34. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/preferences.py +0 -0
  35. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/protocol_handler.py +0 -0
  36. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/protocol_v2.py +0 -0
  37. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/quickstart.py +0 -0
  38. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/scripts/check_secrets.py +0 -0
  39. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/scripts/race_rate_harness.py +0 -0
  40. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/secrets.py +0 -0
  41. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/self_update.py +0 -0
  42. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/setup_clients.py +0 -0
  43. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/supervisor.py +0 -0
  44. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/upload.py +0 -0
  45. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/comms_v4.py +0 -0
  46. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  47. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  48. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/cli.py +0 -0
  49. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  50. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/compat.py +0 -0
  51. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  52. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  53. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/invites.py +0 -0
  54. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  55. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  56. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  57. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  58. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  59. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  60. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  61. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  62. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  63. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  64. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  65. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  66. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  67. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  68. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  69. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  70. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  71. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  72. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/upload.py +0 -0
  73. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  74. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_core.py +0 -0
  75. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  76. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  77. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  78. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  79. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  80. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  81. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  82. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  83. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  84. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  85. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  86. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  87. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  88. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  89. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  90. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  91. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  92. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  93. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  94. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  95. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  96. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  97. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  98. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  99. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  100. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  101. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  102. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  103. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  104. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  105. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  106. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  107. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  108. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  109. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  110. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  111. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  112. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  113. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/comms_v4.py +0 -0
  114. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  115. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  116. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/cli.py +0 -0
  117. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  118. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/compat.py +0 -0
  119. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  120. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  121. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/invites.py +0 -0
  122. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  123. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  124. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  125. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  126. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  127. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  128. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  129. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  130. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  131. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  132. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  133. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  134. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  135. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  136. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  137. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  138. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  139. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  140. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/upload.py +0 -0
  141. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  142. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_core.py +0 -0
  143. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  144. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  145. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  146. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  147. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  148. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  149. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  150. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  151. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  152. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  153. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  154. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/comms_v4.py +0 -0
  155. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  156. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  157. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  158. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  159. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  160. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  161. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  162. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  163. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  164. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  165. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  166. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  167. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  168. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  169. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  170. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  171. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  172. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  173. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  174. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  175. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  176. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  177. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  178. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  179. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  180. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  181. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  182. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  183. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_core.py +0 -0
  184. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  185. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  186. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  187. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  188. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  189. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  190. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  191. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  192. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  193. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  194. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  195. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/dependency_links.txt +0 -0
  196. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/entry_points.txt +0 -0
  197. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/requires.txt +0 -0
  198. {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/top_level.txt +0 -0
  199. {meshcode-2.11.9 → meshcode-2.11.10}/setup.cfg +0 -0
  200. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_auto_update_hardening.py +0 -0
  201. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_boot_bug_regression.py +0 -0
  202. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_core.py +0 -0
  203. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_cross_agent_messaging.py +0 -0
  204. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_epistemic_v1_python_sdk.py +0 -0
  205. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  206. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_esc_deaf_state.py +0 -0
  207. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_exceptions.py +0 -0
  208. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_lease_sigterm_release.py +0 -0
  209. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_mark_read_batch.py +0 -0
  210. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_migration_integrity.py +0 -0
  211. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_realtime_event_freshness.py +0 -0
  212. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_rls_cross_tenant.py +0 -0
  213. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_rpc_migrations.py +0 -0
  214. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_security_regressions.py +0 -0
  215. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_sentinel.py +0 -0
  216. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_sleep_signals.py +0 -0
  217. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_status_enum_coverage.py +0 -0
  218. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_stay_on_loop_hook.py +0 -0
  219. {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.9
3
+ Version: 2.11.10
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.9"
2
+ __version__ = "2.11.10"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -2143,6 +2143,30 @@ def cmd_doctor(flags, pos):
2143
2143
  except Exception as e:
2144
2144
  fail("MCP server", f"import error: {e}. Run: pip install --upgrade meshcode")
2145
2145
 
2146
+ # 4d. Env checks (B2 + B9): Python stub, SSL, PATH, Claude, Node, git, prefs
2147
+ try:
2148
+ from meshcode.doctor import CHECKS as _env_checks
2149
+ # Skip ones cmd_doctor already covered (python version, api key)
2150
+ from meshcode.doctor import (
2151
+ _check_python,
2152
+ _check_api_key,
2153
+ _check_meshcode_on_path, # cmd_doctor doesn't check this
2154
+ )
2155
+ _seen = set()
2156
+ for _fn in _env_checks:
2157
+ if _fn in (_check_python, _check_api_key):
2158
+ continue
2159
+ try:
2160
+ _lvl, _title, _detail = _fn()
2161
+ if _title in _seen:
2162
+ continue
2163
+ _seen.add(_title)
2164
+ {"ok": ok, "warn": warn, "fail": fail}[_lvl](_title, _detail)
2165
+ except Exception as _e:
2166
+ warn(_fn.__name__.lstrip("_"), f"check crashed: {_e}")
2167
+ except ImportError:
2168
+ pass # doctor module not available (legacy install)
2169
+
2146
2170
  # 5. Stale agents check (requires auth)
2147
2171
  if api_key:
2148
2172
  try:
@@ -2745,8 +2769,33 @@ if __name__ == "__main__":
2745
2769
  print(" " + "=" * 40)
2746
2770
  print()
2747
2771
 
2748
- # Try device-code pairing first
2749
- pairing = sb_rpc("mc_create_pairing_session", {})
2772
+ # B2 pre-flight: refuse to onboard if env is broken
2773
+ from meshcode.doctor import run_doctor
2774
+ if run_doctor(skip_api_key_check=True) != 0:
2775
+ print()
2776
+ print(" [meshcode] Fix the blocker(s) above, then run `meshcode init` again.")
2777
+ sys.exit(1)
2778
+ print()
2779
+
2780
+ # B3: wrap pairing RPC so SSL / network errors abort cleanly (no
2781
+ # silent fall-through to API-key paste — friction #7).
2782
+ try:
2783
+ pairing = sb_rpc("mc_create_pairing_session", {})
2784
+ except Exception as _init_e:
2785
+ _msg = str(_init_e).lower()
2786
+ if "certificate" in _msg or "ssl" in _msg:
2787
+ print()
2788
+ print(" [meshcode] SSL certificate verification failed.")
2789
+ print(" [meshcode] This usually means your Python install is missing root certs.")
2790
+ _v = sys.version_info
2791
+ print(f" [meshcode] Fix: run /Applications/Python\\ {_v.major}.{_v.minor}/Install\\ Certificates.command")
2792
+ print(f" [meshcode] (Replace {_v.major}.{_v.minor} with your actual installed version.)")
2793
+ print(f" [meshcode] Alternative: pip install --upgrade certifi")
2794
+ else:
2795
+ print()
2796
+ print(f" [meshcode] Pairing service unreachable: {_init_e}")
2797
+ print(f" [meshcode] Check your network, then re-run `meshcode init`.")
2798
+ sys.exit(1)
2750
2799
  if pairing and isinstance(pairing, dict) and pairing.get("ok"):
2751
2800
  session_id = pairing["session_id"]
2752
2801
  code = pairing["code"]
@@ -0,0 +1,277 @@
1
+ """Pre-flight environment checks. `meshcode doctor` runs all and reports.
2
+
3
+ Each check returns (level, title, detail):
4
+ level: "ok" | "warn" | "fail"
5
+ title: short identifier shown to user
6
+ detail: actionable fix when not OK ("" when OK)
7
+
8
+ Exit code: 1 if any "fail", else 0 (warnings allowed).
9
+
10
+ Covers frictions #4 Python stub, #5 SSL certs, #8 PATH, #10 git/CLT modal,
11
+ #11 Claude not installed. Used as pre-flight by `meshcode init`.
12
+ """
13
+ import os
14
+ import sys
15
+ import json
16
+ import shutil
17
+ import subprocess
18
+ import urllib.request
19
+ import urllib.error
20
+ from typing import List, Tuple
21
+
22
+ Status = Tuple[str, str, str]
23
+
24
+ _HEALTH_URL = "https://meshcode.io/health"
25
+ _SUPABASE_URL = "https://supabase.co"
26
+
27
+
28
+ def _check_python() -> Status:
29
+ v = sys.version_info
30
+ if (v.major, v.minor) < (3, 9):
31
+ return (
32
+ "fail",
33
+ f"Python {v.major}.{v.minor}.{v.micro}",
34
+ "need Python >=3.9; install from python.org or `brew install python@3.13`",
35
+ )
36
+ # macOS Apple-stub detection: importing ssl on the stub fails
37
+ if sys.platform == "darwin":
38
+ try:
39
+ import ssl
40
+
41
+ _ = ssl.OPENSSL_VERSION
42
+ except Exception as e:
43
+ return (
44
+ "fail",
45
+ f"Python {v.major}.{v.minor}.{v.micro}",
46
+ f"Apple stub Python detected (ssl import failed: {e}). "
47
+ f"Install real Python: https://www.python.org/downloads/",
48
+ )
49
+ return ("ok", f"Python {v.major}.{v.minor}.{v.micro}", "")
50
+
51
+
52
+ def _check_ssl() -> Status:
53
+ try:
54
+ urllib.request.urlopen(_HEALTH_URL, timeout=5).read(64)
55
+ return ("ok", "SSL certs", "")
56
+ except urllib.error.URLError as e:
57
+ msg = str(e).lower()
58
+ if "certificate" in msg or "ssl" in msg:
59
+ fix = (
60
+ "run python's Install Certificates.command "
61
+ "(/Applications/Python <ver>/Install Certificates.command) "
62
+ "OR `pip install --upgrade certifi`"
63
+ )
64
+ return ("fail", "SSL certs", fix)
65
+ return ("warn", "SSL certs", f"network unreachable (skip if offline): {e}")
66
+ except Exception as e:
67
+ return ("warn", "SSL certs", f"check inconclusive: {e}")
68
+
69
+
70
+ def _check_meshcode_on_path() -> Status:
71
+ for tool in ("meshcode", "meshcode-mcp"):
72
+ if not shutil.which(tool):
73
+ v = sys.version_info
74
+ if sys.platform == "darwin":
75
+ hint = (
76
+ f'export PATH="$HOME/Library/Python/{v.major}.{v.minor}/bin:$PATH" '
77
+ f'(or run: python3 -m meshcode setup-path)'
78
+ )
79
+ else:
80
+ hint = (
81
+ "add Python user-base bin to PATH "
82
+ "(python3 -m site --user-base)/bin "
83
+ "or run: python3 -m meshcode setup-path"
84
+ )
85
+ return ("fail", f"`{tool}` on PATH", hint)
86
+ return ("ok", "meshcode CLI on PATH", "")
87
+
88
+
89
+ def _check_claude() -> Status:
90
+ if shutil.which("claude"):
91
+ return ("ok", "Claude Code installed", "")
92
+ return (
93
+ "fail",
94
+ "Claude Code installed",
95
+ "install from https://claude.ai/install "
96
+ "or `npm i -g @anthropic-ai/claude-code`",
97
+ )
98
+
99
+
100
+ def _check_node() -> Status:
101
+ needs_node = False
102
+ try:
103
+ from meshcode.preferences import load_prefs
104
+
105
+ cv = (load_prefs().get("claude_version") or "").lower()
106
+ if cv and cv not in ("latest", "none", "skip"):
107
+ needs_node = True
108
+ except Exception:
109
+ pass
110
+ if os.environ.get("MESHCODE_USE_NPX"):
111
+ needs_node = True
112
+ has_node = bool(shutil.which("node"))
113
+ has_npx = bool(shutil.which("npx"))
114
+ if has_node and has_npx:
115
+ return ("ok", "Node/npx", "")
116
+ level = "fail" if needs_node else "warn"
117
+ detail = (
118
+ "install Node: https://nodejs.org or `brew install node`"
119
+ if needs_node
120
+ else "only needed for `claude_version` pin or MESHCODE_USE_NPX; safe to skip"
121
+ )
122
+ return (level, "Node/npx", detail)
123
+
124
+
125
+ def _check_git() -> Status:
126
+ """git pre-flight with Apple stub detection + safe workarounds (B9)."""
127
+ git = shutil.which("git")
128
+ if not git:
129
+ return (
130
+ "fail",
131
+ "git",
132
+ "git not installed. Recommended (skip Apple's brittle modal):\n"
133
+ " - Manual CLT download: https://developer.apple.com/download/all/?q=command%20line%20tools\n"
134
+ " - OR Git for Mac: https://git-scm.com/download/mac\n"
135
+ " - OR `brew install git` (if Homebrew is installed)",
136
+ )
137
+ if sys.platform == "darwin":
138
+ try:
139
+ r = subprocess.run(
140
+ [git, "--version"],
141
+ capture_output=True,
142
+ text=True,
143
+ timeout=3,
144
+ )
145
+ stderr_lower = (r.stderr or "").lower()
146
+ if (
147
+ r.returncode != 0
148
+ or "command line developer tools" in stderr_lower
149
+ ):
150
+ return (
151
+ "fail",
152
+ "git",
153
+ "Apple stub git detected (would trigger CLT install modal). "
154
+ "Install real git: "
155
+ "https://developer.apple.com/download/all/?q=command%20line%20tools "
156
+ "OR `brew install git` OR https://git-scm.com/download/mac",
157
+ )
158
+ except subprocess.TimeoutExpired:
159
+ return (
160
+ "fail",
161
+ "git",
162
+ "git subprocess hung — likely the CLT install modal is "
163
+ "blocked or pending. Cancel any open Apple dialog; install "
164
+ "git manually: https://git-scm.com/download/mac",
165
+ )
166
+ except Exception:
167
+ pass
168
+ return ("ok", "git", "")
169
+
170
+
171
+ def _check_network() -> Status:
172
+ for url in (_HEALTH_URL, _SUPABASE_URL):
173
+ try:
174
+ urllib.request.urlopen(url, timeout=5).read(64)
175
+ except Exception as e:
176
+ return (
177
+ "warn",
178
+ "Network reachability",
179
+ f"{url} unreachable ({type(e).__name__}); "
180
+ f"check VPN/firewall if persistent",
181
+ )
182
+ return ("ok", "Network reachability", "")
183
+
184
+
185
+ def _check_api_key() -> Status:
186
+ try:
187
+ from meshcode.secrets import get_api_key, list_profiles
188
+
189
+ profiles = list_profiles() or []
190
+ if not profiles:
191
+ return (
192
+ "warn",
193
+ "API key in keychain",
194
+ "no profiles — run `meshcode init` to pair and store one",
195
+ )
196
+ for p in profiles:
197
+ if get_api_key(profile=p):
198
+ return ("ok", f"API key (profile={p})", "")
199
+ return (
200
+ "warn",
201
+ "API key in keychain",
202
+ f"profiles exist ({profiles}) but no key retrievable — "
203
+ f"`meshcode init` to re-pair",
204
+ )
205
+ except Exception as e:
206
+ return ("warn", "API key in keychain", f"secrets module unavailable: {e}")
207
+
208
+
209
+ def _check_prefs_json() -> Status:
210
+ pref = os.path.expanduser("~/.meshcode/preferences.json")
211
+ if not os.path.exists(pref):
212
+ return ("ok", "preferences.json", "(not created yet — default fine)")
213
+ try:
214
+ with open(pref) as f:
215
+ json.load(f)
216
+ return ("ok", "preferences.json", "")
217
+ except json.JSONDecodeError as e:
218
+ return (
219
+ "fail",
220
+ "preferences.json",
221
+ f"corrupt JSON ({e}); delete or fix: {pref}",
222
+ )
223
+ except Exception as e:
224
+ return ("warn", "preferences.json", f"unreadable: {e}")
225
+
226
+
227
+ CHECKS = [
228
+ _check_python,
229
+ _check_ssl,
230
+ _check_meshcode_on_path,
231
+ _check_claude,
232
+ _check_node,
233
+ _check_git,
234
+ _check_network,
235
+ _check_api_key,
236
+ _check_prefs_json,
237
+ ]
238
+
239
+
240
+ def run_doctor(skip_api_key_check: bool = False, quiet: bool = False) -> int:
241
+ """Run all checks, print results, return exit code (0 ok/warn, 1 fail)."""
242
+ results: List[Status] = []
243
+ for fn in CHECKS:
244
+ if skip_api_key_check and fn is _check_api_key:
245
+ continue
246
+ try:
247
+ results.append(fn())
248
+ except Exception as e:
249
+ results.append(("fail", fn.__name__.lstrip("_"), f"check crashed: {e}"))
250
+ blockers = 0
251
+ if not quiet:
252
+ print()
253
+ print(" meshcode doctor — environment check")
254
+ print(" " + "=" * 40)
255
+ for level, title, detail in results:
256
+ sym = {"ok": "✓", "warn": "⚠", "fail": "✗"}[level]
257
+ if level == "fail":
258
+ blockers += 1
259
+ line = f" {sym} {title}"
260
+ if detail:
261
+ line += f" — {detail}"
262
+ if not quiet:
263
+ print(line)
264
+ if not quiet:
265
+ print()
266
+ if blockers:
267
+ print(
268
+ f" {blockers} blocker(s) found — fix the above "
269
+ f"before `meshcode init`/`go`."
270
+ )
271
+ else:
272
+ print(" All checks passed (warnings non-blocking).")
273
+ return 1 if blockers else 0
274
+
275
+
276
+ if __name__ == "__main__":
277
+ sys.exit(run_doctor())
@@ -461,6 +461,19 @@ def _resolve_pinned_claude(editor_cmd: str) -> str:
461
461
  """
462
462
  from .preferences import load_prefs
463
463
 
464
+ # B4: if user opts into legacy npx path AND no version is specified,
465
+ # fall back to the historical ESC-safe pin (2.1.104). Otherwise default
466
+ # to using installed claude — no implicit Node dependency.
467
+ if (
468
+ os.environ.get("MESHCODE_USE_NPX")
469
+ and not os.environ.get("MESHCODE_CLAUDE_VERSION")
470
+ ):
471
+ os.environ["MESHCODE_CLAUDE_VERSION"] = "2.1.104"
472
+ print(
473
+ f"[meshcode] --use-npx requested; pinning to legacy ESC-safe 2.1.104",
474
+ file=sys.stderr,
475
+ )
476
+
464
477
  pinned = os.environ.get("MESHCODE_CLAUDE_VERSION", "").strip()
465
478
  if not pinned:
466
479
  # Check global admin config from DB
@@ -471,19 +484,24 @@ def _resolve_pinned_claude(editor_cmd: str) -> str:
471
484
  if not pinned:
472
485
  pinned = load_prefs().get("claude_version", "")
473
486
 
474
- # "latest" or "none" means skip pinning use whatever is installed
487
+ # "latest"/"none"/"skip" explicit opt-out of any pin
475
488
  if pinned and pinned.lower() in ("latest", "none", "skip"):
476
489
  print(f"[meshcode] Version pin disabled ({pinned}) — using installed version", file=sys.stderr)
477
490
  return editor_cmd
478
491
 
492
+ # B4: default = use installed claude. No implicit Node/npx tax. Users who
493
+ # want a specific version pin must opt in via:
494
+ # - `meshcode prefs claude-version <ver>` (persistent)
495
+ # - MESHCODE_CLAUDE_VERSION env var (one-shot)
496
+ # - --use-npx flag on `meshcode go` (one-shot, sets env)
497
+ #
498
+ # The 2.1.104 ESC-safe pin used to be the default; it added a hard Node
499
+ # dependency for users who didn't expect one. The ESC-cancel bug it works
500
+ # around should be addressed in meshcode_mcp/server.py directly; until
501
+ # then, --use-npx remains the escape hatch.
479
502
  if not pinned:
480
- # Default pin: v2.1.104 is the last Claude Code version where ESC
481
- # doesn't kill the MCP server. Newer versions send CancelledError
482
- # that cascades through the event loop. This default is overridden
483
- # by any of the 3 pin sources above (set to "latest" to disable).
484
- # Remove this default once Anthropic fixes the ESC bug upstream.
485
- pinned = "2.1.104"
486
- print(f"[meshcode] Default ESC-safe pin: Claude Code v{pinned}", file=sys.stderr)
503
+ print(f"[meshcode] Using installed Claude Code (no version pin).", file=sys.stderr)
504
+ return editor_cmd
487
505
 
488
506
  current = _get_claude_version(editor_cmd)
489
507
  if current == pinned:
@@ -792,6 +810,42 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
792
810
  print("[meshcode] Then verify: which claude", file=sys.stderr)
793
811
  return 2
794
812
 
813
+ # B9 pre-flight: warn early if git is missing or is the Apple stub.
814
+ # We don't block boot — many agents work without git — but surface the
815
+ # friction so the user isn't surprised by an Apple modal when the
816
+ # Claude Code shell tool first runs `git status`.
817
+ if sys.platform == "darwin":
818
+ try:
819
+ import shutil as _shutil
820
+ _git = _shutil.which("git")
821
+ if not _git:
822
+ print(
823
+ "[meshcode] WARNING: git not found on PATH. Claude Code may "
824
+ "trigger the macOS 'install developer tools' modal when it "
825
+ "first runs git. Install manually: "
826
+ "https://git-scm.com/download/mac or `brew install git`",
827
+ file=sys.stderr,
828
+ )
829
+ else:
830
+ _r = subprocess.run(
831
+ [_git, "--version"],
832
+ capture_output=True,
833
+ text=True,
834
+ timeout=3,
835
+ )
836
+ if (
837
+ _r.returncode != 0
838
+ or "command line developer tools" in (_r.stderr or "").lower()
839
+ ):
840
+ print(
841
+ "[meshcode] WARNING: Apple stub git detected. "
842
+ "Real git: https://git-scm.com/download/mac or "
843
+ "`brew install git`",
844
+ file=sys.stderr,
845
+ )
846
+ except Exception:
847
+ pass # Soft-fail — boot must proceed regardless.
848
+
795
849
  # ── Welcome banner with unique ASCII art + personality ──────────
796
850
  try:
797
851
  from .ascii_art import generate_art, render_welcome
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.9
3
+ Version: 2.11.10
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -7,6 +7,7 @@ meshcode/cli.py
7
7
  meshcode/comms_v4.py
8
8
  meshcode/compat.py
9
9
  meshcode/daemon.py
10
+ meshcode/doctor.py
10
11
  meshcode/error_hints.py
11
12
  meshcode/exceptions.py
12
13
  meshcode/invites.py
@@ -196,6 +197,7 @@ tests/test_auto_update_hardening.py
196
197
  tests/test_boot_bug_regression.py
197
198
  tests/test_core.py
198
199
  tests/test_cross_agent_messaging.py
200
+ tests/test_doctor.py
199
201
  tests/test_epistemic_v1_python_sdk.py
200
202
  tests/test_epistemic_v1_stop_conditions.py
201
203
  tests/test_esc_deaf_state.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.9"
7
+ version = "2.11.10"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,123 @@
1
+ """Tests for meshcode/doctor.py — env pre-flight checks."""
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from unittest import mock
6
+
7
+ import pytest
8
+
9
+ from meshcode import doctor
10
+
11
+
12
+ def test_python_check_modern_returns_ok_or_fail():
13
+ level, title, _ = doctor._check_python()
14
+ assert level in ("ok", "fail")
15
+ if level == "ok":
16
+ assert "Python" in title
17
+
18
+
19
+ def test_run_doctor_returns_0_when_all_ok(monkeypatch):
20
+ monkeypatch.setattr(doctor, "CHECKS", [lambda: ("ok", "fake", "")])
21
+ assert doctor.run_doctor(quiet=True) == 0
22
+
23
+
24
+ def test_run_doctor_returns_1_on_failure(monkeypatch):
25
+ monkeypatch.setattr(doctor, "CHECKS", [lambda: ("fail", "fake", "fix me")])
26
+ assert doctor.run_doctor(quiet=True) == 1
27
+
28
+
29
+ def test_run_doctor_warnings_dont_block(monkeypatch):
30
+ monkeypatch.setattr(
31
+ doctor,
32
+ "CHECKS",
33
+ [
34
+ lambda: ("ok", "a", ""),
35
+ lambda: ("warn", "b", "nbd"),
36
+ ],
37
+ )
38
+ assert doctor.run_doctor(quiet=True) == 0
39
+
40
+
41
+ def test_run_doctor_skips_api_key_when_flagged(monkeypatch):
42
+ called = []
43
+
44
+ def _fake_api():
45
+ called.append(True)
46
+ return ("fail", "api", "x")
47
+
48
+ monkeypatch.setattr(doctor, "_check_api_key", _fake_api)
49
+ monkeypatch.setattr(doctor, "CHECKS", [_fake_api])
50
+ rc = doctor.run_doctor(skip_api_key_check=True, quiet=True)
51
+ assert rc == 0 and not called
52
+
53
+
54
+ def test_check_crash_treated_as_fail(monkeypatch):
55
+ def _boom():
56
+ raise RuntimeError("kaboom")
57
+
58
+ monkeypatch.setattr(doctor, "CHECKS", [_boom])
59
+ assert doctor.run_doctor(quiet=True) == 1
60
+
61
+
62
+ def test_prefs_json_corrupt(tmp_path, monkeypatch):
63
+ pref = tmp_path / "prefs.json"
64
+ pref.write_text("{invalid json")
65
+ orig_expand = os.path.expanduser
66
+ monkeypatch.setattr(
67
+ os.path,
68
+ "expanduser",
69
+ lambda p: str(pref) if "preferences.json" in p else orig_expand(p),
70
+ )
71
+ level, _, detail = doctor._check_prefs_json()
72
+ assert level == "fail" and "corrupt" in detail.lower()
73
+
74
+
75
+ def test_git_check_apple_stub_returns_fail(monkeypatch):
76
+ """Apple stub git -- subprocess returns CLT-prompt-style stderr -> fail."""
77
+ fake = mock.MagicMock(
78
+ returncode=1,
79
+ stderr="git: command line developer tools missing",
80
+ stdout="",
81
+ )
82
+ monkeypatch.setattr(
83
+ "shutil.which",
84
+ lambda x: "/usr/bin/git" if x == "git" else None,
85
+ )
86
+ monkeypatch.setattr(subprocess, "run", lambda *a, **kw: fake)
87
+ monkeypatch.setattr(sys, "platform", "darwin")
88
+ level, _, detail = doctor._check_git()
89
+ assert level == "fail" and "Apple stub" in detail
90
+
91
+
92
+ def test_git_check_real_git_passes(monkeypatch):
93
+ fake = mock.MagicMock(returncode=0, stderr="", stdout="git version 2.43.0")
94
+ monkeypatch.setattr(
95
+ "shutil.which",
96
+ lambda x: "/opt/homebrew/bin/git" if x == "git" else None,
97
+ )
98
+ monkeypatch.setattr(subprocess, "run", lambda *a, **kw: fake)
99
+ monkeypatch.setattr(sys, "platform", "darwin")
100
+ level, _, _ = doctor._check_git()
101
+ assert level == "ok"
102
+
103
+
104
+ def test_git_check_timeout_returns_fail(monkeypatch):
105
+ monkeypatch.setattr(
106
+ "shutil.which",
107
+ lambda x: "/usr/bin/git" if x == "git" else None,
108
+ )
109
+
110
+ def _hang(*a, **kw):
111
+ raise subprocess.TimeoutExpired(cmd="git", timeout=3)
112
+
113
+ monkeypatch.setattr(subprocess, "run", _hang)
114
+ monkeypatch.setattr(sys, "platform", "darwin")
115
+ level, _, detail = doctor._check_git()
116
+ assert level == "fail" and "hung" in detail.lower()
117
+
118
+
119
+ def test_git_check_missing_returns_fail(monkeypatch):
120
+ monkeypatch.setattr("shutil.which", lambda x: None)
121
+ level, _, detail = doctor._check_git()
122
+ assert level == "fail"
123
+ assert "git not installed" in detail