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.
- {meshcode-2.11.9 → meshcode-2.11.10}/PKG-INFO +1 -1
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/__init__.py +1 -1
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/comms_v4.py +51 -2
- meshcode-2.11.10/meshcode/doctor.py +277 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/run_agent.py +62 -8
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/SOURCES.txt +2 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/pyproject.toml +1 -1
- meshcode-2.11.10/tests/test_doctor.py +123 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_install_guard.py +68 -10
- {meshcode-2.11.9 → meshcode-2.11.10}/README.md +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/cli.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/compat.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/daemon.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/invites.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/launcher.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/preferences.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/self_update.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode/upload.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/setup.cfg +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_core.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.9 → meshcode-2.11.10}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -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
|
-
#
|
|
2749
|
-
|
|
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"
|
|
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
|
-
|
|
481
|
-
|
|
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
|
|
@@ -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
|
|
@@ -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
|