railmux 0.4.0.dev10__tar.gz → 0.4.0.dev11__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 (174) hide show
  1. {railmux-0.4.0.dev10/src/railmux.egg-info → railmux-0.4.0.dev11}/PKG-INFO +1 -1
  2. railmux-0.4.0.dev11/src/railmux/__init__.py +1 -0
  3. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/provider_paths.py +37 -15
  4. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/windows_bootstrap.py +28 -7
  5. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/windows_msys2.py +395 -53
  6. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11/src/railmux.egg-info}/PKG-INFO +1 -1
  7. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_provider_paths.py +31 -9
  8. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_bootstrap.py +77 -1
  9. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_msys2.py +313 -24
  10. railmux-0.4.0.dev10/src/railmux/__init__.py +0 -1
  11. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/LICENSE +0 -0
  12. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/MANIFEST.in +0 -0
  13. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/README.md +0 -0
  14. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/pyproject.toml +0 -0
  15. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/setup.cfg +0 -0
  16. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/__main__.py +0 -0
  17. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/atomic_file.py +0 -0
  18. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/background_index.py +0 -0
  19. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/cli.py +0 -0
  20. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/codex_index.py +0 -0
  21. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/config.py +0 -0
  22. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/config_cli.py +0 -0
  23. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/diagnostics.py +0 -0
  24. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/discovery.py +0 -0
  25. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/display_transport.py +0 -0
  26. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/entrypoint.py +0 -0
  27. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fast_display_client.py +0 -0
  28. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fast_display_history.py +0 -0
  29. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fast_display_input.py +0 -0
  30. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fast_display_protocol.py +0 -0
  31. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fast_display_server.py +0 -0
  32. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/favorites.py +0 -0
  33. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/fuzzy.py +0 -0
  34. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/help_workspace.py +0 -0
  35. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/launcher.py +0 -0
  36. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/legacy_sessions.py +0 -0
  37. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/local_clipboard.py +0 -0
  38. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/local_open.py +0 -0
  39. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/models.py +0 -0
  40. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/modes.py +0 -0
  41. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/mouse_manager.py +0 -0
  42. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/orphan_marker.py +0 -0
  43. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/pane_surface.py +0 -0
  44. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/path_codec.py +0 -0
  45. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/remote_config.py +0 -0
  46. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/renames.py +0 -0
  47. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/restart_state.py +0 -0
  48. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/runtime_config.py +0 -0
  49. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/scroll_agent.py +0 -0
  50. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/scroll_manager.py +0 -0
  51. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/selection_isolation.py +0 -0
  52. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/self_update.py +0 -0
  53. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/session_cache.py +0 -0
  54. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/session_index.py +0 -0
  55. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/setting_contracts.py +0 -0
  56. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/settings.py +0 -0
  57. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ssh_args.py +0 -0
  58. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ssh_compat.py +0 -0
  59. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ssh_display_diagnostics.py +0 -0
  60. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ssh_doctor.py +0 -0
  61. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/system_deps.py +0 -0
  62. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/terminal_status.py +0 -0
  63. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/tmux_binding_manager.py +0 -0
  64. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/tmux_ctl.py +0 -0
  65. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/tmux_health.py +0 -0
  66. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/tmux_server.py +0 -0
  67. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/tool_panes.py +0 -0
  68. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/transcript.py +0 -0
  69. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/__init__.py +0 -0
  70. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/_widgets.py +0 -0
  71. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/app.py +0 -0
  72. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/keymap.py +0 -0
  73. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/modals.py +0 -0
  74. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/projects_pane.py +0 -0
  75. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/running_pane.py +0 -0
  76. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/sessions_pane.py +0 -0
  77. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/sidebar.py +0 -0
  78. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/statusbar.py +0 -0
  79. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/ui/workspace.py +0 -0
  80. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/windows_install_log.py +0 -0
  81. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/windows_pacman.py +0 -0
  82. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux/windows_tmux_lifecycle.py +0 -0
  83. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux.egg-info/SOURCES.txt +0 -0
  84. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux.egg-info/dependency_links.txt +0 -0
  85. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux.egg-info/entry_points.txt +0 -0
  86. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux.egg-info/requires.txt +0 -0
  87. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/src/railmux.egg-info/top_level.txt +0 -0
  88. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_agent_workspace.py +0 -0
  89. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_atomic_file.py +0 -0
  90. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_background_index.py +0 -0
  91. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_cli.py +0 -0
  92. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_codex_delete.py +0 -0
  93. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_codex_index.py +0 -0
  94. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_codex_lineage.py +0 -0
  95. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_codex_transcript.py +0 -0
  96. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_config.py +0 -0
  97. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_config_cli.py +0 -0
  98. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_denested_transport_benchmark.py +0 -0
  99. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_diagnostics.py +0 -0
  100. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_discovery.py +0 -0
  101. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_display_transport.py +0 -0
  102. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_fast_full_window.py +0 -0
  103. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_focus_highlight.py +0 -0
  104. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_fullscreen_binding.py +0 -0
  105. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_fuzzy.py +0 -0
  106. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_help_agent.py +0 -0
  107. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_help_workspace.py +0 -0
  108. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_history_cleanup.py +0 -0
  109. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_keymap.py +0 -0
  110. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_launcher.py +0 -0
  111. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_layout_profile.py +0 -0
  112. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_legacy_sessions.py +0 -0
  113. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_local_clipboard.py +0 -0
  114. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_local_open.py +0 -0
  115. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_modals.py +0 -0
  116. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_mode_selection.py +0 -0
  117. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_modes.py +0 -0
  118. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_mouse_manager.py +0 -0
  119. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_new_project.py +0 -0
  120. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_options.py +0 -0
  121. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_orphan_recovery.py +0 -0
  122. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_pane_surface.py +0 -0
  123. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_path_codec.py +0 -0
  124. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_projects_pane.py +0 -0
  125. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_release_notes.py +0 -0
  126. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_release_workflow.py +0 -0
  127. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_remote_config.py +0 -0
  128. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_renames.py +0 -0
  129. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_restart_state.py +0 -0
  130. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_running_pane.py +0 -0
  131. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_running_sort.py +0 -0
  132. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_runtime_config.py +0 -0
  133. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_scroll_agent.py +0 -0
  134. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_scroll_manager.py +0 -0
  135. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_selection_isolation.py +0 -0
  136. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_self_update.py +0 -0
  137. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_session_cache.py +0 -0
  138. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_session_index.py +0 -0
  139. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_sessions_pane.py +0 -0
  140. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_setting_contracts.py +0 -0
  141. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_settings.py +0 -0
  142. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_sidebar.py +0 -0
  143. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_soft_quit.py +0 -0
  144. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_ssh_args.py +0 -0
  145. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_ssh_compat.py +0 -0
  146. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_ssh_display_benchmark.py +0 -0
  147. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_ssh_display_diagnostics.py +0 -0
  148. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_ssh_doctor.py +0 -0
  149. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_status.py +0 -0
  150. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_statusbar.py +0 -0
  151. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_system_deps.py +0 -0
  152. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_terminal_size.py +0 -0
  153. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_terminal_status.py +0 -0
  154. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_termux_local_touch.py +0 -0
  155. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_binding_manager.py +0 -0
  156. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_ctl.py +0 -0
  157. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_health.py +0 -0
  158. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_integration.py +0 -0
  159. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_server.py +0 -0
  160. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tmux_status.py +0 -0
  161. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_tool_panes.py +0 -0
  162. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_transcript.py +0 -0
  163. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_utf8_state.py +0 -0
  164. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_web_demo_recorder.py +0 -0
  165. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_wheel_smoke.py +0 -0
  166. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_widgets.py +0 -0
  167. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_install_log.py +0 -0
  168. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_pacman.py +0 -0
  169. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_tmux_lifecycle.py +0 -0
  170. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_windows_wrapper_contract.py +0 -0
  171. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tests/test_yolo_prompt.py +0 -0
  172. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tools/denested_transport_benchmark.py +0 -0
  173. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/tools/release_notes.py +0 -0
  174. {railmux-0.4.0.dev10 → railmux-0.4.0.dev11}/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.dev10
3
+ Version: 0.4.0.dev11
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.dev11"
@@ -11,7 +11,11 @@ from pathlib import Path
11
11
 
12
12
 
13
13
  _DRIVE_PATH = re.compile(r"^(?:\\\\\?\\)?([A-Za-z]):[\\/](.*)\Z")
14
- _MANAGED_RUNTIME_MARKER = Path("/railmux-runtime.json")
14
+ _MANAGED_BASE_MARKER = Path("/railmux-base.json")
15
+ _MANAGED_APP_ROOT = Path("/opt/railmux/apps")
16
+ _MANAGED_APP_ID = re.compile(
17
+ r"railmux-[0-9]+(?:\.[0-9]+)*(?:\.dev[0-9]+)?\Z"
18
+ )
15
19
 
16
20
 
17
21
  def running_in_windows_wrapper(environ: Mapping[str, str] = os.environ) -> bool:
@@ -25,22 +29,40 @@ def running_in_managed_windows_wrapper(
25
29
  """Verify the installed MSYS2 runtime, not merely its environment hint."""
26
30
  if sys.platform != "cygwin" or not running_in_windows_wrapper(environ):
27
31
  return False
28
- marker = _MANAGED_RUNTIME_MARKER
29
- try:
30
- info = marker.lstat()
31
- if (
32
- not stat.S_ISREG(info.st_mode)
33
- or info.st_uid != os.getuid()
34
- or info.st_mode & 0o022
35
- or info.st_size > 4096
36
- ):
37
- return False
38
- payload = json.loads(marker.read_text(encoding="utf-8"))
39
- except (OSError, UnicodeError, json.JSONDecodeError):
32
+ runtime_id = environ.get("RAILMUX_MSYS2_RUNTIME_ID")
33
+ app_id = environ.get("RAILMUX_MSYS2_APP_ID")
34
+ if (
35
+ not isinstance(runtime_id, str)
36
+ or not runtime_id
37
+ or not isinstance(app_id, str)
38
+ or _MANAGED_APP_ID.fullmatch(app_id) is None
39
+ ):
40
40
  return False
41
+
42
+ def read_safe_marker(marker: Path) -> object | None:
43
+ try:
44
+ info = marker.lstat()
45
+ if (
46
+ not stat.S_ISREG(info.st_mode)
47
+ or info.st_uid != os.getuid()
48
+ or info.st_mode & 0o022
49
+ or info.st_size > 4096
50
+ ):
51
+ return None
52
+ return json.loads(marker.read_text(encoding="utf-8"))
53
+ except (OSError, UnicodeError, json.JSONDecodeError):
54
+ return None
55
+
56
+ base_payload = read_safe_marker(_MANAGED_BASE_MARKER)
57
+ app_payload = read_safe_marker(
58
+ _MANAGED_APP_ROOT / app_id / "railmux-app.json"
59
+ )
41
60
  from railmux import __version__
42
- runtime_id = environ.get("RAILMUX_MSYS2_RUNTIME_ID")
43
- return isinstance(runtime_id, str) and bool(runtime_id) and payload == {
61
+
62
+ return base_payload == {
63
+ "schema": 1,
64
+ "runtime": runtime_id,
65
+ } and app_payload == {
44
66
  "schema": 1,
45
67
  "runtime": runtime_id,
46
68
  "railmux": __version__,
@@ -14,6 +14,7 @@ from railmux.windows_msys2 import (
14
14
  find_runtime,
15
15
  install_managed_runtime,
16
16
  managed_root,
17
+ reusable_managed_base_candidate,
17
18
  )
18
19
 
19
20
 
@@ -45,7 +46,7 @@ def _runtime_status(
45
46
  ownership = "Railmux-managed" if runtime.managed else "user-owned override"
46
47
  print(f"Runtime: {ownership} at {runtime.root}")
47
48
  else:
48
- root = managed_root(environ, version=__version__)
49
+ root = managed_root(environ)
49
50
  if root is not None:
50
51
  print(f"Managed location: {root}")
51
52
  print("Provider data: shared from the Windows user profile")
@@ -55,7 +56,8 @@ def _runtime_status(
55
56
  def _confirm_install(*, input_fn: Callable[[str], str] = input) -> bool:
56
57
  try:
57
58
  answer = input_fn(
58
- "Install the private MSYS2/tmux runtime now? On Windows, Railmux "
59
+ "No reusable MSYS2 base was found. Install the private MSYS2/tmux "
60
+ "runtime now? On Windows, Railmux "
59
61
  "depends on a complete private MSYS2 compatibility wrapper, "
60
62
  "including tmux and Python. This downloads a 50 MB base plus "
61
63
  "required updates and packages, "
@@ -74,14 +76,30 @@ def _install(
74
76
  verbose: bool = False,
75
77
  input_fn: Callable[[str], str] = input,
76
78
  ) -> Msys2Runtime | None:
77
- if not assume_yes and not _confirm_install(input_fn=input_fn):
78
- print("MSYS2 runtime installation cancelled.", file=sys.stderr)
79
- return None
79
+ reuse_only = False
80
+ if not assume_yes:
81
+ reusable = reusable_managed_base_candidate(environ)
82
+ if reusable is None:
83
+ if not _confirm_install(input_fn=input_fn):
84
+ print("MSYS2 runtime installation cancelled.", file=sys.stderr)
85
+ return None
86
+ else:
87
+ _root, source_version = reusable
88
+ detail = (
89
+ f" from Railmux {source_version}" if source_version else ""
90
+ )
91
+ print(
92
+ f"Reusing the matching MSYS2 {MSYS2_RELEASE} private base"
93
+ f"{detail}; only the Railmux {__version__} app layer will be "
94
+ "installed."
95
+ )
96
+ reuse_only = True
80
97
  try:
81
98
  runtime = install_managed_runtime(
82
99
  version=__version__,
83
100
  environ=environ,
84
101
  verbose=verbose,
102
+ reuse_only=reuse_only,
85
103
  )
86
104
  except RuntimeInstallError as exc:
87
105
  print(f"error: {exc}", file=sys.stderr)
@@ -166,7 +184,7 @@ def main(
166
184
  return 0 if runtime is not None else 2
167
185
 
168
186
  if runtime is None:
169
- root = managed_root(environ, version=__version__)
187
+ root = managed_root(environ)
170
188
  print(
171
189
  "Railmux for Windows needs its private MSYS2/tmux runtime.\n"
172
190
  "The Windows Codex/Claude executables and their existing session "
@@ -175,7 +193,10 @@ def main(
175
193
  )
176
194
  if root is not None:
177
195
  print(f"Managed location: {root}", file=sys.stderr)
178
- if not stdin_isatty():
196
+ if (
197
+ not stdin_isatty()
198
+ and reusable_managed_base_candidate(environ) is None
199
+ ):
179
200
  print(
180
201
  "Run 'railmux runtime install' interactively or "
181
202
  "'railmux runtime install --yes' to install it.",