meshcode 2.11.25__tar.gz → 2.11.27__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.25 → meshcode-2.11.27}/PKG-INFO +1 -1
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/__init__.py +1 -1
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/comms_v4.py +14 -2
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/server.py +36 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/rpc_allowlist.py +3 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/run_agent.py +35 -16
- meshcode-2.11.27/meshcode/self_update.py +370 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/SOURCES.txt +3 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/pyproject.toml +1 -1
- meshcode-2.11.27/tests/test_run_agent_dry_run.py +128 -0
- meshcode-2.11.27/tests/test_run_agent_no_server_import.py +85 -0
- meshcode-2.11.27/tests/test_self_update_user_site.py +139 -0
- meshcode-2.11.25/meshcode/self_update.py +0 -345
- {meshcode-2.11.25 → meshcode-2.11.27}/README.md +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/atomic_push.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/cli.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/compat.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/daemon.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/date_parse.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/doctor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/invites.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/launcher.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/preferences.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode/upload.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/setup.cfg +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_color_truecolor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_core.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_date_parse.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_doctor.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_init_device_code.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_rpc_grants.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_setup_path.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.25 → meshcode-2.11.27}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -2597,13 +2597,20 @@ EXAMPLES:
|
|
|
2597
2597
|
meshcode run backend
|
|
2598
2598
|
meshcode run my-app/backend --editor cursor
|
|
2599
2599
|
""",
|
|
2600
|
-
"go": """meshcode go <agent> [--project <name>]
|
|
2600
|
+
"go": """meshcode go <agent> [--project <name>] [--dry-run]
|
|
2601
2601
|
|
|
2602
2602
|
Shortcut for setup + run. Creates workspace if needed, then launches.
|
|
2603
2603
|
|
|
2604
|
+
--dry-run Run every bootstrap step (auth, workspace, .mcp.json,
|
|
2605
|
+
editor detection, banner) but stop just before the
|
|
2606
|
+
heartbeat + editor exec. Returns 0 on a clean path.
|
|
2607
|
+
Use in CI / pre-publish smokes to catch import-time
|
|
2608
|
+
sys.exit and env-guard regressions.
|
|
2609
|
+
|
|
2604
2610
|
EXAMPLES:
|
|
2605
2611
|
meshcode go backend
|
|
2606
2612
|
meshcode go my-app/frontend
|
|
2613
|
+
meshcode go my-app/frontend --dry-run
|
|
2607
2614
|
""",
|
|
2608
2615
|
"scan": """meshcode scan
|
|
2609
2616
|
|
|
@@ -3234,6 +3241,11 @@ if __name__ == "__main__":
|
|
|
3234
3241
|
perm_override = "bypass"
|
|
3235
3242
|
elif "--safe" in sys.argv:
|
|
3236
3243
|
perm_override = "safe"
|
|
3244
|
+
# --dry-run: exercise the full bootstrap codepath but stop just
|
|
3245
|
+
# before pre-flight heartbeat + execvp(editor). Returns 0 on a
|
|
3246
|
+
# clean path. Catches import-time sys.exit / env-guard regressions
|
|
3247
|
+
# before they hit users (cf. 2.11.11 BUG-COMMANDER-MCP-BOOT).
|
|
3248
|
+
dry_run = "--dry-run" in sys.argv
|
|
3237
3249
|
# Auth pre-check: if not logged in, prompt inline
|
|
3238
3250
|
api_key = _load_api_key_for_cli()
|
|
3239
3251
|
if not api_key:
|
|
@@ -3249,7 +3261,7 @@ if __name__ == "__main__":
|
|
|
3249
3261
|
sys.exit(1)
|
|
3250
3262
|
import importlib
|
|
3251
3263
|
_run = importlib.import_module("meshcode.run_agent").run
|
|
3252
|
-
sys.exit(_run(agent, project=proj_override, editor_override=editor_override, permission_override=perm_override))
|
|
3264
|
+
sys.exit(_run(agent, project=proj_override, editor_override=editor_override, permission_override=perm_override, dry_run=dry_run))
|
|
3253
3265
|
|
|
3254
3266
|
elif cmd == "scan":
|
|
3255
3267
|
# meshcode scan — detect identicon from stdin/clipboard and launch agent
|
|
@@ -5398,6 +5398,42 @@ def meshcode_remember(
|
|
|
5398
5398
|
return result
|
|
5399
5399
|
|
|
5400
5400
|
|
|
5401
|
+
@mcp.tool()
|
|
5402
|
+
@with_working_status
|
|
5403
|
+
def meshcode_seed_fixture(
|
|
5404
|
+
table: str,
|
|
5405
|
+
overrides: Optional[Dict[str, Any]] = None,
|
|
5406
|
+
ttl_seconds: int = 3600,
|
|
5407
|
+
) -> Dict[str, Any]:
|
|
5408
|
+
"""Seed a fixture row in a whitelisted table for Playwright/verify.
|
|
5409
|
+
MESH-IMPROVE-7 (mig 345). Whitelist: mc_tasks, mc_messages, mc_agents.
|
|
5410
|
+
Auto-swept after ttl_seconds (min 60). Pair with meshcode_release_fixture.
|
|
5411
|
+
"""
|
|
5412
|
+
if table not in ("mc_tasks", "mc_messages", "mc_agents"):
|
|
5413
|
+
return {"error": "table not seedable",
|
|
5414
|
+
"hint": "whitelist: mc_tasks, mc_messages, mc_agents"}
|
|
5415
|
+
return be.sb_rpc("mc_seed_fixture", {
|
|
5416
|
+
"p_api_key": _get_api_key(),
|
|
5417
|
+
"p_project_id": _PROJECT_ID,
|
|
5418
|
+
"p_table": table,
|
|
5419
|
+
"p_overrides": overrides or {},
|
|
5420
|
+
"p_ttl_seconds": max(int(ttl_seconds), 60),
|
|
5421
|
+
})
|
|
5422
|
+
|
|
5423
|
+
|
|
5424
|
+
@mcp.tool()
|
|
5425
|
+
@with_working_status
|
|
5426
|
+
def meshcode_release_fixture(row_id: str) -> Dict[str, Any]:
|
|
5427
|
+
"""Release a fixture row immediately (don't wait for sweeper).
|
|
5428
|
+
MESH-IMPROVE-7 (mig 345)."""
|
|
5429
|
+
if not row_id or not row_id.strip():
|
|
5430
|
+
return {"error": "row_id cannot be empty"}
|
|
5431
|
+
return be.sb_rpc("mc_release_fixture", {
|
|
5432
|
+
"p_api_key": _get_api_key(),
|
|
5433
|
+
"p_row_id": row_id.strip(),
|
|
5434
|
+
})
|
|
5435
|
+
|
|
5436
|
+
|
|
5401
5437
|
@mcp.tool()
|
|
5402
5438
|
@with_working_status
|
|
5403
5439
|
def meshcode_register_skills(skills: List[str]) -> Dict[str, Any]:
|
|
@@ -678,13 +678,24 @@ def _preflight_heartbeat(agent: str, project: str) -> None:
|
|
|
678
678
|
print(f"[meshcode] Pre-flight heartbeat skipped: {e}", file=sys.stderr)
|
|
679
679
|
|
|
680
680
|
|
|
681
|
-
def run(agent: str, project: Optional[str] = None, editor_override: Optional[str] = None, permission_override: Optional[str] = None) -> int:
|
|
682
|
-
"""Launch the user's editor with ONLY the named agent's MCP server loaded.
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
681
|
+
def run(agent: str, project: Optional[str] = None, editor_override: Optional[str] = None, permission_override: Optional[str] = None, dry_run: bool = False) -> int:
|
|
682
|
+
"""Launch the user's editor with ONLY the named agent's MCP server loaded.
|
|
683
|
+
|
|
684
|
+
dry_run: when True, exercise the full bootstrap codepath (auth check,
|
|
685
|
+
workspace + .mcp.json validation, editor detection, banner generation,
|
|
686
|
+
env-var assembly, cmd construction) but stop just before mutating shared
|
|
687
|
+
state (pre-flight heartbeat) and replacing the process (execvp). Print
|
|
688
|
+
"[meshcode] DRY-RUN OK: ..." and return 0. Designed to catch bootstrap-
|
|
689
|
+
class bugs (import-time sys.exit, missing env-var guards, broken cross-
|
|
690
|
+
process imports — cf. BUG-COMMANDER-MCP-BOOT 2026-05-20) before publish.
|
|
691
|
+
"""
|
|
692
|
+
# Non-blocking self-update check (consumes prior result, may spawn bg pip).
|
|
693
|
+
# Skip in dry-run — we don't want CI / pre-publish smokes to fork a pip.
|
|
694
|
+
if not dry_run:
|
|
695
|
+
try:
|
|
696
|
+
self_update.check_and_maybe_update()
|
|
697
|
+
except Exception:
|
|
698
|
+
pass
|
|
688
699
|
|
|
689
700
|
# Detect: are we already inside a Claude Code session? os.execvp(claude)
|
|
690
701
|
# from inside an existing claude won't work — claude needs a fresh
|
|
@@ -846,15 +857,12 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
846
857
|
except Exception:
|
|
847
858
|
pass # Soft-fail — boot must proceed regardless.
|
|
848
859
|
|
|
849
|
-
#
|
|
850
|
-
#
|
|
851
|
-
#
|
|
852
|
-
#
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
_REGISTER_PROFILE_READY.wait(timeout=0.35)
|
|
856
|
-
except Exception:
|
|
857
|
-
pass
|
|
860
|
+
# NOTE: do not import meshcode_mcp.server from this module. server.py has
|
|
861
|
+
# a module-level MESHCODE_PROJECT/AGENT precondition that sys.exit(2)s when
|
|
862
|
+
# those vars are not set in the parent shell — kills `meshcode go` on
|
|
863
|
+
# fresh machines. _fetch_or_generate_art below already fetches the profile
|
|
864
|
+
# color synchronously, so the banner gets the real color without any
|
|
865
|
+
# cross-process coordination.
|
|
858
866
|
|
|
859
867
|
# ── Welcome banner with unique ASCII art + personality ──────────
|
|
860
868
|
try:
|
|
@@ -993,6 +1001,17 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
993
1001
|
except OSError:
|
|
994
1002
|
pass # No devnull (unlikely) — let the editor handle it
|
|
995
1003
|
|
|
1004
|
+
# Dry-run bail-out: every validation above has passed, the editor cmd
|
|
1005
|
+
# is fully assembled, and the next step (heartbeat + execvp) would
|
|
1006
|
+
# mutate shared state. CI / pre-publish smokes stop here.
|
|
1007
|
+
if dry_run:
|
|
1008
|
+
print(
|
|
1009
|
+
f"[meshcode] DRY-RUN OK: would exec {cmd[0]!r} for "
|
|
1010
|
+
f"{resolved_project}/{agent} from cwd {os.getcwd()!r}",
|
|
1011
|
+
file=sys.stderr,
|
|
1012
|
+
)
|
|
1013
|
+
return 0
|
|
1014
|
+
|
|
996
1015
|
# Pre-flight heartbeat: mark the agent as online BEFORE the editor
|
|
997
1016
|
# subprocess spawns. On Windows without bypass mode, Claude Code may
|
|
998
1017
|
# delay starting the MCP server until the user interacts — leaving the
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Background self-update for the meshcode CLI.
|
|
2
|
+
|
|
3
|
+
Design goals:
|
|
4
|
+
- ZERO added latency to `meshcode run`. The PyPI check + pip install
|
|
5
|
+
runs in a fully detached background subprocess. The current launch
|
|
6
|
+
is never blocked.
|
|
7
|
+
- Opt-in (first-run prompt, like permission_mode). Default ON for
|
|
8
|
+
interactive TTY users, default OFF for CI / non-TTY.
|
|
9
|
+
- Cache the PyPI check for 1 hour so we don't hit the network on
|
|
10
|
+
every launch.
|
|
11
|
+
- Two-launch model: launch N detects + downloads in the background;
|
|
12
|
+
launch N+1 reads the result file, prints "[meshcode] updated X → Y",
|
|
13
|
+
and the new code is already on disk.
|
|
14
|
+
- Safe in all the edge cases that bite Python CLI updaters:
|
|
15
|
+
* editable installs (dev mode) → skip
|
|
16
|
+
* pipx installs → use `pipx upgrade meshcode`
|
|
17
|
+
* MCP server subprocess → skip (we're inside an agent runtime)
|
|
18
|
+
* --no-update flag, MESHCODE_NO_UPDATE=1 env → skip
|
|
19
|
+
* offline / network error → silent skip
|
|
20
|
+
* concurrent runs → file lock so only one bg update at a time
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Optional, Tuple
|
|
31
|
+
|
|
32
|
+
PKG_NAME = "meshcode"
|
|
33
|
+
PYPI_URL = f"https://pypi.org/pypi/{PKG_NAME}/json"
|
|
34
|
+
CACHE_TTL_SEC = 3600 # 1 hour
|
|
35
|
+
NETWORK_TIMEOUT_SEC = 1.5
|
|
36
|
+
|
|
37
|
+
STATE_DIR = Path.home() / ".meshcode"
|
|
38
|
+
RESULT_PATH = STATE_DIR / ".update_result.json"
|
|
39
|
+
LOCK_PATH = STATE_DIR / ".update.lock"
|
|
40
|
+
LOG_PATH = STATE_DIR / "update.log"
|
|
41
|
+
LOCK_STALE_SEC = 600 # 10 min — if a lock is older, treat as crashed
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ============================================================
|
|
45
|
+
# Detection helpers — when NOT to auto-update
|
|
46
|
+
# ============================================================
|
|
47
|
+
|
|
48
|
+
def _current_version() -> str:
|
|
49
|
+
try:
|
|
50
|
+
from . import __version__
|
|
51
|
+
return __version__
|
|
52
|
+
except Exception:
|
|
53
|
+
return "0.0.0"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_editable_install() -> bool:
|
|
57
|
+
"""True if meshcode is installed via `pip install -e .` (dev mode)."""
|
|
58
|
+
try:
|
|
59
|
+
import meshcode
|
|
60
|
+
path = os.path.realpath(meshcode.__file__)
|
|
61
|
+
return "site-packages" not in path and "dist-packages" not in path
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_user_site_install() -> bool:
|
|
67
|
+
"""True if meshcode is installed in the per-user site (`pip install --user`).
|
|
68
|
+
|
|
69
|
+
Used to keep the upgrade in the same site so the user-PATH shim
|
|
70
|
+
(e.g. ~/Library/Python/3.X/bin/meshcode) stays valid. Without this,
|
|
71
|
+
`pip install -U` falls back to system-site and orphans the binary
|
|
72
|
+
the user's PATH points at.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
import site
|
|
76
|
+
import meshcode
|
|
77
|
+
path = os.path.realpath(meshcode.__file__)
|
|
78
|
+
user_site = os.path.realpath(site.getusersitepackages())
|
|
79
|
+
return path.startswith(user_site + os.sep)
|
|
80
|
+
except Exception:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_inside_mcp_serve() -> bool:
|
|
85
|
+
"""True if we're running inside the MCP subprocess of an agent client."""
|
|
86
|
+
if os.environ.get("MESHCODE_MCP_SERVE") == "1":
|
|
87
|
+
return True
|
|
88
|
+
argv0 = " ".join(sys.argv).lower()
|
|
89
|
+
return "meshcode_mcp" in argv0 or "meshcode.meshcode_mcp" in argv0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def is_pipx_install() -> bool:
|
|
93
|
+
"""True if installed via pipx (we should use `pipx upgrade` instead)."""
|
|
94
|
+
exe = os.path.realpath(sys.executable)
|
|
95
|
+
return "/pipx/" in exe or "\\pipx\\" in exe
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def update_disabled() -> bool:
|
|
99
|
+
"""User explicitly opted out for this run / globally."""
|
|
100
|
+
if os.environ.get("MESHCODE_NO_UPDATE") == "1":
|
|
101
|
+
return True
|
|
102
|
+
if "--no-update" in sys.argv:
|
|
103
|
+
return True
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _version_tuple(v: str) -> Tuple[int, ...]:
|
|
108
|
+
parts = []
|
|
109
|
+
for p in v.split("."):
|
|
110
|
+
digits = "".join(c for c in p if c.isdigit())
|
|
111
|
+
parts.append(int(digits) if digits else 0)
|
|
112
|
+
return tuple(parts)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _is_newer(remote: str, local: str) -> bool:
|
|
116
|
+
try:
|
|
117
|
+
return _version_tuple(remote) > _version_tuple(local)
|
|
118
|
+
except Exception:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ============================================================
|
|
123
|
+
# Cache + result file (consumed by next launch)
|
|
124
|
+
# ============================================================
|
|
125
|
+
|
|
126
|
+
def _read_prefs() -> dict:
|
|
127
|
+
try:
|
|
128
|
+
from .preferences import load_prefs
|
|
129
|
+
return load_prefs()
|
|
130
|
+
except Exception:
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _write_prefs_kv(**kv) -> None:
|
|
135
|
+
try:
|
|
136
|
+
from .preferences import load_prefs, save_prefs
|
|
137
|
+
prefs = load_prefs()
|
|
138
|
+
prefs.update(kv)
|
|
139
|
+
save_prefs(prefs)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _cache_fresh() -> bool:
|
|
145
|
+
last = _read_prefs().get("last_update_check_at", 0)
|
|
146
|
+
return (time.time() - last) < CACHE_TTL_SEC
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _mark_checked(latest: Optional[str]) -> None:
|
|
150
|
+
_write_prefs_kv(
|
|
151
|
+
last_update_check_at=int(time.time()),
|
|
152
|
+
last_known_latest_version=latest or _current_version(),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def consume_pending_result() -> None:
|
|
157
|
+
"""Read + delete .update_result.json, print outcome to stderr.
|
|
158
|
+
|
|
159
|
+
Called at the START of each `meshcode run` to surface what the
|
|
160
|
+
previous launch's background updater did.
|
|
161
|
+
"""
|
|
162
|
+
if not RESULT_PATH.exists():
|
|
163
|
+
return
|
|
164
|
+
try:
|
|
165
|
+
data = json.loads(RESULT_PATH.read_text(encoding="utf-8"))
|
|
166
|
+
RESULT_PATH.unlink(missing_ok=True)
|
|
167
|
+
except Exception:
|
|
168
|
+
return
|
|
169
|
+
cur = _current_version()
|
|
170
|
+
new_v = data.get("version") or "?"
|
|
171
|
+
if data.get("ok"):
|
|
172
|
+
if _is_newer(new_v, cur):
|
|
173
|
+
print(f"[meshcode] updated {cur} → {new_v}. Restart your editor to load the new version.", file=sys.stderr)
|
|
174
|
+
else:
|
|
175
|
+
# update ran but pip didn't actually upgrade (already at latest)
|
|
176
|
+
pass
|
|
177
|
+
else:
|
|
178
|
+
err = data.get("error", "unknown error")
|
|
179
|
+
print(f"[meshcode] WARN: last auto-update failed: {err}", file=sys.stderr)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ============================================================
|
|
183
|
+
# Network — PyPI version probe
|
|
184
|
+
# ============================================================
|
|
185
|
+
|
|
186
|
+
def fetch_latest_version(timeout: float = NETWORK_TIMEOUT_SEC) -> Optional[str]:
|
|
187
|
+
try:
|
|
188
|
+
import urllib.request
|
|
189
|
+
req = urllib.request.Request(PYPI_URL, headers={"User-Agent": f"meshcode/{_current_version()}"})
|
|
190
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
191
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
192
|
+
return data.get("info", {}).get("version")
|
|
193
|
+
except Exception:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ============================================================
|
|
198
|
+
# File lock — prevent concurrent bg updates
|
|
199
|
+
# ============================================================
|
|
200
|
+
|
|
201
|
+
def _acquire_lock() -> bool:
|
|
202
|
+
try:
|
|
203
|
+
if LOCK_PATH.exists():
|
|
204
|
+
age = time.time() - LOCK_PATH.stat().st_mtime
|
|
205
|
+
if age < LOCK_STALE_SEC:
|
|
206
|
+
return False
|
|
207
|
+
LOCK_PATH.unlink(missing_ok=True)
|
|
208
|
+
LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
LOCK_PATH.write_text(str(os.getpid()), encoding="utf-8")
|
|
210
|
+
return True
|
|
211
|
+
except Exception:
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ============================================================
|
|
216
|
+
# Background update spawn
|
|
217
|
+
# ============================================================
|
|
218
|
+
|
|
219
|
+
_BG_RUNNER_PY = r'''
|
|
220
|
+
import json, os, subprocess, sys, time
|
|
221
|
+
from pathlib import Path
|
|
222
|
+
|
|
223
|
+
result = {"version": None, "ok": False, "error": None, "finished_at": None}
|
|
224
|
+
state_dir = Path.home() / ".meshcode"
|
|
225
|
+
result_path = state_dir / ".update_result.json"
|
|
226
|
+
lock_path = state_dir / ".update.lock"
|
|
227
|
+
log_path = state_dir / "update.log"
|
|
228
|
+
state_dir.mkdir(parents=True, exist_ok=True)
|
|
229
|
+
|
|
230
|
+
mode = sys.argv[1] if len(sys.argv) > 1 else "pip"
|
|
231
|
+
target_version = sys.argv[2] if len(sys.argv) > 2 else None
|
|
232
|
+
site_flag = sys.argv[3] if len(sys.argv) > 3 else "system"
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
if mode == "pipx":
|
|
236
|
+
cmd = ["pipx", "upgrade", "meshcode"]
|
|
237
|
+
else:
|
|
238
|
+
cmd = [sys.executable, "-m", "pip", "install", "-U", "--disable-pip-version-check", "--quiet"]
|
|
239
|
+
# Keep upgrade in the same site as the current install so the
|
|
240
|
+
# user-PATH shim (~/Library/Python/X.Y/bin) doesn't get orphaned.
|
|
241
|
+
if site_flag == "user":
|
|
242
|
+
cmd.append("--user")
|
|
243
|
+
cmd.append("meshcode")
|
|
244
|
+
with open(log_path, "ab") as logf:
|
|
245
|
+
logf.write(f"\n=== {time.strftime('%Y-%m-%d %H:%M:%S')} bg update via {mode} ===\n".encode())
|
|
246
|
+
logf.flush()
|
|
247
|
+
proc = subprocess.run(cmd, stdout=logf, stderr=logf, timeout=180)
|
|
248
|
+
if proc.returncode == 0:
|
|
249
|
+
result["ok"] = True
|
|
250
|
+
result["version"] = target_version
|
|
251
|
+
else:
|
|
252
|
+
result["error"] = f"pip exit {proc.returncode}"
|
|
253
|
+
except subprocess.TimeoutExpired:
|
|
254
|
+
result["error"] = "pip install timed out after 180s"
|
|
255
|
+
except Exception as e:
|
|
256
|
+
result["error"] = str(e)
|
|
257
|
+
finally:
|
|
258
|
+
result["finished_at"] = int(time.time())
|
|
259
|
+
try:
|
|
260
|
+
result_path.write_text(json.dumps(result), encoding="utf-8")
|
|
261
|
+
except Exception:
|
|
262
|
+
pass
|
|
263
|
+
try:
|
|
264
|
+
lock_path.unlink()
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
'''
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _spawn_background_updater(target_version: str) -> bool:
|
|
271
|
+
"""Spawn a fully detached subprocess that runs the updater.
|
|
272
|
+
|
|
273
|
+
The parent (current `meshcode run`) returns immediately. The child
|
|
274
|
+
runs pip install in the background, writes the result to disk, and
|
|
275
|
+
exits. Next `meshcode run` consumes the result.
|
|
276
|
+
"""
|
|
277
|
+
if not _acquire_lock():
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
mode = "pipx" if is_pipx_install() else "pip"
|
|
281
|
+
site_flag = "user" if (mode == "pip" and is_user_site_install()) else "system"
|
|
282
|
+
|
|
283
|
+
# We pass the runner code via stdin so we don't need to ship a
|
|
284
|
+
# second .py file. The child reads it from sys.stdin and execs it.
|
|
285
|
+
runner = f"import sys; exec(sys.stdin.read())"
|
|
286
|
+
args = [sys.executable, "-c", runner, mode, target_version, site_flag]
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
if sys.platform == "win32":
|
|
290
|
+
DETACHED_PROCESS = 0x00000008
|
|
291
|
+
CREATE_NEW_PROCESS_GROUP = 0x00000200
|
|
292
|
+
CREATE_NO_WINDOW = 0x08000000
|
|
293
|
+
flags = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
|
294
|
+
proc = subprocess.Popen(
|
|
295
|
+
args,
|
|
296
|
+
stdin=subprocess.PIPE,
|
|
297
|
+
stdout=subprocess.DEVNULL,
|
|
298
|
+
stderr=subprocess.DEVNULL,
|
|
299
|
+
creationflags=flags,
|
|
300
|
+
close_fds=True,
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
proc = subprocess.Popen(
|
|
304
|
+
args,
|
|
305
|
+
stdin=subprocess.PIPE,
|
|
306
|
+
stdout=subprocess.DEVNULL,
|
|
307
|
+
stderr=subprocess.DEVNULL,
|
|
308
|
+
start_new_session=True,
|
|
309
|
+
close_fds=True,
|
|
310
|
+
)
|
|
311
|
+
if proc.stdin:
|
|
312
|
+
proc.stdin.write(_BG_RUNNER_PY.encode("utf-8"))
|
|
313
|
+
proc.stdin.close()
|
|
314
|
+
return True
|
|
315
|
+
except Exception:
|
|
316
|
+
try:
|
|
317
|
+
LOCK_PATH.unlink(missing_ok=True)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# ============================================================
|
|
324
|
+
# Public entrypoint — called from meshcode run
|
|
325
|
+
# ============================================================
|
|
326
|
+
|
|
327
|
+
def check_and_maybe_update(verbose: bool = False) -> None:
|
|
328
|
+
"""Non-blocking. Call at the start of `meshcode run`.
|
|
329
|
+
|
|
330
|
+
Order of checks (any failure → silent return, never raise):
|
|
331
|
+
1. consume any pending result from previous launch
|
|
332
|
+
2. opt-out gates: --no-update, MESHCODE_NO_UPDATE=1
|
|
333
|
+
3. context gates: editable install, MCP subprocess
|
|
334
|
+
4. user preference: resolve_auto_update() (may prompt first time)
|
|
335
|
+
5. cache TTL: skip if checked < 1h ago
|
|
336
|
+
6. PyPI fetch (1.5s timeout)
|
|
337
|
+
7. version compare; if newer → spawn background updater
|
|
338
|
+
"""
|
|
339
|
+
try:
|
|
340
|
+
consume_pending_result()
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
if update_disabled():
|
|
345
|
+
return
|
|
346
|
+
if is_inside_mcp_serve():
|
|
347
|
+
return
|
|
348
|
+
if is_editable_install():
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
from .preferences import resolve_auto_update
|
|
353
|
+
if not resolve_auto_update():
|
|
354
|
+
return
|
|
355
|
+
except Exception:
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
if _cache_fresh():
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
latest = fetch_latest_version()
|
|
362
|
+
_mark_checked(latest)
|
|
363
|
+
if not latest:
|
|
364
|
+
return
|
|
365
|
+
if not _is_newer(latest, _current_version()):
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
started = _spawn_background_updater(latest)
|
|
369
|
+
if started and verbose:
|
|
370
|
+
print(f"[meshcode] downloading {latest} in background...", file=sys.stderr)
|
|
@@ -217,7 +217,10 @@ tests/test_realtime_event_freshness.py
|
|
|
217
217
|
tests/test_rls_cross_tenant.py
|
|
218
218
|
tests/test_rpc_grants.py
|
|
219
219
|
tests/test_rpc_migrations.py
|
|
220
|
+
tests/test_run_agent_dry_run.py
|
|
221
|
+
tests/test_run_agent_no_server_import.py
|
|
220
222
|
tests/test_security_regressions.py
|
|
223
|
+
tests/test_self_update_user_site.py
|
|
221
224
|
tests/test_sentinel.py
|
|
222
225
|
tests/test_setup_path.py
|
|
223
226
|
tests/test_sleep_signals.py
|