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.
Files changed (180) hide show
  1. {railmux-0.4.0.dev23/src/railmux.egg-info → railmux-0.4.0.dev26}/PKG-INFO +1 -1
  2. railmux-0.4.0.dev26/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/cli.py +62 -0
  4. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/config_cli.py +12 -0
  5. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/diagnostics.py +35 -3
  6. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_client.py +122 -43
  7. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/provider_paths.py +23 -2
  8. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/remote_config.py +35 -15
  9. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_doctor.py +54 -18
  10. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/app.py +24 -0
  11. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_bootstrap.py +189 -10
  12. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_install_log.py +18 -0
  13. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_msys2.py +689 -15
  14. railmux-0.4.0.dev26/src/railmux/windows_ui_transition.py +739 -0
  15. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26/src/railmux.egg-info}/PKG-INFO +1 -1
  16. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/SOURCES.txt +2 -0
  17. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_cli.py +20 -0
  18. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_config_cli.py +21 -2
  19. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_diagnostics.py +2 -1
  20. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fast_full_window.py +93 -22
  21. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_remote_config.py +113 -6
  22. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_doctor.py +57 -49
  23. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_bootstrap.py +68 -8
  24. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_msys2.py +416 -15
  25. railmux-0.4.0.dev26/tests/test_windows_ui_transition.py +326 -0
  26. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/wheel_smoke.py +1 -1
  27. railmux-0.4.0.dev23/src/railmux/__init__.py +0 -1
  28. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/LICENSE +0 -0
  29. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/MANIFEST.in +0 -0
  30. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/README.md +0 -0
  31. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/pyproject.toml +0 -0
  32. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/setup.cfg +0 -0
  33. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/__main__.py +0 -0
  34. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/atomic_file.py +0 -0
  35. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/background_index.py +0 -0
  36. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/codex_index.py +0 -0
  37. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/config.py +0 -0
  38. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/discovery.py +0 -0
  39. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/display_transport.py +0 -0
  40. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/entrypoint.py +0 -0
  41. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_history.py +0 -0
  42. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_input.py +0 -0
  43. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_protocol.py +0 -0
  44. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fast_display_server.py +0 -0
  45. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/favorites.py +0 -0
  46. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/fuzzy.py +0 -0
  47. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/help_workspace.py +0 -0
  48. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/launcher.py +0 -0
  49. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/legacy_sessions.py +0 -0
  50. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/local_clipboard.py +0 -0
  51. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/local_open.py +0 -0
  52. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/models.py +0 -0
  53. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/modes.py +0 -0
  54. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/mouse_manager.py +0 -0
  55. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/orphan_marker.py +0 -0
  56. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/pane_surface.py +0 -0
  57. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/path_codec.py +0 -0
  58. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/renames.py +0 -0
  59. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/restart_state.py +0 -0
  60. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/runtime_config.py +0 -0
  61. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/scroll_agent.py +0 -0
  62. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/scroll_manager.py +0 -0
  63. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/selection_isolation.py +0 -0
  64. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/self_update.py +0 -0
  65. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_cache.py +0 -0
  66. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_index.py +0 -0
  67. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/session_lease.py +0 -0
  68. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/setting_contracts.py +0 -0
  69. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/settings.py +0 -0
  70. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_args.py +0 -0
  71. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_compat.py +0 -0
  72. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ssh_display_diagnostics.py +0 -0
  73. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/system_deps.py +0 -0
  74. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/terminal_status.py +0 -0
  75. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_binding_manager.py +0 -0
  76. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_ctl.py +0 -0
  77. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_health.py +0 -0
  78. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tmux_server.py +0 -0
  79. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/tool_panes.py +0 -0
  80. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/transcript.py +0 -0
  81. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/__init__.py +0 -0
  82. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/_widgets.py +0 -0
  83. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/keymap.py +0 -0
  84. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/modals.py +0 -0
  85. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/projects_pane.py +0 -0
  86. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/running_pane.py +0 -0
  87. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/sessions_pane.py +0 -0
  88. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/sidebar.py +0 -0
  89. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/statusbar.py +0 -0
  90. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/ui/workspace.py +0 -0
  91. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_attach_relay.py +0 -0
  92. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_pacman.py +0 -0
  93. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux/windows_tmux_lifecycle.py +0 -0
  94. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/dependency_links.txt +0 -0
  95. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/entry_points.txt +0 -0
  96. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/requires.txt +0 -0
  97. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/src/railmux.egg-info/top_level.txt +0 -0
  98. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_agent_workspace.py +0 -0
  99. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_atomic_file.py +0 -0
  100. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_background_index.py +0 -0
  101. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_delete.py +0 -0
  102. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_index.py +0 -0
  103. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_lineage.py +0 -0
  104. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_codex_transcript.py +0 -0
  105. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_config.py +0 -0
  106. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_denested_transport_benchmark.py +0 -0
  107. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_discovery.py +0 -0
  108. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_display_transport.py +0 -0
  109. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_focus_highlight.py +0 -0
  110. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fullscreen_binding.py +0 -0
  111. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_fuzzy.py +0 -0
  112. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_help_agent.py +0 -0
  113. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_help_workspace.py +0 -0
  114. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_history_cleanup.py +0 -0
  115. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_keymap.py +0 -0
  116. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_launcher.py +0 -0
  117. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_layout_profile.py +0 -0
  118. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_legacy_sessions.py +0 -0
  119. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_local_clipboard.py +0 -0
  120. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_local_open.py +0 -0
  121. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_modals.py +0 -0
  122. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_mode_selection.py +0 -0
  123. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_modes.py +0 -0
  124. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_mouse_manager.py +0 -0
  125. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_new_project.py +0 -0
  126. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_options.py +0 -0
  127. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_orphan_recovery.py +0 -0
  128. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_pane_surface.py +0 -0
  129. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_path_codec.py +0 -0
  130. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_projects_pane.py +0 -0
  131. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_provider_paths.py +0 -0
  132. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_release_notes.py +0 -0
  133. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_release_workflow.py +0 -0
  134. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_renames.py +0 -0
  135. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_restart_state.py +0 -0
  136. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_running_pane.py +0 -0
  137. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_running_sort.py +0 -0
  138. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_runtime_config.py +0 -0
  139. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_scroll_agent.py +0 -0
  140. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_scroll_manager.py +0 -0
  141. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_selection_isolation.py +0 -0
  142. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_self_update.py +0 -0
  143. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_cache.py +0 -0
  144. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_index.py +0 -0
  145. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_session_lease.py +0 -0
  146. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_sessions_pane.py +0 -0
  147. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_setting_contracts.py +0 -0
  148. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_settings.py +0 -0
  149. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_sidebar.py +0 -0
  150. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_soft_quit.py +0 -0
  151. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_args.py +0 -0
  152. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_compat.py +0 -0
  153. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_display_benchmark.py +0 -0
  154. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_ssh_display_diagnostics.py +0 -0
  155. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_status.py +0 -0
  156. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_statusbar.py +0 -0
  157. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_system_deps.py +0 -0
  158. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_terminal_size.py +0 -0
  159. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_terminal_status.py +0 -0
  160. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_termux_local_touch.py +0 -0
  161. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_binding_manager.py +0 -0
  162. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_ctl.py +0 -0
  163. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_health.py +0 -0
  164. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_integration.py +0 -0
  165. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_server.py +0 -0
  166. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tmux_status.py +0 -0
  167. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_tool_panes.py +0 -0
  168. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_transcript.py +0 -0
  169. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_utf8_state.py +0 -0
  170. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_web_demo_recorder.py +0 -0
  171. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_wheel_smoke.py +0 -0
  172. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_widgets.py +0 -0
  173. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_attach_relay.py +0 -0
  174. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_install_log.py +0 -0
  175. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_pacman.py +0 -0
  176. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_tmux_lifecycle.py +0 -0
  177. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_windows_wrapper_contract.py +0 -0
  178. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tests/test_yolo_prompt.py +0 -0
  179. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/denested_transport_benchmark.py +0 -0
  180. {railmux-0.4.0.dev23 → railmux-0.4.0.dev26}/tools/release_notes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev23
3
+ Version: 0.4.0.dev26
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -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 = 3
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
- return _remote_launch_mode(args) is RemoteLaunchMode.DIRECT or (
1814
- hello is not None and hello.platform == "windows-msys2"
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
- launch_mode = _remote_launch_mode(args)
2278
- argv = build_ssh_argv(
2279
- args.destination,
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
- launch_mode=launch_mode,
2383
+ remote_platform=getattr(args, "remote_platform", "auto"),
2286
2384
  )
2287
- if on_stage is not None:
2288
- on_stage("Connecting to remote host…")
2289
- process = _spawn_remote(argv)
2290
- startup = await_remote_startup(process)
2291
- if (
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.POSIX
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
- direct_argv = build_ssh_argv(
2299
- args.destination,
2300
- session=args.session,
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
- "the remote shell did not accept the POSIX discovery command, "
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
- elif startup.kind is RemoteStartKind.HELLO:
2327
- _remember_remote_launch_mode(args, launch_mode)
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
- return base_payload == {
63
+ base_valid = base_payload == {
63
64
  "schema": 1,
64
65
  "runtime": runtime_id,
65
- } and app_payload == {
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
- build_ssh_argv,
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
- ) -> tuple[subprocess.Popen, RemoteStartup]:
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
- argv = build_ssh_argv(
86
+ probe = probe_remote_launch(
84
87
  destination,
85
- session="railmux",
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
- ) -> None:
177
- process, startup = _start_probe(destination, ssh_args)
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(destination, ssh_args, install="user")
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(destination, ssh_args, install="venv")
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: