meshcode 2.11.10__tar.gz → 2.11.12__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.10 → meshcode-2.11.12}/PKG-INFO +1 -1
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/__init__.py +1 -1
- meshcode-2.11.12/meshcode/__main__.py +42 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/comms_v4.py +173 -52
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/server.py +25 -2
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/run_agent.py +10 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/SOURCES.txt +4 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/pyproject.toml +1 -1
- meshcode-2.11.12/tests/test_color_truecolor.py +83 -0
- meshcode-2.11.12/tests/test_init_device_code.py +68 -0
- meshcode-2.11.12/tests/test_setup_path.py +66 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/README.md +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/cli.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/compat.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/daemon.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/doctor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/invites.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/launcher.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/preferences.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/self_update.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode/upload.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/setup.cfg +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_boot_bug_regression.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_core.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_doctor.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_epistemic_v1_python_sdk.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_epistemic_v1_stop_conditions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_install_guard.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.10 → meshcode-2.11.12}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Helpful error when user invokes `python -m meshcode` because the
|
|
2
|
+
console-script isn't on PATH yet (B5 friction).
|
|
3
|
+
|
|
4
|
+
Also dispatches `python -m meshcode setup-path` to the real CLI so the
|
|
5
|
+
user can fix PATH without needing the `meshcode` binary on PATH first.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
# If the user explicitly asked to set up PATH, delegate to the real CLI.
|
|
13
|
+
if len(sys.argv) > 1 and sys.argv[1] == "setup-path":
|
|
14
|
+
from meshcode import comms_v4 as _cv # noqa: F401
|
|
15
|
+
sys.argv[0] = "meshcode"
|
|
16
|
+
# comms_v4 runs main() at module load via `if __name__ == "__main__"`.
|
|
17
|
+
# Re-exec the body by importing as __main__ would require executing
|
|
18
|
+
# the module again; simpler: invoke via runpy.
|
|
19
|
+
import runpy
|
|
20
|
+
runpy.run_module("meshcode.comms_v4", run_name="__main__", alter_sys=True)
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
# Otherwise: the user is here because `meshcode` isn't on PATH.
|
|
24
|
+
print()
|
|
25
|
+
print(" meshcode is installed but the `meshcode` command isn't on your PATH.")
|
|
26
|
+
print()
|
|
27
|
+
print(" Quick fix:")
|
|
28
|
+
print(f" {sys.executable} -m meshcode setup-path")
|
|
29
|
+
print()
|
|
30
|
+
print(" Or manually:")
|
|
31
|
+
v = sys.version_info
|
|
32
|
+
if sys.platform == "darwin":
|
|
33
|
+
print(f' export PATH="$HOME/Library/Python/{v.major}.{v.minor}/bin:$PATH"')
|
|
34
|
+
else:
|
|
35
|
+
print(' export PATH="$(python3 -m site --user-base)/bin:$PATH"')
|
|
36
|
+
print()
|
|
37
|
+
print(" Then re-run: meshcode --help")
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
@@ -2777,26 +2777,108 @@ if __name__ == "__main__":
|
|
|
2777
2777
|
sys.exit(1)
|
|
2778
2778
|
print()
|
|
2779
2779
|
|
|
2780
|
-
# B3: wrap pairing RPC so SSL / network errors abort cleanly
|
|
2781
|
-
# silent fall-through
|
|
2780
|
+
# B3: wrap any pairing RPC so SSL / network errors abort cleanly with
|
|
2781
|
+
# an actionable message (no silent fall-through, friction #7).
|
|
2782
|
+
def _pairing_rpc(fn, args):
|
|
2783
|
+
try:
|
|
2784
|
+
return sb_rpc(fn, args)
|
|
2785
|
+
except Exception as _e:
|
|
2786
|
+
_msg = str(_e).lower()
|
|
2787
|
+
if "certificate" in _msg or "ssl" in _msg:
|
|
2788
|
+
print()
|
|
2789
|
+
print(" [meshcode] SSL certificate verification failed.")
|
|
2790
|
+
print(" [meshcode] This usually means your Python install is missing root certs.")
|
|
2791
|
+
_v = sys.version_info
|
|
2792
|
+
print(f" [meshcode] Fix: run /Applications/Python\\ {_v.major}.{_v.minor}/Install\\ Certificates.command")
|
|
2793
|
+
print(f" [meshcode] (Replace {_v.major}.{_v.minor} with your actual installed version.)")
|
|
2794
|
+
print(f" [meshcode] Alternative: pip install --upgrade certifi")
|
|
2795
|
+
else:
|
|
2796
|
+
print()
|
|
2797
|
+
print(f" [meshcode] Pairing service unreachable: {_e}")
|
|
2798
|
+
print(f" [meshcode] Check your network, then re-run `meshcode init`.")
|
|
2799
|
+
sys.exit(1)
|
|
2800
|
+
|
|
2801
|
+
# B6.2 device-code OAuth (preferred): generates a code, opens browser
|
|
2802
|
+
# to /auth/device?code=…, user clicks Approve, CLI receives a scoped
|
|
2803
|
+
# token. Token is per-device, revocable from dashboard. Replaces the
|
|
2804
|
+
# legacy api-key paste path (which left long-lived secrets in
|
|
2805
|
+
# terminal history — Samuel friction).
|
|
2806
|
+
import hashlib as _hashlib
|
|
2807
|
+
import platform as _platform
|
|
2782
2808
|
try:
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2809
|
+
_hn = _platform.node() or "unknown"
|
|
2810
|
+
_device_fp = _hashlib.sha256(
|
|
2811
|
+
f"{_platform.system()}|{_hn}|{_platform.machine()}".encode()
|
|
2812
|
+
).hexdigest()[:32]
|
|
2813
|
+
except Exception:
|
|
2814
|
+
_device_fp = _hashlib.sha256(b"meshcode-anon-device").hexdigest()[:32]
|
|
2815
|
+
_device_name = f"{_platform.node() or 'device'} ({_platform.system()})"
|
|
2816
|
+
|
|
2817
|
+
b6_resp = _pairing_rpc("mc_device_code_create", {
|
|
2818
|
+
"p_device_fingerprint": _device_fp,
|
|
2819
|
+
"p_device_name": _device_name,
|
|
2820
|
+
})
|
|
2821
|
+
if isinstance(b6_resp, dict) and b6_resp.get("ok") and b6_resp.get("code"):
|
|
2822
|
+
code = b6_resp["code"]
|
|
2823
|
+
expires_in = int(b6_resp.get("expires_in_s") or 600)
|
|
2824
|
+
url = f"https://meshcode.io/auth/device?code={code}"
|
|
2825
|
+
print(f" Step 1: Confirm the code in your browser:")
|
|
2826
|
+
print()
|
|
2827
|
+
print(f" ┌─────────────────┐")
|
|
2828
|
+
print(f" │ {code:^11} │")
|
|
2829
|
+
print(f" └─────────────────┘")
|
|
2830
|
+
print()
|
|
2831
|
+
print(f" Opening {url} ...")
|
|
2832
|
+
print(f" (If browser doesn't open: visit https://meshcode.io/auth/device)")
|
|
2833
|
+
print()
|
|
2834
|
+
try:
|
|
2835
|
+
webbrowser.open(url)
|
|
2836
|
+
except Exception:
|
|
2837
|
+
pass
|
|
2838
|
+
print(" Waiting for approval", end="", flush=True)
|
|
2839
|
+
token = None
|
|
2840
|
+
deadline = _t.time() + expires_in
|
|
2841
|
+
last_status = "pending"
|
|
2842
|
+
while _t.time() < deadline:
|
|
2843
|
+
_t.sleep(2)
|
|
2844
|
+
print(".", end="", flush=True)
|
|
2845
|
+
poll = _pairing_rpc("mc_device_code_poll", {
|
|
2846
|
+
"p_code": code,
|
|
2847
|
+
"p_device_fingerprint": _device_fp,
|
|
2848
|
+
})
|
|
2849
|
+
if not (isinstance(poll, dict)):
|
|
2850
|
+
continue
|
|
2851
|
+
if poll.get("status") == "approved" and poll.get("token"):
|
|
2852
|
+
token = poll["token"]
|
|
2853
|
+
break
|
|
2854
|
+
if poll.get("status") in ("expired", "consumed", "rejected") or poll.get("error") not in (None, "rate_limited"):
|
|
2855
|
+
last_status = poll.get("status") or poll.get("error")
|
|
2856
|
+
break
|
|
2857
|
+
print()
|
|
2858
|
+
if token:
|
|
2859
|
+
login(token)
|
|
2787
2860
|
print()
|
|
2788
|
-
print("
|
|
2789
|
-
print("
|
|
2790
|
-
|
|
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:
|
|
2861
|
+
print(" ✓ Paired successfully — device added to your account.")
|
|
2862
|
+
print(" Step 2: Create your first meshwork")
|
|
2863
|
+
print(" Run: meshcode go <agent-name> --project <meshwork-name>")
|
|
2795
2864
|
print()
|
|
2796
|
-
print(
|
|
2797
|
-
print(
|
|
2865
|
+
print(" Example:")
|
|
2866
|
+
print(" meshcode go backend --project my-app")
|
|
2867
|
+
print()
|
|
2868
|
+
sys.exit(0)
|
|
2869
|
+
print()
|
|
2870
|
+
print(f" Pairing did not complete ({last_status}). Re-run: meshcode init")
|
|
2798
2871
|
sys.exit(1)
|
|
2799
|
-
|
|
2872
|
+
|
|
2873
|
+
# Legacy fallback: if mc_device_code_create returned a function-not-found
|
|
2874
|
+
# error (B6.1 mig 326 not yet applied on this Supabase project), drop
|
|
2875
|
+
# back to the older mc_create_pairing_session flow. Manual API-key paste
|
|
2876
|
+
# has been retired (security — friction #6).
|
|
2877
|
+
_legacy_b6_err = ""
|
|
2878
|
+
if isinstance(b6_resp, dict):
|
|
2879
|
+
_legacy_b6_err = str(b6_resp.get("error") or "")
|
|
2880
|
+
pairing = _pairing_rpc("mc_create_pairing_session", {})
|
|
2881
|
+
if isinstance(pairing, dict) and pairing.get("ok"):
|
|
2800
2882
|
session_id = pairing["session_id"]
|
|
2801
2883
|
code = pairing["code"]
|
|
2802
2884
|
url = pairing["url"]
|
|
@@ -2811,11 +2893,11 @@ if __name__ == "__main__":
|
|
|
2811
2893
|
pass
|
|
2812
2894
|
print(" Waiting for approval", end="", flush=True)
|
|
2813
2895
|
api_key = None
|
|
2814
|
-
for _ in range(150):
|
|
2896
|
+
for _ in range(150):
|
|
2815
2897
|
_t.sleep(2)
|
|
2816
2898
|
print(".", end="", flush=True)
|
|
2817
|
-
poll =
|
|
2818
|
-
if
|
|
2899
|
+
poll = _pairing_rpc("mc_poll_pairing_session", {"p_session_id": session_id})
|
|
2900
|
+
if isinstance(poll, dict):
|
|
2819
2901
|
if poll.get("status") == "approved" and poll.get("api_key"):
|
|
2820
2902
|
api_key = poll["api_key"]
|
|
2821
2903
|
break
|
|
@@ -2823,52 +2905,28 @@ if __name__ == "__main__":
|
|
|
2823
2905
|
break
|
|
2824
2906
|
print()
|
|
2825
2907
|
if api_key:
|
|
2826
|
-
print()
|
|
2827
2908
|
login(api_key)
|
|
2828
2909
|
print()
|
|
2829
2910
|
print(" Step 2: Create your first meshwork")
|
|
2830
2911
|
print(" Run: meshcode go <agent-name> --project <meshwork-name>")
|
|
2831
2912
|
print()
|
|
2832
|
-
print(" Example:")
|
|
2833
|
-
print(" meshcode go backend --project my-app")
|
|
2834
|
-
print()
|
|
2835
2913
|
sys.exit(0)
|
|
2836
|
-
else:
|
|
2837
|
-
print(" Pairing timed out or was not approved.")
|
|
2838
|
-
print()
|
|
2839
|
-
|
|
2840
|
-
# Fallback: manual API key paste
|
|
2841
|
-
print(" Step 1: Create your account at meshcode.io")
|
|
2842
|
-
try:
|
|
2843
|
-
webbrowser.open("https://meshcode.io")
|
|
2844
|
-
except Exception:
|
|
2845
|
-
print(" Visit: https://meshcode.io")
|
|
2846
|
-
print(" Step 2: Copy your API key from Settings")
|
|
2847
|
-
print()
|
|
2848
|
-
try:
|
|
2849
|
-
api_key = input(" Paste your API key (mc_...): ").strip()
|
|
2850
|
-
except (EOFError, KeyboardInterrupt):
|
|
2851
|
-
api_key = ""
|
|
2852
2914
|
print()
|
|
2853
|
-
|
|
2854
|
-
login(api_key)
|
|
2855
|
-
print()
|
|
2856
|
-
print(" Step 3: Create your first meshwork")
|
|
2857
|
-
print(" Run: meshcode go <agent-name> --project <meshwork-name>")
|
|
2858
|
-
print()
|
|
2859
|
-
print(" Example:")
|
|
2860
|
-
print(" meshcode go backend --project my-app")
|
|
2861
|
-
print()
|
|
2862
|
-
else:
|
|
2863
|
-
print(" [meshcode] No API key provided. Run: meshcode login <api_key>")
|
|
2915
|
+
print(f" Pairing did not complete. Re-run: meshcode init")
|
|
2864
2916
|
sys.exit(1)
|
|
2865
|
-
|
|
2917
|
+
|
|
2918
|
+
# Both paths failed.
|
|
2919
|
+
print()
|
|
2920
|
+
print(f" [meshcode] Pairing unavailable: device-code ({_legacy_b6_err or 'unknown'}); "
|
|
2921
|
+
f"legacy pairing ({pairing.get('error') if isinstance(pairing, dict) else 'unknown'}).")
|
|
2922
|
+
print(f" [meshcode] Re-run `meshcode init` later or contact support.")
|
|
2923
|
+
sys.exit(1)
|
|
2866
2924
|
|
|
2867
2925
|
# Auth guard: commands that talk to Supabase need a valid API key.
|
|
2868
2926
|
# doctor, help, version, login, prefs, launcher don't need auth.
|
|
2869
2927
|
_NO_AUTH_CMDS = {"doctor", "compat", "upgrade", "help", "--help", "-h", "login",
|
|
2870
2928
|
"init", "prefs", "launcher", "--version", "-V", "version",
|
|
2871
|
-
"whoami", "profiles", "scan"}
|
|
2929
|
+
"whoami", "profiles", "scan", "setup-path"}
|
|
2872
2930
|
if cmd not in _NO_AUTH_CMDS:
|
|
2873
2931
|
_cli_key = _load_api_key_for_cli()
|
|
2874
2932
|
if not _cli_key:
|
|
@@ -3479,6 +3537,69 @@ if __name__ == "__main__":
|
|
|
3479
3537
|
print(" meshcode prefs reset")
|
|
3480
3538
|
sys.exit(1)
|
|
3481
3539
|
|
|
3540
|
+
elif cmd == "setup-path":
|
|
3541
|
+
# B5: append Python user-bin to user's shell rc file (idempotent).
|
|
3542
|
+
# Friction #8 fix: after `pip install --user meshcode`, `meshcode`
|
|
3543
|
+
# isn't on PATH; this gives the user a one-command remediation.
|
|
3544
|
+
import platform as _pl
|
|
3545
|
+
import subprocess as _sp
|
|
3546
|
+
_v = sys.version_info
|
|
3547
|
+
if _pl.system() == "Darwin":
|
|
3548
|
+
user_bin = os.path.expanduser(f"~/Library/Python/{_v.major}.{_v.minor}/bin")
|
|
3549
|
+
else:
|
|
3550
|
+
try:
|
|
3551
|
+
user_bin = _sp.check_output(
|
|
3552
|
+
[sys.executable, "-m", "site", "--user-base"],
|
|
3553
|
+
text=True, timeout=5,
|
|
3554
|
+
).strip() + "/bin"
|
|
3555
|
+
except Exception:
|
|
3556
|
+
user_bin = os.path.expanduser("~/.local/bin")
|
|
3557
|
+
|
|
3558
|
+
shell = os.environ.get("SHELL", "")
|
|
3559
|
+
if "zsh" in shell:
|
|
3560
|
+
rc = os.path.expanduser("~/.zshrc")
|
|
3561
|
+
fish = False
|
|
3562
|
+
elif "fish" in shell:
|
|
3563
|
+
rc = os.path.expanduser("~/.config/fish/config.fish")
|
|
3564
|
+
fish = True
|
|
3565
|
+
else:
|
|
3566
|
+
rc = os.path.expanduser("~/.bashrc")
|
|
3567
|
+
fish = False
|
|
3568
|
+
|
|
3569
|
+
marker = "# meshcode: PATH for user-installed Python binaries"
|
|
3570
|
+
line = (f"set -gx PATH {user_bin} $PATH" if fish
|
|
3571
|
+
else f'export PATH="{user_bin}:$PATH"')
|
|
3572
|
+
|
|
3573
|
+
existing = ""
|
|
3574
|
+
try:
|
|
3575
|
+
if os.path.exists(rc):
|
|
3576
|
+
with open(rc) as f:
|
|
3577
|
+
existing = f.read()
|
|
3578
|
+
except Exception:
|
|
3579
|
+
pass
|
|
3580
|
+
|
|
3581
|
+
if user_bin in existing:
|
|
3582
|
+
print(f"[meshcode] {rc} already exports {user_bin} — nothing to do.")
|
|
3583
|
+
sys.exit(0)
|
|
3584
|
+
|
|
3585
|
+
try:
|
|
3586
|
+
os.makedirs(os.path.dirname(rc), exist_ok=True)
|
|
3587
|
+
with open(rc, "a") as f:
|
|
3588
|
+
f.write(f"\n{marker}\n{line}\n")
|
|
3589
|
+
print(f"[meshcode] Appended PATH export to {rc}")
|
|
3590
|
+
print(f"[meshcode] Open a new terminal OR run: source {rc}")
|
|
3591
|
+
print(f"[meshcode] Then: meshcode --help")
|
|
3592
|
+
sys.exit(0)
|
|
3593
|
+
except PermissionError:
|
|
3594
|
+
print(f"[meshcode] ERROR: cannot write {rc} (permission denied).")
|
|
3595
|
+
print(f"[meshcode] Manually add this line to your shell rc:")
|
|
3596
|
+
print(f" {line}")
|
|
3597
|
+
sys.exit(1)
|
|
3598
|
+
except Exception as _spe:
|
|
3599
|
+
print(f"[meshcode] ERROR: {_spe}")
|
|
3600
|
+
print(f"[meshcode] Manually add: {line}")
|
|
3601
|
+
sys.exit(1)
|
|
3602
|
+
|
|
3482
3603
|
elif cmd == "upgrade":
|
|
3483
3604
|
# meshcode upgrade — interactive foreground upgrade
|
|
3484
3605
|
import importlib
|
|
@@ -45,9 +45,21 @@ _ANSI_RGB = [
|
|
|
45
45
|
(255, 85, 85), (85, 255, 255), (255, 85, 255), (85, 255, 85), (85, 85, 255),
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
+
# BUG-AUDIO-COLOR fix (task e552412a, 2026-05-19): use 24-bit truecolor
|
|
49
|
+
# escapes when the terminal supports them so every terminal renders the
|
|
50
|
+
# same agent color (4-bit palette indices are remapped by terminal themes,
|
|
51
|
+
# producing the cross-machine mismatch Samuel saw).
|
|
52
|
+
_TRUECOLOR_OK = os.environ.get("COLORTERM", "").lower() in ("truecolor", "24bit")
|
|
53
|
+
|
|
48
54
|
|
|
49
55
|
def _hex_to_ansi(hex_color: str) -> str:
|
|
50
|
-
"""
|
|
56
|
+
"""Return ANSI escape for a hex color.
|
|
57
|
+
|
|
58
|
+
On truecolor-capable terminals returns 24-bit truecolor (\\e[38;2;R;G;Bm) so
|
|
59
|
+
the color bypasses palette remapping. Falls back to nearest 4-bit ANSI
|
|
60
|
+
palette index when COLORTERM is not truecolor.
|
|
61
|
+
Returns None if hex is malformed.
|
|
62
|
+
"""
|
|
51
63
|
hx = hex_color.strip().lstrip("#")
|
|
52
64
|
if len(hx) != 6:
|
|
53
65
|
return None
|
|
@@ -55,6 +67,9 @@ def _hex_to_ansi(hex_color: str) -> str:
|
|
|
55
67
|
r, g, b = int(hx[0:2], 16), int(hx[2:4], 16), int(hx[4:6], 16)
|
|
56
68
|
except ValueError:
|
|
57
69
|
return None
|
|
70
|
+
if _TRUECOLOR_OK:
|
|
71
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
72
|
+
# Fallback: map to nearest 4-bit ANSI palette index
|
|
58
73
|
best_i, best_d = 0, float("inf")
|
|
59
74
|
for i, (ar, ag, ab) in enumerate(_ANSI_RGB):
|
|
60
75
|
d = (r - ar) ** 2 + (g - ag) ** 2 + (b - ab) ** 2
|
|
@@ -65,10 +80,18 @@ def _hex_to_ansi(hex_color: str) -> str:
|
|
|
65
80
|
|
|
66
81
|
|
|
67
82
|
def _agent_color(name: str) -> str:
|
|
68
|
-
"""
|
|
83
|
+
"""Profile-canonical color if cached, else a deterministic per-name color.
|
|
84
|
+
Never random — same name -> same color on every machine."""
|
|
69
85
|
if _CACHED_AGENT_COLOR:
|
|
70
86
|
return _CACHED_AGENT_COLOR
|
|
71
87
|
h = int(_hashlib.md5(name.encode()).hexdigest()[:8], 16)
|
|
88
|
+
if _TRUECOLOR_OK:
|
|
89
|
+
# Pseudo-random but deterministic 24-bit color from the hash. Clamp
|
|
90
|
+
# mid-range so the color is readable on both dark and light terminals.
|
|
91
|
+
r = max(70, min(220, (h >> 16) & 0xFF))
|
|
92
|
+
g = max(70, min(220, (h >> 8) & 0xFF))
|
|
93
|
+
b = max(70, min(220, h & 0xFF))
|
|
94
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
72
95
|
return _ANSI_COLORS[h % len(_ANSI_COLORS)]
|
|
73
96
|
|
|
74
97
|
|
|
@@ -846,6 +846,16 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
846
846
|
except Exception:
|
|
847
847
|
pass # Soft-fail — boot must proceed regardless.
|
|
848
848
|
|
|
849
|
+
# BUG-AUDIO-COLOR fix: wait briefly for profile fetch to populate
|
|
850
|
+
# _CACHED_AGENT_COLOR before rendering the banner, so the boot art
|
|
851
|
+
# doesn't flash the deterministic-hash fallback color and then "jump"
|
|
852
|
+
# to the canonical profile color. Upper-bound 350ms.
|
|
853
|
+
try:
|
|
854
|
+
from .meshcode_mcp.server import _REGISTER_PROFILE_READY
|
|
855
|
+
_REGISTER_PROFILE_READY.wait(timeout=0.35)
|
|
856
|
+
except Exception:
|
|
857
|
+
pass
|
|
858
|
+
|
|
849
859
|
# ── Welcome banner with unique ASCII art + personality ──────────
|
|
850
860
|
try:
|
|
851
861
|
from .ascii_art import generate_art, render_welcome
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
3
|
meshcode/__init__.py
|
|
4
|
+
meshcode/__main__.py
|
|
4
5
|
meshcode/_stop_hook_template.py
|
|
5
6
|
meshcode/ascii_art.py
|
|
6
7
|
meshcode/cli.py
|
|
@@ -195,6 +196,7 @@ meshcode/scripts/check_secrets.py
|
|
|
195
196
|
meshcode/scripts/race_rate_harness.py
|
|
196
197
|
tests/test_auto_update_hardening.py
|
|
197
198
|
tests/test_boot_bug_regression.py
|
|
199
|
+
tests/test_color_truecolor.py
|
|
198
200
|
tests/test_core.py
|
|
199
201
|
tests/test_cross_agent_messaging.py
|
|
200
202
|
tests/test_doctor.py
|
|
@@ -202,6 +204,7 @@ tests/test_epistemic_v1_python_sdk.py
|
|
|
202
204
|
tests/test_epistemic_v1_stop_conditions.py
|
|
203
205
|
tests/test_esc_deaf_state.py
|
|
204
206
|
tests/test_exceptions.py
|
|
207
|
+
tests/test_init_device_code.py
|
|
205
208
|
tests/test_install_guard.py
|
|
206
209
|
tests/test_lease_sigterm_release.py
|
|
207
210
|
tests/test_mark_read_batch.py
|
|
@@ -211,6 +214,7 @@ tests/test_rls_cross_tenant.py
|
|
|
211
214
|
tests/test_rpc_migrations.py
|
|
212
215
|
tests/test_security_regressions.py
|
|
213
216
|
tests/test_sentinel.py
|
|
217
|
+
tests/test_setup_path.py
|
|
214
218
|
tests/test_sleep_signals.py
|
|
215
219
|
tests/test_status_enum_coverage.py
|
|
216
220
|
tests/test_stay_on_loop_hook.py
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Tests for BUG-AUDIO-COLOR (task e552412a) — truecolor agent colors."""
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture(autouse=True)
|
|
8
|
+
def _set_required_env(monkeypatch):
|
|
9
|
+
"""server.py requires these env vars at import."""
|
|
10
|
+
monkeypatch.setenv("MESHCODE_PROJECT", "test")
|
|
11
|
+
monkeypatch.setenv("MESHCODE_AGENT", "test")
|
|
12
|
+
monkeypatch.setenv("MESHCODE_PROJECT_ID", "00000000-0000-0000-0000-000000000000")
|
|
13
|
+
yield
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_hex_to_ansi_truecolor_format(monkeypatch):
|
|
17
|
+
from meshcode.meshcode_mcp import server as srv
|
|
18
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", True)
|
|
19
|
+
assert srv._hex_to_ansi("#E53935") == "\033[38;2;229;57;53m"
|
|
20
|
+
assert srv._hex_to_ansi("E53935") == "\033[38;2;229;57;53m"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_hex_to_ansi_4bit_fallback(monkeypatch):
|
|
24
|
+
from meshcode.meshcode_mcp import server as srv
|
|
25
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", False)
|
|
26
|
+
result = srv._hex_to_ansi("#E53935")
|
|
27
|
+
# 4-bit ANSI escape, one of the palette entries
|
|
28
|
+
assert result in srv._ANSI_COLORS
|
|
29
|
+
assert result.startswith("\033[")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_hex_to_ansi_malformed_returns_none(monkeypatch):
|
|
33
|
+
from meshcode.meshcode_mcp import server as srv
|
|
34
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", True)
|
|
35
|
+
assert srv._hex_to_ansi("not-hex") is None
|
|
36
|
+
assert srv._hex_to_ansi("#zzzzzz") is None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_agent_color_deterministic_truecolor(monkeypatch):
|
|
40
|
+
"""Same name → same color across calls in truecolor mode."""
|
|
41
|
+
from meshcode.meshcode_mcp import server as srv
|
|
42
|
+
monkeypatch.setattr(srv, "_CACHED_AGENT_COLOR", None)
|
|
43
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", True)
|
|
44
|
+
a = srv._agent_color("commander")
|
|
45
|
+
b = srv._agent_color("commander")
|
|
46
|
+
assert a == b
|
|
47
|
+
assert a.startswith("\033[38;2;") # truecolor
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_agent_color_deterministic_4bit(monkeypatch):
|
|
51
|
+
from meshcode.meshcode_mcp import server as srv
|
|
52
|
+
monkeypatch.setattr(srv, "_CACHED_AGENT_COLOR", None)
|
|
53
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", False)
|
|
54
|
+
a = srv._agent_color("commander")
|
|
55
|
+
b = srv._agent_color("commander")
|
|
56
|
+
assert a == b
|
|
57
|
+
assert a in srv._ANSI_COLORS
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_agent_color_cached_overrides(monkeypatch):
|
|
61
|
+
"""If _CACHED_AGENT_COLOR is set (from profile fetch), use that."""
|
|
62
|
+
from meshcode.meshcode_mcp import server as srv
|
|
63
|
+
monkeypatch.setattr(srv, "_CACHED_AGENT_COLOR", "\033[38;2;100;200;50m")
|
|
64
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", True)
|
|
65
|
+
assert srv._agent_color("any-name") == "\033[38;2;100;200;50m"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_agent_color_clamped_to_mid_range(monkeypatch):
|
|
69
|
+
"""Truecolor RGB components clamped to [70, 220] for readability on
|
|
70
|
+
both dark and light terminals."""
|
|
71
|
+
from meshcode.meshcode_mcp import server as srv
|
|
72
|
+
monkeypatch.setattr(srv, "_CACHED_AGENT_COLOR", None)
|
|
73
|
+
monkeypatch.setattr(srv, "_TRUECOLOR_OK", True)
|
|
74
|
+
# Try many names to exercise the clamp
|
|
75
|
+
for name in ("a", "bbbb", "cccccc", "dddddddd", "eeeeeeeeee"):
|
|
76
|
+
c = srv._agent_color(name)
|
|
77
|
+
# Parse the \033[38;2;R;G;Bm
|
|
78
|
+
body = c.lstrip("\033[").rstrip("m")
|
|
79
|
+
parts = body.split(";")
|
|
80
|
+
r, g, b = int(parts[2]), int(parts[3]), int(parts[4])
|
|
81
|
+
assert 70 <= r <= 220, f"R={r} out of range for {name}"
|
|
82
|
+
assert 70 <= g <= 220, f"G={g} out of range for {name}"
|
|
83
|
+
assert 70 <= b <= 220, f"B={b} out of range for {name}"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Tests for B6.2 — device-code OAuth flow in `meshcode init`.
|
|
2
|
+
|
|
3
|
+
The full init() flow is tightly coupled to comms_v4's main() dispatch and
|
|
4
|
+
has side effects (sys.exit, webbrowser, input, supabase). Rather than spin
|
|
5
|
+
up subprocesses with monkey-patches that don't survive runpy re-imports,
|
|
6
|
+
this module verifies the *contracts* and the small helpers — the integration
|
|
7
|
+
is exercised by the live RPC handshake in production.
|
|
8
|
+
"""
|
|
9
|
+
import hashlib
|
|
10
|
+
import platform
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_device_fingerprint_is_stable():
|
|
14
|
+
"""Same machine -> same fingerprint across calls (init can't pair if it
|
|
15
|
+
drifts mid-session)."""
|
|
16
|
+
def _fp():
|
|
17
|
+
try:
|
|
18
|
+
hn = platform.node() or "unknown"
|
|
19
|
+
return hashlib.sha256(
|
|
20
|
+
f"{platform.system()}|{hn}|{platform.machine()}".encode()
|
|
21
|
+
).hexdigest()[:32]
|
|
22
|
+
except Exception:
|
|
23
|
+
return hashlib.sha256(b"meshcode-anon-device").hexdigest()[:32]
|
|
24
|
+
|
|
25
|
+
a = _fp()
|
|
26
|
+
b = _fp()
|
|
27
|
+
assert a == b
|
|
28
|
+
assert len(a) == 32
|
|
29
|
+
# Hex string
|
|
30
|
+
assert all(c in "0123456789abcdef" for c in a)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_init_branch_has_no_apikey_paste_prompt():
|
|
34
|
+
"""Source-level invariant: comms_v4.py 'cmd == init' block must not call
|
|
35
|
+
builtins.input() for the API key (security regression sentinel)."""
|
|
36
|
+
import pathlib
|
|
37
|
+
src = pathlib.Path("meshcode/comms_v4.py").read_text()
|
|
38
|
+
init_idx = src.find('if cmd == "init":')
|
|
39
|
+
assert init_idx >= 0
|
|
40
|
+
# Find the next top-level `elif cmd ==` or `if cmd not in _NO_AUTH_CMDS`
|
|
41
|
+
end_idx = min(
|
|
42
|
+
i for i in (
|
|
43
|
+
src.find("if cmd not in _NO_AUTH_CMDS:", init_idx + 1),
|
|
44
|
+
src.find("elif cmd == ", init_idx + 1),
|
|
45
|
+
) if i > 0
|
|
46
|
+
)
|
|
47
|
+
init_block = src[init_idx:end_idx]
|
|
48
|
+
# Must not contain input("Paste your API key") or similar paste prompts
|
|
49
|
+
paste_prompts = ['Paste your API key', 'input(" Paste']
|
|
50
|
+
for p in paste_prompts:
|
|
51
|
+
assert p not in init_block, (
|
|
52
|
+
f"init block must not contain {p!r} — B6.2 security goal "
|
|
53
|
+
"(no terminal paste of long-lived secrets)"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_init_branch_calls_device_code_first():
|
|
58
|
+
"""Source-level: device-code RPC must be called BEFORE legacy pairing."""
|
|
59
|
+
import pathlib
|
|
60
|
+
src = pathlib.Path("meshcode/comms_v4.py").read_text()
|
|
61
|
+
init_idx = src.find('if cmd == "init":')
|
|
62
|
+
end_idx = src.find("if cmd not in _NO_AUTH_CMDS:", init_idx)
|
|
63
|
+
init_block = src[init_idx:end_idx]
|
|
64
|
+
dc_idx = init_block.find("mc_device_code_create")
|
|
65
|
+
legacy_idx = init_block.find("mc_create_pairing_session")
|
|
66
|
+
assert dc_idx > 0, "init must call mc_device_code_create"
|
|
67
|
+
assert legacy_idx > 0, "init must keep mc_create_pairing_session fallback"
|
|
68
|
+
assert dc_idx < legacy_idx, "device-code must be tried before legacy"
|