railmux 0.4.0.dev23__tar.gz → 0.4.0.dev26__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.
- {railmux-0.4.0.dev23/src/railmux.egg-info → railmux-0.4.0.dev26}/PKG-INFO +1 -1
- railmux-0.4.0.dev26/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/cli.py +62 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/config_cli.py +12 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/diagnostics.py +35 -3
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_client.py +122 -43
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/provider_paths.py +23 -2
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/remote_config.py +35 -15
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_doctor.py +54 -18
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/app.py +24 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_bootstrap.py +189 -10
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_install_log.py +18 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_msys2.py +689 -15
- railmux-0.4.0.dev26/src/railmux/windows_ui_transition.py +739 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/SOURCES.txt +2 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_cli.py +20 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_config_cli.py +21 -2
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_diagnostics.py +2 -1
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fast_full_window.py +93 -22
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_remote_config.py +113 -6
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_doctor.py +57 -49
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_bootstrap.py +68 -8
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_msys2.py +416 -15
- railmux-0.4.0.dev26/tests/test_windows_ui_transition.py +326 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/wheel_smoke.py +1 -1
- railmux-0.4.0.dev23/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/LICENSE +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/README.md +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/pyproject.toml +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/setup.cfg +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/transcript.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_lease.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_transcript.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_install_log.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/release_notes.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev26"
|
|
@@ -442,9 +442,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
442
442
|
metavar="ARGS",
|
|
443
443
|
help="a quoted group of ssh arguments for --remote",
|
|
444
444
|
)
|
|
445
|
+
doctor_parser.add_argument(
|
|
446
|
+
"--remote-platform",
|
|
447
|
+
choices=("auto", "posix", "windows"),
|
|
448
|
+
default="auto",
|
|
449
|
+
help=(
|
|
450
|
+
"remote shell family for --remote: auto, posix, or windows "
|
|
451
|
+
"(default: auto)"
|
|
452
|
+
),
|
|
453
|
+
)
|
|
445
454
|
doctor_args = doctor_parser.parse_args(raw_args[1:])
|
|
446
455
|
if doctor_args.ssh_arg and not doctor_args.remote:
|
|
447
456
|
doctor_parser.error("--ssh-args requires --remote")
|
|
457
|
+
if doctor_args.remote_platform != "auto" and not doctor_args.remote:
|
|
458
|
+
doctor_parser.error("--remote-platform requires --remote")
|
|
448
459
|
if doctor_args.remote:
|
|
449
460
|
from railmux.ssh_doctor import run_remote_ssh_doctor
|
|
450
461
|
|
|
@@ -452,6 +463,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
452
463
|
doctor_args.remote,
|
|
453
464
|
ssh_args=doctor_args.ssh_arg,
|
|
454
465
|
json_output=doctor_args.json,
|
|
466
|
+
remote_platform=doctor_args.remote_platform,
|
|
455
467
|
)
|
|
456
468
|
return run_doctor(
|
|
457
469
|
claude_home=Path(doctor_args.claude_home),
|
|
@@ -630,6 +642,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
630
642
|
tmux_server.target_session_id(dedicated_target, "railmux")
|
|
631
643
|
if dedicated_target is not None else None
|
|
632
644
|
)
|
|
645
|
+
created_detached_session = False
|
|
633
646
|
if dedicated_target is not None and dedicated_session_id is None:
|
|
634
647
|
from railmux.provider_paths import (
|
|
635
648
|
running_in_managed_windows_wrapper,
|
|
@@ -644,6 +657,34 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
644
657
|
initial_size=_interactive_terminal_size(),
|
|
645
658
|
)
|
|
646
659
|
)
|
|
660
|
+
created_detached_session = dedicated_session_id is not None
|
|
661
|
+
if (
|
|
662
|
+
dedicated_target is not None
|
|
663
|
+
and dedicated_session_id is not None
|
|
664
|
+
and not created_detached_session
|
|
665
|
+
):
|
|
666
|
+
from railmux.provider_paths import running_in_managed_windows_wrapper
|
|
667
|
+
|
|
668
|
+
if running_in_managed_windows_wrapper():
|
|
669
|
+
from railmux.windows_ui_transition import ensure_current_ui
|
|
670
|
+
|
|
671
|
+
runtime_id = os.environ.get("RAILMUX_MSYS2_RUNTIME_ID", "")
|
|
672
|
+
app_id = os.environ.get("RAILMUX_MSYS2_APP_ID", "")
|
|
673
|
+
transition = ensure_current_ui(
|
|
674
|
+
dedicated_target,
|
|
675
|
+
dedicated_session_id,
|
|
676
|
+
runtime=runtime_id,
|
|
677
|
+
target_app=app_id,
|
|
678
|
+
target_version=__version__,
|
|
679
|
+
)
|
|
680
|
+
if transition.status in {"legacy", "pending", "blocked"}:
|
|
681
|
+
detail = transition.detail or "the running UI was unchanged"
|
|
682
|
+
print(
|
|
683
|
+
"info: the Windows app-layer update is pending; "
|
|
684
|
+
f"{detail}. Soft Quit the existing UI, then run "
|
|
685
|
+
"'railmux' again to finish it.",
|
|
686
|
+
file=sys.stderr,
|
|
687
|
+
)
|
|
647
688
|
cmd = tmux_server.launcher_argv(launch_prefix, raw_args)
|
|
648
689
|
return _run_tmux_client_with_watchdog(
|
|
649
690
|
cmd,
|
|
@@ -676,6 +717,27 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
676
717
|
),
|
|
677
718
|
)
|
|
678
719
|
app.run()
|
|
720
|
+
request = getattr(app, "_ui_upgrade_request", None)
|
|
721
|
+
if request is not None:
|
|
722
|
+
from railmux.windows_ui_transition import UpgradeRequest, upgrade_exec_argv
|
|
723
|
+
|
|
724
|
+
# Mocked or partially constructed App objects must not manufacture an
|
|
725
|
+
# upgrade merely by dynamically answering arbitrary attributes.
|
|
726
|
+
if not isinstance(request, UpgradeRequest):
|
|
727
|
+
return 0
|
|
728
|
+
|
|
729
|
+
upgrade_argv = upgrade_exec_argv(request, [sys.argv[0], *raw_args])
|
|
730
|
+
if upgrade_argv is None:
|
|
731
|
+
print(
|
|
732
|
+
"error: the requested Windows app layer no longer validates; "
|
|
733
|
+
"run 'railmux runtime status --verify'",
|
|
734
|
+
file=sys.stderr,
|
|
735
|
+
)
|
|
736
|
+
return 2
|
|
737
|
+
upgrade_env = os.environ.copy()
|
|
738
|
+
upgrade_env["RAILMUX_MSYS2_RUNTIME_ID"] = request.runtime
|
|
739
|
+
upgrade_env["RAILMUX_MSYS2_APP_ID"] = request.app
|
|
740
|
+
os.execve(upgrade_argv[0], upgrade_argv, upgrade_env)
|
|
679
741
|
return 0
|
|
680
742
|
|
|
681
743
|
|
|
@@ -782,6 +782,15 @@ def main(
|
|
|
782
782
|
metavar="HOST",
|
|
783
783
|
help="edit the configuration owned by an SSH destination",
|
|
784
784
|
)
|
|
785
|
+
parser.add_argument(
|
|
786
|
+
"--remote-platform",
|
|
787
|
+
choices=("auto", "posix", "windows"),
|
|
788
|
+
default="auto",
|
|
789
|
+
help=(
|
|
790
|
+
"remote shell family for --remote: auto, posix, or windows "
|
|
791
|
+
"(default: auto)"
|
|
792
|
+
),
|
|
793
|
+
)
|
|
785
794
|
parser.add_argument(
|
|
786
795
|
"--ssh-arg",
|
|
787
796
|
action=AppendSshArgument,
|
|
@@ -810,6 +819,8 @@ def main(
|
|
|
810
819
|
parser.error("--remote and --remote-context cannot be combined")
|
|
811
820
|
if args.ssh_arg and not args.remote:
|
|
812
821
|
parser.error("--ssh-args requires --remote")
|
|
822
|
+
if args.remote_platform != "auto" and not args.remote:
|
|
823
|
+
parser.error("--remote-platform requires --remote")
|
|
813
824
|
except SystemExit as exc:
|
|
814
825
|
return int(exc.code)
|
|
815
826
|
if args.remote:
|
|
@@ -819,6 +830,7 @@ def main(
|
|
|
819
830
|
args.remote,
|
|
820
831
|
ssh_args=args.ssh_arg,
|
|
821
832
|
raw_argv=tuple([] if argv is None else argv),
|
|
833
|
+
remote_platform=args.remote_platform,
|
|
822
834
|
)
|
|
823
835
|
remote_context = bool(args.remote_context)
|
|
824
836
|
stdin = sys.stdin if stdin is None else stdin
|
|
@@ -38,7 +38,7 @@ from railmux.terminal_status import (
|
|
|
38
38
|
_VERSION_RE = re.compile(
|
|
39
39
|
r"(?<![A-Za-z0-9])v?(\d+(?:\.\d+){1,3}(?:[A-Za-z]|[-+][0-9A-Za-z.-]+)?)"
|
|
40
40
|
)
|
|
41
|
-
DOCTOR_SCHEMA_VERSION =
|
|
41
|
+
DOCTOR_SCHEMA_VERSION = 4
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
@dataclass(frozen=True)
|
|
@@ -79,6 +79,15 @@ class DirectoryDiagnostic:
|
|
|
79
79
|
writable: bool
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class ManagedWindowsDiagnostic:
|
|
84
|
+
runtime_id: str | None
|
|
85
|
+
app_version: str | None
|
|
86
|
+
base_content_id: str | None
|
|
87
|
+
running_ui_version: str | None
|
|
88
|
+
transition_status: str | None
|
|
89
|
+
|
|
90
|
+
|
|
82
91
|
@dataclass(frozen=True)
|
|
83
92
|
class DoctorSnapshot:
|
|
84
93
|
"""Versioned, privacy-safe authority shared by text and JSON output."""
|
|
@@ -103,6 +112,7 @@ class DoctorSnapshot:
|
|
|
103
112
|
preferred_agent_display: str
|
|
104
113
|
data_directories: dict[str, DirectoryDiagnostic]
|
|
105
114
|
ssh_display: SshDisplayDiagnostic
|
|
115
|
+
managed_windows: ManagedWindowsDiagnostic | None = None
|
|
106
116
|
|
|
107
117
|
|
|
108
118
|
def is_ssh_session(environ: dict[str, str] | None = None) -> bool:
|
|
@@ -350,6 +360,11 @@ def collect_doctor_snapshot(
|
|
|
350
360
|
else _legacy_tmux_diagnostic()
|
|
351
361
|
)
|
|
352
362
|
)
|
|
363
|
+
managed_windows = None
|
|
364
|
+
if running_in_windows_wrapper(env):
|
|
365
|
+
from railmux.windows_ui_transition import diagnostic_status
|
|
366
|
+
|
|
367
|
+
managed_windows = ManagedWindowsDiagnostic(**diagnostic_status(env))
|
|
353
368
|
return DoctorSnapshot(
|
|
354
369
|
schema_version=DOCTOR_SCHEMA_VERSION,
|
|
355
370
|
railmux_version=__version__,
|
|
@@ -376,6 +391,7 @@ def collect_doctor_snapshot(
|
|
|
376
391
|
),
|
|
377
392
|
},
|
|
378
393
|
ssh_display=read_ssh_display_diagnostic(),
|
|
394
|
+
managed_windows=managed_windows,
|
|
379
395
|
)
|
|
380
396
|
|
|
381
397
|
|
|
@@ -495,7 +511,7 @@ def _ssh_display_text(diagnostic: SshDisplayDiagnostic) -> str:
|
|
|
495
511
|
|
|
496
512
|
def render_doctor_text(snapshot: DoctorSnapshot) -> str:
|
|
497
513
|
"""Render the stable human report from the structured authority."""
|
|
498
|
-
lines =
|
|
514
|
+
lines = [
|
|
499
515
|
"Railmux diagnostics",
|
|
500
516
|
f"Railmux: {snapshot.railmux_version}",
|
|
501
517
|
f"Python: {snapshot.python_version}",
|
|
@@ -525,6 +541,22 @@ def render_doctor_text(snapshot: DoctorSnapshot) -> str:
|
|
|
525
541
|
f"Config: {_config_text(snapshot.config)}",
|
|
526
542
|
"Settings repair: run 'railmux config' (no tmux required)",
|
|
527
543
|
f"Preferred agent display: {snapshot.preferred_agent_display}",
|
|
544
|
+
]
|
|
545
|
+
if snapshot.managed_windows is not None:
|
|
546
|
+
managed = snapshot.managed_windows
|
|
547
|
+
identity = (
|
|
548
|
+
managed.base_content_id[:12]
|
|
549
|
+
if managed.base_content_id is not None
|
|
550
|
+
else "unavailable"
|
|
551
|
+
)
|
|
552
|
+
lines.extend((
|
|
553
|
+
f"Windows managed runtime: {managed.runtime_id or 'unavailable'}; "
|
|
554
|
+
f"content={identity}",
|
|
555
|
+
f"Windows app layer: installed={managed.app_version or 'unavailable'}, "
|
|
556
|
+
f"running={managed.running_ui_version or 'not running'}, "
|
|
557
|
+
f"transition={managed.transition_status or 'none'}",
|
|
558
|
+
))
|
|
559
|
+
lines.extend((
|
|
528
560
|
(
|
|
529
561
|
"Most recent railmux ssh (host not recorded): "
|
|
530
562
|
f"{_ssh_display_text(snapshot.ssh_display)}"
|
|
@@ -535,7 +567,7 @@ def render_doctor_text(snapshot: DoctorSnapshot) -> str:
|
|
|
535
567
|
"Privacy: session IDs, transcript content, credentials, hostnames, "
|
|
536
568
|
"and raw custom paths are omitted; review before sharing."
|
|
537
569
|
),
|
|
538
|
-
)
|
|
570
|
+
))
|
|
539
571
|
return "\n".join(lines)
|
|
540
572
|
|
|
541
573
|
|
|
@@ -316,6 +316,15 @@ class RemoteStartup:
|
|
|
316
316
|
returncode: int | None = None
|
|
317
317
|
|
|
318
318
|
|
|
319
|
+
@dataclass(frozen=True)
|
|
320
|
+
class RemoteProbe:
|
|
321
|
+
"""One cooked-mode launch-family probe before any attach mutation."""
|
|
322
|
+
|
|
323
|
+
process: subprocess.Popen
|
|
324
|
+
startup: RemoteStartup
|
|
325
|
+
launch_mode: RemoteLaunchMode
|
|
326
|
+
|
|
327
|
+
|
|
319
328
|
def await_remote_attach_status(
|
|
320
329
|
process: subprocess.Popen,
|
|
321
330
|
timeout: float = _REMOTE_ATTACH_TIMEOUT,
|
|
@@ -1579,6 +1588,75 @@ def build_remote_command_argv(
|
|
|
1579
1588
|
]
|
|
1580
1589
|
|
|
1581
1590
|
|
|
1591
|
+
def probe_remote_launch(
|
|
1592
|
+
destination: str,
|
|
1593
|
+
*,
|
|
1594
|
+
remote_args: Sequence[str],
|
|
1595
|
+
ssh_args: Sequence[str],
|
|
1596
|
+
remote_platform: str = "auto",
|
|
1597
|
+
force_tty: bool = False,
|
|
1598
|
+
timeout: float = _REMOTE_HELLO_TIMEOUT,
|
|
1599
|
+
) -> RemoteProbe:
|
|
1600
|
+
"""Probe one remote Railmux command and pin its shell launch family.
|
|
1601
|
+
|
|
1602
|
+
``auto`` preserves the POSIX discovery ladder, then makes one direct
|
|
1603
|
+
shell-neutral attempt only after a non-SSH launch failure. Each shell
|
|
1604
|
+
family receives the full bounded startup timeout. Explicit Windows mode
|
|
1605
|
+
requires a managed Windows hello; auto mode accepts an authoritative POSIX
|
|
1606
|
+
hello reached through a shell-neutral direct command.
|
|
1607
|
+
"""
|
|
1608
|
+
if remote_platform not in {"auto", "posix", "windows"}:
|
|
1609
|
+
raise ValueError("invalid remote platform")
|
|
1610
|
+
launch_mode = (
|
|
1611
|
+
RemoteLaunchMode.DIRECT
|
|
1612
|
+
if remote_platform == "windows"
|
|
1613
|
+
else RemoteLaunchMode.POSIX
|
|
1614
|
+
)
|
|
1615
|
+
effective_ssh_args = (
|
|
1616
|
+
*ssh_args,
|
|
1617
|
+
"-o",
|
|
1618
|
+
"ServerAliveInterval=5",
|
|
1619
|
+
"-o",
|
|
1620
|
+
"ServerAliveCountMax=3",
|
|
1621
|
+
)
|
|
1622
|
+
|
|
1623
|
+
def start(mode: RemoteLaunchMode) -> tuple[subprocess.Popen, RemoteStartup]:
|
|
1624
|
+
argv = build_remote_command_argv(
|
|
1625
|
+
destination,
|
|
1626
|
+
remote_args=remote_args,
|
|
1627
|
+
ssh_args=effective_ssh_args,
|
|
1628
|
+
force_tty=force_tty,
|
|
1629
|
+
launch_mode=mode,
|
|
1630
|
+
)
|
|
1631
|
+
process = _spawn_remote(argv)
|
|
1632
|
+
startup = await_remote_startup(process, timeout=timeout)
|
|
1633
|
+
return process, startup
|
|
1634
|
+
|
|
1635
|
+
process, startup = start(launch_mode)
|
|
1636
|
+
if (
|
|
1637
|
+
remote_platform == "auto"
|
|
1638
|
+
and launch_mode is RemoteLaunchMode.POSIX
|
|
1639
|
+
and startup.kind is RemoteStartKind.FAILED
|
|
1640
|
+
and startup.returncode != 255
|
|
1641
|
+
):
|
|
1642
|
+
_stop_unstarted_remote(process)
|
|
1643
|
+
launch_mode = RemoteLaunchMode.DIRECT
|
|
1644
|
+
process, startup = start(launch_mode)
|
|
1645
|
+
|
|
1646
|
+
if (
|
|
1647
|
+
startup.kind is RemoteStartKind.HELLO
|
|
1648
|
+
and startup.hello is not None
|
|
1649
|
+
and remote_platform == "windows"
|
|
1650
|
+
and startup.hello.platform != "windows-msys2"
|
|
1651
|
+
):
|
|
1652
|
+
_stop_unstarted_remote(process)
|
|
1653
|
+
raise ProbeError(
|
|
1654
|
+
"the direct remote launcher answered from a non-Windows Railmux "
|
|
1655
|
+
"runtime; use --remote-platform posix for that host"
|
|
1656
|
+
)
|
|
1657
|
+
return RemoteProbe(process, startup, launch_mode)
|
|
1658
|
+
|
|
1659
|
+
|
|
1582
1660
|
def build_ssh_argv(
|
|
1583
1661
|
destination: str,
|
|
1584
1662
|
*,
|
|
@@ -1792,6 +1870,18 @@ def remote_windows_install_help(destination: str, version: str) -> str:
|
|
|
1792
1870
|
)
|
|
1793
1871
|
|
|
1794
1872
|
|
|
1873
|
+
def local_windows_update_help(version: str) -> str:
|
|
1874
|
+
"""Return the native-owner update path for a managed Windows client."""
|
|
1875
|
+
return (
|
|
1876
|
+
"Update this Windows Railmux installation from PowerShell, then "
|
|
1877
|
+
"retry:\n"
|
|
1878
|
+
f" py -m pip install --upgrade railmux=={version}\n"
|
|
1879
|
+
" railmux runtime install --yes\n"
|
|
1880
|
+
"Do not update the versioned MSYS2 app environment with its private "
|
|
1881
|
+
"pip; the native bootstrap owns application-layer installation."
|
|
1882
|
+
)
|
|
1883
|
+
|
|
1884
|
+
|
|
1795
1885
|
def _remote_launch_mode(args: argparse.Namespace) -> RemoteLaunchMode:
|
|
1796
1886
|
selected = getattr(args, "_selected_remote_launch_mode", None)
|
|
1797
1887
|
if isinstance(selected, RemoteLaunchMode):
|
|
@@ -1810,9 +1900,9 @@ def _remember_remote_launch_mode(
|
|
|
1810
1900
|
def _remote_uses_windows_runtime(
|
|
1811
1901
|
args: argparse.Namespace, hello: RemoteHello | None = None
|
|
1812
1902
|
) -> bool:
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
)
|
|
1903
|
+
if hello is not None:
|
|
1904
|
+
return hello.platform == "windows-msys2"
|
|
1905
|
+
return _remote_launch_mode(args) is RemoteLaunchMode.DIRECT
|
|
1816
1906
|
|
|
1817
1907
|
|
|
1818
1908
|
def remote_tmux_help(destination: str) -> str:
|
|
@@ -1881,8 +1971,12 @@ def _upgrade_local_and_restart(
|
|
|
1881
1971
|
*,
|
|
1882
1972
|
subcommand: str = "ssh",
|
|
1883
1973
|
) -> NoReturn:
|
|
1974
|
+
from railmux.provider_paths import running_in_windows_wrapper
|
|
1884
1975
|
from railmux.self_update import installed_version_matches
|
|
1885
1976
|
|
|
1977
|
+
if running_in_windows_wrapper():
|
|
1978
|
+
raise ProbeError(local_windows_update_help(version))
|
|
1979
|
+
|
|
1886
1980
|
argv = _local_upgrade_argv(version)
|
|
1887
1981
|
command_label = f"railmux {subcommand}"
|
|
1888
1982
|
print(
|
|
@@ -2274,57 +2368,42 @@ def prepare_remote_process(
|
|
|
2274
2368
|
if before_interaction is not None:
|
|
2275
2369
|
before_interaction()
|
|
2276
2370
|
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2371
|
+
if on_stage is not None:
|
|
2372
|
+
on_stage("Connecting to remote host…")
|
|
2373
|
+
remote_args = _remote_server_args(
|
|
2280
2374
|
session=args.session,
|
|
2281
2375
|
width=current_size.columns,
|
|
2282
2376
|
height=current_size.lines,
|
|
2283
2377
|
fps=args.fps,
|
|
2378
|
+
)
|
|
2379
|
+
probe = probe_remote_launch(
|
|
2380
|
+
args.destination,
|
|
2381
|
+
remote_args=remote_args,
|
|
2284
2382
|
ssh_args=args.ssh_arg,
|
|
2285
|
-
|
|
2383
|
+
remote_platform=getattr(args, "remote_platform", "auto"),
|
|
2286
2384
|
)
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
startup
|
|
2291
|
-
|
|
2385
|
+
process = probe.process
|
|
2386
|
+
startup = probe.startup
|
|
2387
|
+
launch_mode = probe.launch_mode
|
|
2388
|
+
if startup.kind is RemoteStartKind.HELLO:
|
|
2389
|
+
_remember_remote_launch_mode(args, launch_mode)
|
|
2390
|
+
elif (
|
|
2292
2391
|
getattr(args, "remote_platform", "auto") == "auto"
|
|
2293
|
-
and launch_mode is RemoteLaunchMode.
|
|
2294
|
-
and startup.kind is RemoteStartKind.FAILED
|
|
2295
|
-
and startup.returncode != 255
|
|
2392
|
+
and launch_mode is RemoteLaunchMode.DIRECT
|
|
2296
2393
|
):
|
|
2297
2394
|
_stop_unstarted_remote(process)
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
width=current_size.columns,
|
|
2302
|
-
height=current_size.lines,
|
|
2303
|
-
fps=args.fps,
|
|
2304
|
-
ssh_args=args.ssh_arg,
|
|
2305
|
-
launch_mode=RemoteLaunchMode.DIRECT,
|
|
2306
|
-
)
|
|
2307
|
-
process = _spawn_remote(direct_argv)
|
|
2308
|
-
startup = await_remote_startup(process)
|
|
2309
|
-
if startup.kind is RemoteStartKind.HELLO:
|
|
2310
|
-
launch_mode = RemoteLaunchMode.DIRECT
|
|
2311
|
-
_remember_remote_launch_mode(args, launch_mode)
|
|
2312
|
-
else:
|
|
2313
|
-
_stop_unstarted_remote(process)
|
|
2314
|
-
if startup.returncode == 255:
|
|
2315
|
-
raise ProbeError("ssh could not connect to the remote host")
|
|
2316
|
-
if startup.kind is RemoteStartKind.TIMEOUT:
|
|
2317
|
-
raise ProbeError(
|
|
2318
|
-
"timed out while checking the remote host's direct Railmux launcher"
|
|
2319
|
-
)
|
|
2395
|
+
if startup.returncode == 255:
|
|
2396
|
+
raise ProbeError("ssh could not connect to the remote host")
|
|
2397
|
+
if startup.kind is RemoteStartKind.TIMEOUT:
|
|
2320
2398
|
raise ProbeError(
|
|
2321
|
-
"
|
|
2322
|
-
"and a direct Railmux launch did not complete. If this is a "
|
|
2323
|
-
"Windows preview host:\n"
|
|
2324
|
-
f"{remote_windows_install_help(args.destination, __version__)}"
|
|
2399
|
+
"timed out while checking the remote host's direct Railmux launcher"
|
|
2325
2400
|
)
|
|
2326
|
-
|
|
2327
|
-
|
|
2401
|
+
raise ProbeError(
|
|
2402
|
+
"the remote shell did not accept the POSIX discovery command, "
|
|
2403
|
+
"and a direct Railmux launch did not complete. If this is a "
|
|
2404
|
+
"Windows preview host:\n"
|
|
2405
|
+
f"{remote_windows_install_help(args.destination, __version__)}"
|
|
2406
|
+
)
|
|
2328
2407
|
|
|
2329
2408
|
if (
|
|
2330
2409
|
getattr(args, "remote_platform", "auto") == "windows"
|
|
@@ -12,6 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
_DRIVE_PATH = re.compile(r"^(?:\\\\\?\\)?([A-Za-z]):[\\/](.*)\Z")
|
|
14
14
|
_MANAGED_BASE_MARKER = Path("/railmux-base.json")
|
|
15
|
+
_MANAGED_BASE_CONTENT_MARKER = Path("/railmux-base-content-v1.json")
|
|
15
16
|
_MANAGED_APP_ROOT = Path("/opt/railmux/apps")
|
|
16
17
|
_MANAGED_APP_ID = re.compile(
|
|
17
18
|
r"railmux-[0-9]+(?:\.[0-9]+)*(?:\.dev[0-9]+)?\Z"
|
|
@@ -59,14 +60,34 @@ def running_in_managed_windows_wrapper(
|
|
|
59
60
|
)
|
|
60
61
|
from railmux import __version__
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
base_valid = base_payload == {
|
|
63
64
|
"schema": 1,
|
|
64
65
|
"runtime": runtime_id,
|
|
65
|
-
}
|
|
66
|
+
}
|
|
67
|
+
legacy_app = app_payload == {
|
|
66
68
|
"schema": 1,
|
|
67
69
|
"runtime": runtime_id,
|
|
68
70
|
"railmux": __version__,
|
|
69
71
|
}
|
|
72
|
+
content_payload = read_safe_marker(_MANAGED_BASE_CONTENT_MARKER)
|
|
73
|
+
content_id = (
|
|
74
|
+
content_payload.get("content_id")
|
|
75
|
+
if isinstance(content_payload, dict)
|
|
76
|
+
and content_payload.get("schema") == 1
|
|
77
|
+
and content_payload.get("runtime") == runtime_id
|
|
78
|
+
else None
|
|
79
|
+
)
|
|
80
|
+
exact_app = bool(
|
|
81
|
+
isinstance(content_id, str)
|
|
82
|
+
and re.fullmatch(r"[0-9a-f]{64}", content_id)
|
|
83
|
+
and app_payload == {
|
|
84
|
+
"schema": 2,
|
|
85
|
+
"runtime": runtime_id,
|
|
86
|
+
"railmux": __version__,
|
|
87
|
+
"base_content_id": content_id,
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
return base_valid and (legacy_app or exact_app)
|
|
70
91
|
|
|
71
92
|
|
|
72
93
|
def private_mode_is_safe(mode: int) -> bool:
|
|
@@ -13,6 +13,7 @@ from railmux import __version__
|
|
|
13
13
|
from railmux.fast_display_client import (
|
|
14
14
|
ProbeError,
|
|
15
15
|
RemoteHello,
|
|
16
|
+
RemoteLaunchMode,
|
|
16
17
|
RemoteStartKind,
|
|
17
18
|
RemoteStartup,
|
|
18
19
|
_remote_server_args,
|
|
@@ -23,8 +24,9 @@ from railmux.fast_display_client import (
|
|
|
23
24
|
build_remote_command_argv,
|
|
24
25
|
build_remote_install_argv,
|
|
25
26
|
build_remote_private_venv_install_argv,
|
|
26
|
-
|
|
27
|
+
probe_remote_launch,
|
|
27
28
|
remote_install_help,
|
|
29
|
+
remote_windows_install_help,
|
|
28
30
|
)
|
|
29
31
|
from railmux.fast_display_protocol import REMOTE_CONFIG_PROTOCOL
|
|
30
32
|
from railmux.terminal_status import (
|
|
@@ -63,7 +65,8 @@ def _start_probe(
|
|
|
63
65
|
ssh_args: Sequence[str],
|
|
64
66
|
*,
|
|
65
67
|
install: str | None = None,
|
|
66
|
-
|
|
68
|
+
remote_platform: str = "auto",
|
|
69
|
+
) -> tuple[subprocess.Popen, RemoteStartup, RemoteLaunchMode]:
|
|
67
70
|
remote_args = _probe_args()
|
|
68
71
|
if install == "user":
|
|
69
72
|
argv = build_remote_install_argv(
|
|
@@ -80,16 +83,15 @@ def _start_probe(
|
|
|
80
83
|
ssh_args=ssh_args,
|
|
81
84
|
)
|
|
82
85
|
else:
|
|
83
|
-
|
|
86
|
+
probe = probe_remote_launch(
|
|
84
87
|
destination,
|
|
85
|
-
|
|
86
|
-
width=_PROBE_WIDTH,
|
|
87
|
-
height=_PROBE_HEIGHT,
|
|
88
|
-
fps=_PROBE_FPS,
|
|
88
|
+
remote_args=remote_args,
|
|
89
89
|
ssh_args=ssh_args,
|
|
90
|
+
remote_platform=remote_platform,
|
|
90
91
|
)
|
|
92
|
+
return probe.process, probe.startup, probe.launch_mode
|
|
91
93
|
process = _spawn_remote(argv)
|
|
92
|
-
return process, await_remote_startup(process)
|
|
94
|
+
return process, await_remote_startup(process), RemoteLaunchMode.POSIX
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def _version_order(remote_version: str) -> int | None:
|
|
@@ -172,9 +174,11 @@ def _ensure_remote_config_cli(
|
|
|
172
174
|
ssh_args: Sequence[str],
|
|
173
175
|
raw_argv: Sequence[str],
|
|
174
176
|
*,
|
|
177
|
+
remote_platform: str = "auto",
|
|
175
178
|
status: TransientStatusLine | None = None,
|
|
176
|
-
) ->
|
|
177
|
-
process, startup = _start_probe(
|
|
179
|
+
) -> RemoteLaunchMode:
|
|
180
|
+
process, startup, launch_mode = _start_probe(
|
|
181
|
+
destination, ssh_args, remote_platform=remote_platform)
|
|
178
182
|
if startup.kind is RemoteStartKind.TIMEOUT:
|
|
179
183
|
_stop_unstarted_remote(process)
|
|
180
184
|
raise ProbeError(
|
|
@@ -193,7 +197,7 @@ def _ensure_remote_config_cli(
|
|
|
193
197
|
status=status,
|
|
194
198
|
)
|
|
195
199
|
if supported:
|
|
196
|
-
return
|
|
200
|
+
return launch_mode
|
|
197
201
|
reason = (
|
|
198
202
|
f"Remote Railmux {hello.version} does not support safe remote "
|
|
199
203
|
"configuration."
|
|
@@ -201,6 +205,16 @@ def _ensure_remote_config_cli(
|
|
|
201
205
|
else:
|
|
202
206
|
reason = "Railmux is not installed or discoverable remotely."
|
|
203
207
|
|
|
208
|
+
windows_remote = (
|
|
209
|
+
hello.platform == "windows-msys2"
|
|
210
|
+
if hello is not None
|
|
211
|
+
else launch_mode is RemoteLaunchMode.DIRECT
|
|
212
|
+
)
|
|
213
|
+
if windows_remote:
|
|
214
|
+
raise ProbeError(
|
|
215
|
+
f"{reason}\n{remote_windows_install_help(destination, __version__)}"
|
|
216
|
+
)
|
|
217
|
+
|
|
204
218
|
if status is not None:
|
|
205
219
|
status.clear()
|
|
206
220
|
if not _confirm(
|
|
@@ -209,7 +223,8 @@ def _ensure_remote_config_cli(
|
|
|
209
223
|
):
|
|
210
224
|
raise ProbeError(remote_install_help(destination, __version__))
|
|
211
225
|
|
|
212
|
-
process, startup = _start_probe(
|
|
226
|
+
process, startup, _launch_mode = _start_probe(
|
|
227
|
+
destination, ssh_args, install="user", remote_platform="posix")
|
|
213
228
|
hello = _installed_probe_or_error(process, startup)
|
|
214
229
|
if hello is not None:
|
|
215
230
|
_stop_unstarted_remote(process)
|
|
@@ -219,7 +234,7 @@ def _ensure_remote_config_cli(
|
|
|
219
234
|
status=status,
|
|
220
235
|
)
|
|
221
236
|
if supported:
|
|
222
|
-
return
|
|
237
|
+
return RemoteLaunchMode.POSIX
|
|
223
238
|
raise ProbeError(
|
|
224
239
|
"automatic installation completed but did not provide a compatible "
|
|
225
240
|
"remote configuration command"
|
|
@@ -237,7 +252,8 @@ def _ensure_remote_config_cli(
|
|
|
237
252
|
f"{remote_install_help(destination, __version__)}"
|
|
238
253
|
)
|
|
239
254
|
|
|
240
|
-
process, startup = _start_probe(
|
|
255
|
+
process, startup, _launch_mode = _start_probe(
|
|
256
|
+
destination, ssh_args, install="venv", remote_platform="posix")
|
|
241
257
|
hello = _installed_probe_or_error(process, startup)
|
|
242
258
|
if hello is None:
|
|
243
259
|
raise ProbeError(
|
|
@@ -255,6 +271,7 @@ def _ensure_remote_config_cli(
|
|
|
255
271
|
"automatic private-environment installation completed but did not "
|
|
256
272
|
"provide a compatible remote configuration command"
|
|
257
273
|
)
|
|
274
|
+
return RemoteLaunchMode.POSIX
|
|
258
275
|
|
|
259
276
|
|
|
260
277
|
def run_remote_config(
|
|
@@ -262,6 +279,7 @@ def run_remote_config(
|
|
|
262
279
|
*,
|
|
263
280
|
ssh_args: Sequence[str],
|
|
264
281
|
raw_argv: Sequence[str],
|
|
282
|
+
remote_platform: str = "auto",
|
|
265
283
|
) -> int:
|
|
266
284
|
"""Negotiate safely, then run the target user's cooked config editor."""
|
|
267
285
|
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
@@ -282,10 +300,11 @@ def run_remote_config(
|
|
|
282
300
|
stream=sys.stderr,
|
|
283
301
|
)
|
|
284
302
|
)
|
|
285
|
-
_ensure_remote_config_cli(
|
|
303
|
+
launch_mode = _ensure_remote_config_cli(
|
|
286
304
|
destination,
|
|
287
305
|
ssh_args,
|
|
288
306
|
raw_argv,
|
|
307
|
+
remote_platform=remote_platform,
|
|
289
308
|
status=status,
|
|
290
309
|
)
|
|
291
310
|
status.show(
|
|
@@ -300,6 +319,7 @@ def run_remote_config(
|
|
|
300
319
|
remote_args=("config", "--remote-context"),
|
|
301
320
|
ssh_args=ssh_args,
|
|
302
321
|
force_tty=True,
|
|
322
|
+
launch_mode=launch_mode,
|
|
303
323
|
)
|
|
304
324
|
result = subprocess.run(argv, check=False)
|
|
305
325
|
except KeyboardInterrupt:
|