railmux 0.4.0.dev22__tar.gz → 0.4.0.dev23__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 (178) hide show
  1. {railmux-0.4.0.dev22/src/railmux.egg-info → railmux-0.4.0.dev23}/PKG-INFO +1 -1
  2. railmux-0.4.0.dev23/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_client.py +142 -2
  4. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_server.py +5 -0
  5. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23/src/railmux.egg-info}/PKG-INFO +1 -1
  6. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fast_full_window.py +187 -1
  7. railmux-0.4.0.dev22/src/railmux/__init__.py +0 -1
  8. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/LICENSE +0 -0
  9. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/MANIFEST.in +0 -0
  10. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/README.md +0 -0
  11. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/pyproject.toml +0 -0
  12. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/setup.cfg +0 -0
  13. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/__main__.py +0 -0
  14. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/atomic_file.py +0 -0
  15. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/background_index.py +0 -0
  16. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/cli.py +0 -0
  17. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/codex_index.py +0 -0
  18. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/config.py +0 -0
  19. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/config_cli.py +0 -0
  20. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/diagnostics.py +0 -0
  21. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/discovery.py +0 -0
  22. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/display_transport.py +0 -0
  23. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/entrypoint.py +0 -0
  24. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_history.py +0 -0
  25. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_input.py +0 -0
  26. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fast_display_protocol.py +0 -0
  27. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/favorites.py +0 -0
  28. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/fuzzy.py +0 -0
  29. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/help_workspace.py +0 -0
  30. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/launcher.py +0 -0
  31. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/legacy_sessions.py +0 -0
  32. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/local_clipboard.py +0 -0
  33. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/local_open.py +0 -0
  34. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/models.py +0 -0
  35. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/modes.py +0 -0
  36. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/mouse_manager.py +0 -0
  37. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/orphan_marker.py +0 -0
  38. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/pane_surface.py +0 -0
  39. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/path_codec.py +0 -0
  40. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/provider_paths.py +0 -0
  41. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/remote_config.py +0 -0
  42. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/renames.py +0 -0
  43. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/restart_state.py +0 -0
  44. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/runtime_config.py +0 -0
  45. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/scroll_agent.py +0 -0
  46. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/scroll_manager.py +0 -0
  47. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/selection_isolation.py +0 -0
  48. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/self_update.py +0 -0
  49. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_cache.py +0 -0
  50. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_index.py +0 -0
  51. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/session_lease.py +0 -0
  52. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/setting_contracts.py +0 -0
  53. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/settings.py +0 -0
  54. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_args.py +0 -0
  55. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_compat.py +0 -0
  56. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_display_diagnostics.py +0 -0
  57. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ssh_doctor.py +0 -0
  58. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/system_deps.py +0 -0
  59. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/terminal_status.py +0 -0
  60. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_binding_manager.py +0 -0
  61. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_ctl.py +0 -0
  62. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_health.py +0 -0
  63. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tmux_server.py +0 -0
  64. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/tool_panes.py +0 -0
  65. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/transcript.py +0 -0
  66. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/__init__.py +0 -0
  67. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/_widgets.py +0 -0
  68. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/app.py +0 -0
  69. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/keymap.py +0 -0
  70. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/modals.py +0 -0
  71. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/projects_pane.py +0 -0
  72. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/running_pane.py +0 -0
  73. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/sessions_pane.py +0 -0
  74. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/sidebar.py +0 -0
  75. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/statusbar.py +0 -0
  76. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/ui/workspace.py +0 -0
  77. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_attach_relay.py +0 -0
  78. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_bootstrap.py +0 -0
  79. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_install_log.py +0 -0
  80. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_msys2.py +0 -0
  81. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_pacman.py +0 -0
  82. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux/windows_tmux_lifecycle.py +0 -0
  83. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/SOURCES.txt +0 -0
  84. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/dependency_links.txt +0 -0
  85. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/entry_points.txt +0 -0
  86. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/requires.txt +0 -0
  87. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/src/railmux.egg-info/top_level.txt +0 -0
  88. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_agent_workspace.py +0 -0
  89. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_atomic_file.py +0 -0
  90. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_background_index.py +0 -0
  91. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_cli.py +0 -0
  92. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_delete.py +0 -0
  93. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_index.py +0 -0
  94. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_lineage.py +0 -0
  95. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_codex_transcript.py +0 -0
  96. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_config.py +0 -0
  97. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_config_cli.py +0 -0
  98. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_denested_transport_benchmark.py +0 -0
  99. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_diagnostics.py +0 -0
  100. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_discovery.py +0 -0
  101. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_display_transport.py +0 -0
  102. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_focus_highlight.py +0 -0
  103. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fullscreen_binding.py +0 -0
  104. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_fuzzy.py +0 -0
  105. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_help_agent.py +0 -0
  106. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_help_workspace.py +0 -0
  107. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_history_cleanup.py +0 -0
  108. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_keymap.py +0 -0
  109. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_launcher.py +0 -0
  110. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_layout_profile.py +0 -0
  111. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_legacy_sessions.py +0 -0
  112. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_local_clipboard.py +0 -0
  113. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_local_open.py +0 -0
  114. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_modals.py +0 -0
  115. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_mode_selection.py +0 -0
  116. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_modes.py +0 -0
  117. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_mouse_manager.py +0 -0
  118. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_new_project.py +0 -0
  119. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_options.py +0 -0
  120. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_orphan_recovery.py +0 -0
  121. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_pane_surface.py +0 -0
  122. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_path_codec.py +0 -0
  123. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_projects_pane.py +0 -0
  124. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_provider_paths.py +0 -0
  125. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_release_notes.py +0 -0
  126. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_release_workflow.py +0 -0
  127. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_remote_config.py +0 -0
  128. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_renames.py +0 -0
  129. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_restart_state.py +0 -0
  130. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_running_pane.py +0 -0
  131. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_running_sort.py +0 -0
  132. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_runtime_config.py +0 -0
  133. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_scroll_agent.py +0 -0
  134. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_scroll_manager.py +0 -0
  135. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_selection_isolation.py +0 -0
  136. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_self_update.py +0 -0
  137. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_cache.py +0 -0
  138. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_index.py +0 -0
  139. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_session_lease.py +0 -0
  140. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_sessions_pane.py +0 -0
  141. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_setting_contracts.py +0 -0
  142. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_settings.py +0 -0
  143. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_sidebar.py +0 -0
  144. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_soft_quit.py +0 -0
  145. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_args.py +0 -0
  146. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_compat.py +0 -0
  147. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_display_benchmark.py +0 -0
  148. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_display_diagnostics.py +0 -0
  149. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_ssh_doctor.py +0 -0
  150. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_status.py +0 -0
  151. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_statusbar.py +0 -0
  152. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_system_deps.py +0 -0
  153. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_terminal_size.py +0 -0
  154. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_terminal_status.py +0 -0
  155. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_termux_local_touch.py +0 -0
  156. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_binding_manager.py +0 -0
  157. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_ctl.py +0 -0
  158. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_health.py +0 -0
  159. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_integration.py +0 -0
  160. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_server.py +0 -0
  161. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tmux_status.py +0 -0
  162. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_tool_panes.py +0 -0
  163. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_transcript.py +0 -0
  164. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_utf8_state.py +0 -0
  165. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_web_demo_recorder.py +0 -0
  166. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_wheel_smoke.py +0 -0
  167. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_widgets.py +0 -0
  168. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_attach_relay.py +0 -0
  169. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_bootstrap.py +0 -0
  170. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_install_log.py +0 -0
  171. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_msys2.py +0 -0
  172. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_pacman.py +0 -0
  173. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_tmux_lifecycle.py +0 -0
  174. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_windows_wrapper_contract.py +0 -0
  175. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tests/test_yolo_prompt.py +0 -0
  176. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/denested_transport_benchmark.py +0 -0
  177. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/release_notes.py +0 -0
  178. {railmux-0.4.0.dev22 → railmux-0.4.0.dev23}/tools/wheel_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev22
3
+ Version: 0.4.0.dev23
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.dev23"
@@ -281,6 +281,7 @@ class RemoteHello:
281
281
  config_status: str = "valid"
282
282
  tmux_configured: bool = False
283
283
  config_protocol: int = 0
284
+ platform: str = "posix"
284
285
 
285
286
 
286
287
  @dataclass(frozen=True)
@@ -296,6 +297,11 @@ class RemoteStartKind(Enum):
296
297
  TIMEOUT = "timeout"
297
298
 
298
299
 
300
+ class RemoteLaunchMode(Enum):
301
+ POSIX = "posix"
302
+ DIRECT = "direct"
303
+
304
+
299
305
  class RemoteAttachKind(Enum):
300
306
  ACCEPTED = "accepted"
301
307
  BUSY = "busy"
@@ -369,6 +375,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
369
375
  config_status = value.get("config_status", "valid")
370
376
  tmux_configured = value.get("tmux_configured", False)
371
377
  config_protocol = value.get("config_protocol", 0)
378
+ platform = value.get("platform", "posix")
372
379
  if (
373
380
  not isinstance(version, str)
374
381
  or not version
@@ -383,6 +390,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
383
390
  or not isinstance(config_protocol, int)
384
391
  or isinstance(config_protocol, bool)
385
392
  or not 0 <= config_protocol <= 65535
393
+ or platform not in {"posix", "windows-msys2"}
386
394
  ):
387
395
  raise ValueError("invalid Railmux remote hello")
388
396
  try:
@@ -397,6 +405,7 @@ def parse_remote_hello(line: bytes) -> RemoteHello:
397
405
  config_status,
398
406
  tmux_configured,
399
407
  config_protocol,
408
+ platform,
400
409
  )
401
410
 
402
411
 
@@ -1540,12 +1549,18 @@ def _remote_launch_command(server_args: Sequence[str]) -> str:
1540
1549
  return "; ".join(branches)
1541
1550
 
1542
1551
 
1552
+ def _remote_direct_launch_command(server_args: Sequence[str]) -> str:
1553
+ """Build a shell-neutral command accepted by Windows OpenSSH PowerShell."""
1554
+ return shlex.join(["railmux", *server_args])
1555
+
1556
+
1543
1557
  def build_remote_command_argv(
1544
1558
  destination: str,
1545
1559
  *,
1546
1560
  remote_args: Sequence[str],
1547
1561
  ssh_args: Sequence[str],
1548
1562
  force_tty: bool = False,
1563
+ launch_mode: RemoteLaunchMode = RemoteLaunchMode.POSIX,
1549
1564
  ) -> list[str]:
1550
1565
  """Build SSH argv through the shared remote Railmux discovery ladder."""
1551
1566
  return [
@@ -1556,7 +1571,11 @@ def build_remote_command_argv(
1556
1571
  # user arguments so a copied -t/-T flag cannot invert the transport.
1557
1572
  "-tt" if force_tty else "-T",
1558
1573
  destination,
1559
- _remote_launch_command(remote_args),
1574
+ (
1575
+ _remote_direct_launch_command(remote_args)
1576
+ if launch_mode is RemoteLaunchMode.DIRECT
1577
+ else _remote_launch_command(remote_args)
1578
+ ),
1560
1579
  ]
1561
1580
 
1562
1581
 
@@ -1570,6 +1589,7 @@ def build_ssh_argv(
1570
1589
  ssh_args: Sequence[str],
1571
1590
  replace_existing_client: bool = False,
1572
1591
  existing_session_only: bool = False,
1592
+ launch_mode: RemoteLaunchMode = RemoteLaunchMode.POSIX,
1573
1593
  ) -> list[str]:
1574
1594
  server_args = _remote_server_args(
1575
1595
  session=session,
@@ -1592,6 +1612,7 @@ def build_ssh_argv(
1592
1612
  "-o",
1593
1613
  "ServerAliveCountMax=3",
1594
1614
  ),
1615
+ launch_mode=launch_mode,
1595
1616
  )
1596
1617
 
1597
1618
 
@@ -1757,6 +1778,43 @@ def remote_install_help(destination: str, version: str) -> str:
1757
1778
  )
1758
1779
 
1759
1780
 
1781
+ def remote_windows_install_help(destination: str, version: str) -> str:
1782
+ return (
1783
+ f"Update the user-level Windows preview on {destination} from "
1784
+ "PowerShell, then retry:\n"
1785
+ f" py -m pip install --upgrade railmux=={version}\n"
1786
+ " railmux runtime install --yes\n"
1787
+ "The runtime command installs only the matching Railmux app layer and "
1788
+ "reuses a verified managed MSYS2 base when its pinned version is "
1789
+ "unchanged. It does not modify Codex or Claude session files. If 'py' "
1790
+ "is unavailable, use the same Windows Python executable that installed "
1791
+ "the Railmux command."
1792
+ )
1793
+
1794
+
1795
+ def _remote_launch_mode(args: argparse.Namespace) -> RemoteLaunchMode:
1796
+ selected = getattr(args, "_selected_remote_launch_mode", None)
1797
+ if isinstance(selected, RemoteLaunchMode):
1798
+ return selected
1799
+ if getattr(args, "remote_platform", "auto") == "windows":
1800
+ return RemoteLaunchMode.DIRECT
1801
+ return RemoteLaunchMode.POSIX
1802
+
1803
+
1804
+ def _remember_remote_launch_mode(
1805
+ args: argparse.Namespace, mode: RemoteLaunchMode
1806
+ ) -> None:
1807
+ args._selected_remote_launch_mode = mode
1808
+
1809
+
1810
+ def _remote_uses_windows_runtime(
1811
+ args: argparse.Namespace, hello: RemoteHello | None = None
1812
+ ) -> bool:
1813
+ return _remote_launch_mode(args) is RemoteLaunchMode.DIRECT or (
1814
+ hello is not None and hello.platform == "windows-msys2"
1815
+ )
1816
+
1817
+
1760
1818
  def remote_tmux_help(destination: str) -> str:
1761
1819
  return (
1762
1820
  f"tmux is not installed or not on PATH on {destination}. Install it "
@@ -1989,6 +2047,7 @@ def _reconnect_remote_attach(
1989
2047
  ssh_args=ssh_args,
1990
2048
  replace_existing_client=replace_existing_client,
1991
2049
  existing_session_only=existing_session_only,
2050
+ launch_mode=_remote_launch_mode(args),
1992
2051
  )
1993
2052
  process = (
1994
2053
  _spawn_remote(argv, suppress_stderr=True)
@@ -2215,6 +2274,7 @@ def prepare_remote_process(
2215
2274
  if before_interaction is not None:
2216
2275
  before_interaction()
2217
2276
 
2277
+ launch_mode = _remote_launch_mode(args)
2218
2278
  argv = build_ssh_argv(
2219
2279
  args.destination,
2220
2280
  session=args.session,
@@ -2222,11 +2282,62 @@ def prepare_remote_process(
2222
2282
  height=current_size.lines,
2223
2283
  fps=args.fps,
2224
2284
  ssh_args=args.ssh_arg,
2285
+ launch_mode=launch_mode,
2225
2286
  )
2226
2287
  if on_stage is not None:
2227
2288
  on_stage("Connecting to remote host…")
2228
2289
  process = _spawn_remote(argv)
2229
2290
  startup = await_remote_startup(process)
2291
+ if (
2292
+ 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
2296
+ ):
2297
+ _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
+ )
2320
+ 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__)}"
2325
+ )
2326
+ elif startup.kind is RemoteStartKind.HELLO:
2327
+ _remember_remote_launch_mode(args, launch_mode)
2328
+
2329
+ if (
2330
+ getattr(args, "remote_platform", "auto") == "windows"
2331
+ and startup.kind is not RemoteStartKind.HELLO
2332
+ ):
2333
+ _stop_unstarted_remote(process)
2334
+ if startup.returncode == 255:
2335
+ raise ProbeError("ssh could not connect to the remote host")
2336
+ if startup.kind is RemoteStartKind.TIMEOUT:
2337
+ raise ProbeError(
2338
+ "timed out waiting for the Windows Railmux compatibility handshake"
2339
+ )
2340
+ raise ProbeError(remote_windows_install_help(args.destination, __version__))
2230
2341
  install_version = __version__
2231
2342
  install_reason: str | None = None
2232
2343
  if startup.kind is RemoteStartKind.MISSING:
@@ -2281,6 +2392,12 @@ def prepare_remote_process(
2281
2392
  if decision.action == "prompt" and decision.prompt == "remote_install":
2282
2393
  assert decision.reason is not None
2283
2394
  assert decision.install_version is not None
2395
+ if _remote_uses_windows_runtime(args, hello):
2396
+ _stop_unstarted_remote(process)
2397
+ raise ProbeError(
2398
+ f"{decision.reason}\n"
2399
+ f"{remote_windows_install_help(args.destination, decision.install_version)}"
2400
+ )
2284
2401
  consents["remote_install"] = _confirm_remote_install(
2285
2402
  args,
2286
2403
  decision.reason,
@@ -2295,9 +2412,14 @@ def prepare_remote_process(
2295
2412
  _stop_unstarted_remote(process)
2296
2413
  message = decision.reason or "incompatible remote Railmux"
2297
2414
  if decision.install_version is not None:
2415
+ install_help = (
2416
+ remote_windows_install_help
2417
+ if _remote_uses_windows_runtime(args, hello)
2418
+ else remote_install_help
2419
+ )
2298
2420
  message = (
2299
2421
  f"{message}\n"
2300
- f"{remote_install_help(args.destination, decision.install_version)}"
2422
+ f"{install_help(args.destination, decision.install_version)}"
2301
2423
  )
2302
2424
  raise ProbeError(message)
2303
2425
  if decision.warning is not None:
@@ -2320,6 +2442,14 @@ def prepare_remote_process(
2320
2442
  raise AssertionError(f"unknown compatibility action: {decision.action}")
2321
2443
 
2322
2444
  assert install_reason is not None
2445
+ if _remote_uses_windows_runtime(
2446
+ args, startup.hello if startup.kind is RemoteStartKind.HELLO else None
2447
+ ):
2448
+ _stop_unstarted_remote(process)
2449
+ raise ProbeError(
2450
+ f"{install_reason}\n"
2451
+ f"{remote_windows_install_help(args.destination, install_version)}"
2452
+ )
2323
2453
  if startup.kind is not RemoteStartKind.HELLO and not _confirm_remote_install(
2324
2454
  args,
2325
2455
  install_reason,
@@ -2430,6 +2560,16 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
2430
2560
  default=20.0,
2431
2561
  help="maximum coalesced display update rate, 1-60 (default: 20)",
2432
2562
  )
2563
+ parser.add_argument(
2564
+ "--remote-platform",
2565
+ choices=("auto", "posix", "windows"),
2566
+ default="auto",
2567
+ help=(
2568
+ "remote shell family: auto tries POSIX discovery then direct "
2569
+ "Windows launch; windows avoids the failed POSIX probe and an "
2570
+ "extra password prompt (default: auto)"
2571
+ ),
2572
+ )
2433
2573
  parser.add_argument(
2434
2574
  "--no-mouse",
2435
2575
  action="store_true",
@@ -248,6 +248,11 @@ def _emit_remote_hello(
248
248
  "config_status": config_status,
249
249
  "tmux_configured": tmux_configured,
250
250
  "config_protocol": REMOTE_CONFIG_PROTOCOL,
251
+ "platform": (
252
+ "windows-msys2"
253
+ if os.environ.get("RAILMUX_WINDOWS_RUNTIME") == "msys2"
254
+ else "posix"
255
+ ),
251
256
  "version": __version__,
252
257
  },
253
258
  separators=(",", ":"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev22
3
+ Version: 0.4.0.dev23
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
@@ -68,6 +68,7 @@ from railmux.fast_display_client import (
68
68
  LOCAL_ESCAPE,
69
69
  RemoteHello,
70
70
  RemoteAttachKind,
71
+ RemoteLaunchMode,
71
72
  RemoteStartKind,
72
73
  RemoteStartup,
73
74
  ScreenModel,
@@ -4392,6 +4393,22 @@ def test_full_window_ssh_command_uses_railmux_remote_subcommand_and_protocol():
4392
4393
  assert "--width 120 --height 40 --fps 20.0" in argv[-1]
4393
4394
 
4394
4395
 
4396
+ def test_windows_remote_command_uses_shell_neutral_direct_launch():
4397
+ argv = build_ssh_argv(
4398
+ "windows-server",
4399
+ session="rail mux",
4400
+ width=120,
4401
+ height=40,
4402
+ fps=20.0,
4403
+ ssh_args=(),
4404
+ launch_mode=RemoteLaunchMode.DIRECT,
4405
+ )
4406
+
4407
+ assert argv[-1].startswith("railmux remote-server ")
4408
+ assert "if [" not in argv[-1]
4409
+ assert "--session 'rail mux'" in argv[-1]
4410
+
4411
+
4395
4412
  def test_full_window_ssh_keepalive_defaults_follow_user_overrides():
4396
4413
  argv = build_ssh_argv(
4397
4414
  "server",
@@ -4538,6 +4555,12 @@ def test_remote_hello_is_strictly_bounded_and_typed():
4538
4555
  )
4539
4556
  assert configured.config_status == "invalid"
4540
4557
  assert configured.tmux_configured is True
4558
+ windows = parse_remote_hello(
4559
+ REMOTE_HELLO_PREFIX
4560
+ + b'{"platform":"windows-msys2","protocol":6,"ready":true,'
4561
+ b'"tmux":true,"version":"1.2.3"}\n'
4562
+ )
4563
+ assert windows.platform == "windows-msys2"
4541
4564
  with pytest.raises(ValueError):
4542
4565
  parse_remote_hello(
4543
4566
  REMOTE_HELLO_PREFIX + b'{"protocol":true,"ready":true,"tmux":true,'
@@ -4545,6 +4568,12 @@ def test_remote_hello_is_strictly_bounded_and_typed():
4545
4568
  )
4546
4569
  with pytest.raises(ValueError):
4547
4570
  parse_remote_hello(REMOTE_HELLO_PREFIX + b"not-json\n")
4571
+ with pytest.raises(ValueError):
4572
+ parse_remote_hello(
4573
+ REMOTE_HELLO_PREFIX
4574
+ + b'{"platform":"windows","protocol":6,"ready":true,'
4575
+ b'"tmux":true,"version":"1.2.3"}\n'
4576
+ )
4548
4577
 
4549
4578
 
4550
4579
  def test_remote_startup_wait_reads_hello_before_raw_mode():
@@ -4853,6 +4882,14 @@ def test_ssh_parser_accepts_ordered_exact_and_grouped_arguments():
4853
4882
  ]
4854
4883
 
4855
4884
 
4885
+ def test_ssh_parser_selects_remote_platform_without_changing_default():
4886
+ assert parse_client_args(["server"]).remote_platform == "auto"
4887
+ assert (
4888
+ parse_client_args(["server", "--remote-platform", "windows"]).remote_platform
4889
+ == "windows"
4890
+ )
4891
+
4892
+
4856
4893
  def test_reconnect_attach_forces_noninteractive_bounded_ssh(monkeypatch):
4857
4894
  process = _PreflightProcess()
4858
4895
  built = MagicMock(return_value=["ssh", "remote"])
@@ -4901,6 +4938,7 @@ def test_reconnect_attach_forces_noninteractive_bounded_ssh(monkeypatch):
4901
4938
  "ConnectTimeout=5",
4902
4939
  ]
4903
4940
  assert built.call_args.kwargs["replace_existing_client"] is False
4941
+ assert built.call_args.kwargs["launch_mode"] is RemoteLaunchMode.POSIX
4904
4942
  spawn.assert_called_once_with(
4905
4943
  ["ssh", "remote"],
4906
4944
  suppress_stderr=True,
@@ -4948,6 +4986,44 @@ def test_reconnect_connect_timeout_keeps_user_first_value(monkeypatch):
4948
4986
  )
4949
4987
 
4950
4988
 
4989
+ def test_reconnect_reuses_selected_windows_launch_mode(monkeypatch):
4990
+ process = _PreflightProcess()
4991
+ built = MagicMock(return_value=["ssh", "remote"])
4992
+ monkeypatch.setattr(fast_display_client, "build_ssh_argv", built)
4993
+ monkeypatch.setattr(
4994
+ fast_display_client,
4995
+ "_spawn_remote",
4996
+ MagicMock(return_value=process),
4997
+ )
4998
+ monkeypatch.setattr(
4999
+ fast_display_client,
5000
+ "await_remote_startup",
5001
+ lambda *_args, **_kwargs: RemoteStartup(
5002
+ RemoteStartKind.HELLO,
5003
+ RemoteHello(
5004
+ fast_display_client.__version__,
5005
+ PROTOCOL_VERSION,
5006
+ True,
5007
+ platform="windows-msys2",
5008
+ ),
5009
+ ),
5010
+ )
5011
+ monkeypatch.setattr(
5012
+ fast_display_client,
5013
+ "await_remote_attach_status",
5014
+ lambda *_args, **_kwargs: RemoteAttachKind.ACCEPTED,
5015
+ )
5016
+ args = parse_client_args(["server", "--remote-platform", "windows"])
5017
+
5018
+ fast_display_client._reconnect_remote_attach(
5019
+ args,
5020
+ os.terminal_size((120, 40)),
5021
+ replace_existing_client=False,
5022
+ )
5023
+
5024
+ assert built.call_args.kwargs["launch_mode"] is RemoteLaunchMode.DIRECT
5025
+
5026
+
4951
5027
  def test_remote_command_keeps_railmux_tty_mode_after_user_flags():
4952
5028
  binary = build_remote_command_argv(
4953
5029
  "server",
@@ -5348,6 +5424,71 @@ def test_compatible_remote_is_confirmed_before_attach(monkeypatch):
5348
5424
  ]
5349
5425
 
5350
5426
 
5427
+ def test_auto_remote_launch_falls_back_to_windows_and_pins_reconnect_mode(
5428
+ monkeypatch,
5429
+ ):
5430
+ _accept_attach(monkeypatch)
5431
+ posix = _PreflightProcess(1)
5432
+ windows = _PreflightProcess()
5433
+ processes = iter((posix, windows))
5434
+ commands = []
5435
+
5436
+ def spawn(argv):
5437
+ commands.append(argv)
5438
+ return next(processes)
5439
+
5440
+ startups = iter(
5441
+ (
5442
+ RemoteStartup(RemoteStartKind.FAILED, returncode=1),
5443
+ RemoteStartup(
5444
+ RemoteStartKind.HELLO,
5445
+ RemoteHello(
5446
+ fast_display_client.__version__,
5447
+ PROTOCOL_VERSION,
5448
+ True,
5449
+ platform="windows-msys2",
5450
+ ),
5451
+ ),
5452
+ )
5453
+ )
5454
+ monkeypatch.setattr(fast_display_client, "_spawn_remote", spawn)
5455
+ monkeypatch.setattr(
5456
+ fast_display_client,
5457
+ "await_remote_startup",
5458
+ lambda _process: next(startups),
5459
+ )
5460
+ args = parse_client_args(["server"])
5461
+
5462
+ selected = prepare_remote_process(args, os.terminal_size((120, 40)))
5463
+
5464
+ assert selected is windows
5465
+ assert posix.terminated is False
5466
+ assert "if [" in commands[0][-1]
5467
+ assert commands[1][-1].startswith("railmux remote-server ")
5468
+ assert args._selected_remote_launch_mode is RemoteLaunchMode.DIRECT
5469
+ assert windows.stdin.getvalue() == REMOTE_START
5470
+
5471
+
5472
+ def test_explicit_windows_remote_failure_never_runs_posix_installer(monkeypatch):
5473
+ process = _PreflightProcess(1)
5474
+ args = parse_client_args(["server", "--remote-platform", "windows"])
5475
+ monkeypatch.setattr(fast_display_client, "_spawn_remote", lambda _argv: process)
5476
+ monkeypatch.setattr(
5477
+ fast_display_client,
5478
+ "await_remote_startup",
5479
+ lambda _process: RemoteStartup(RemoteStartKind.FAILED, returncode=1),
5480
+ )
5481
+ install = MagicMock()
5482
+ monkeypatch.setattr(fast_display_client, "_install_remote_and_start", install)
5483
+
5484
+ with pytest.raises(fast_display_client.ProbeError) as exc:
5485
+ prepare_remote_process(args, os.terminal_size((120, 40)))
5486
+
5487
+ assert "py -m pip install --upgrade" in str(exc.value)
5488
+ assert "railmux runtime install --yes" in str(exc.value)
5489
+ install.assert_not_called()
5490
+
5491
+
5351
5492
  def test_reconnect_flag_does_not_change_initial_ssh_command():
5352
5493
  plain = parse_client_args(["server"])
5353
5494
  reconnecting = parse_client_args(["server", "--reconnect"])
@@ -5492,7 +5633,8 @@ def test_missing_remote_prompts_then_installs_and_starts(monkeypatch):
5492
5633
  missing = _PreflightProcess(127)
5493
5634
  installed = _PreflightProcess()
5494
5635
  args = parse_client_args(["server"])
5495
- monkeypatch.setattr(fast_display_client, "_spawn_remote", lambda _argv: missing)
5636
+ spawn = MagicMock(return_value=missing)
5637
+ monkeypatch.setattr(fast_display_client, "_spawn_remote", spawn)
5496
5638
  monkeypatch.setattr(
5497
5639
  fast_display_client,
5498
5640
  "await_remote_startup",
@@ -5515,6 +5657,7 @@ def test_missing_remote_prompts_then_installs_and_starts(monkeypatch):
5515
5657
 
5516
5658
  assert selected is installed
5517
5659
  assert installed.stdin.getvalue() == REMOTE_START
5660
+ spawn.assert_called_once()
5518
5661
 
5519
5662
 
5520
5663
  def test_missing_remote_decline_returns_copyable_install_help(
@@ -5813,6 +5956,37 @@ def test_older_remote_protocol_prompts_for_matching_remote_upgrade(monkeypatch):
5813
5956
  assert upgraded.stdin.getvalue() == REMOTE_START
5814
5957
 
5815
5958
 
5959
+ def test_windows_remote_upgrade_fails_closed_with_windows_guidance(monkeypatch):
5960
+ process = _PreflightProcess()
5961
+ args = parse_client_args(["server", "--remote-platform", "windows"])
5962
+ monkeypatch.setattr(fast_display_client, "_spawn_remote", lambda _argv: process)
5963
+ monkeypatch.setattr(
5964
+ fast_display_client,
5965
+ "await_remote_startup",
5966
+ lambda _process: RemoteStartup(
5967
+ RemoteStartKind.HELLO,
5968
+ RemoteHello(
5969
+ "0.1.0",
5970
+ PROTOCOL_VERSION - 1,
5971
+ True,
5972
+ platform="windows-msys2",
5973
+ ),
5974
+ ),
5975
+ )
5976
+ confirm = MagicMock()
5977
+ install = MagicMock()
5978
+ monkeypatch.setattr(fast_display_client, "_confirm", confirm)
5979
+ monkeypatch.setattr(fast_display_client, "_install_remote_and_start", install)
5980
+
5981
+ with pytest.raises(fast_display_client.ProbeError) as exc:
5982
+ prepare_remote_process(args, os.terminal_size((120, 40)))
5983
+
5984
+ assert "py -m pip install --upgrade" in str(exc.value)
5985
+ assert "railmux runtime install --yes" in str(exc.value)
5986
+ confirm.assert_not_called()
5987
+ install.assert_not_called()
5988
+
5989
+
5816
5990
  def test_older_compatible_remote_can_be_upgraded_to_local_version(monkeypatch):
5817
5991
  _accept_attach(monkeypatch)
5818
5992
  monkeypatch.setattr(fast_display_client, "__version__", "0.2.5")
@@ -6066,6 +6240,18 @@ def test_remote_server_hello_reports_version_protocol_and_dependency(monkeypatch
6066
6240
  )
6067
6241
 
6068
6242
 
6243
+ def test_remote_server_hello_identifies_managed_windows_runtime(monkeypatch):
6244
+ output = io.BytesIO()
6245
+ monkeypatch.setenv("RAILMUX_WINDOWS_RUNTIME", "msys2")
6246
+ monkeypatch.setattr(fast_display_server.shutil, "which", lambda _name: "/tmux")
6247
+ monkeypatch.setattr(fast_display_server.sys, "stdout", MagicMock(buffer=output))
6248
+
6249
+ fast_display_server._emit_remote_hello(True)
6250
+
6251
+ hello = parse_remote_hello(output.getvalue())
6252
+ assert hello.platform == "windows-msys2"
6253
+
6254
+
6069
6255
  def test_remote_server_waits_for_exact_start_confirmation(monkeypatch):
6070
6256
  remote_input = MagicMock(buffer=io.BytesIO(REMOTE_START))
6071
6257
  monkeypatch.setattr(fast_display_server.sys, "stdin", remote_input)
@@ -1 +0,0 @@
1
- __version__ = "0.4.0.dev22"
File without changes
File without changes
File without changes
File without changes