railmux 0.4.0.dev26__tar.gz → 0.4.0.dev27__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 (181) hide show
  1. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/MANIFEST.in +1 -0
  2. {railmux-0.4.0.dev26/src/railmux.egg-info → railmux-0.4.0.dev27}/PKG-INFO +1 -1
  3. railmux-0.4.0.dev27/src/railmux/__init__.py +1 -0
  4. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_msys2.py +19 -2
  5. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27/src/railmux.egg-info}/PKG-INFO +1 -1
  6. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux.egg-info/SOURCES.txt +2 -1
  7. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_install_log.py +25 -0
  8. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_msys2.py +32 -4
  9. railmux-0.4.0.dev27/tools/windows_archive_smoke.py +67 -0
  10. railmux-0.4.0.dev26/src/railmux/__init__.py +0 -1
  11. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/LICENSE +0 -0
  12. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/README.md +0 -0
  13. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/pyproject.toml +0 -0
  14. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/setup.cfg +0 -0
  15. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/__main__.py +0 -0
  16. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/atomic_file.py +0 -0
  17. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/background_index.py +0 -0
  18. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/cli.py +0 -0
  19. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/codex_index.py +0 -0
  20. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/config.py +0 -0
  21. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/config_cli.py +0 -0
  22. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/diagnostics.py +0 -0
  23. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/discovery.py +0 -0
  24. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/display_transport.py +0 -0
  25. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/entrypoint.py +0 -0
  26. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fast_display_client.py +0 -0
  27. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fast_display_history.py +0 -0
  28. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fast_display_input.py +0 -0
  29. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fast_display_protocol.py +0 -0
  30. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fast_display_server.py +0 -0
  31. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/favorites.py +0 -0
  32. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/fuzzy.py +0 -0
  33. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/help_workspace.py +0 -0
  34. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/launcher.py +0 -0
  35. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/legacy_sessions.py +0 -0
  36. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/local_clipboard.py +0 -0
  37. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/local_open.py +0 -0
  38. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/models.py +0 -0
  39. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/modes.py +0 -0
  40. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/mouse_manager.py +0 -0
  41. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/orphan_marker.py +0 -0
  42. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/pane_surface.py +0 -0
  43. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/path_codec.py +0 -0
  44. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/provider_paths.py +0 -0
  45. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/remote_config.py +0 -0
  46. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/renames.py +0 -0
  47. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/restart_state.py +0 -0
  48. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/runtime_config.py +0 -0
  49. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/scroll_agent.py +0 -0
  50. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/scroll_manager.py +0 -0
  51. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/selection_isolation.py +0 -0
  52. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/self_update.py +0 -0
  53. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/session_cache.py +0 -0
  54. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/session_index.py +0 -0
  55. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/session_lease.py +0 -0
  56. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/setting_contracts.py +0 -0
  57. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/settings.py +0 -0
  58. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ssh_args.py +0 -0
  59. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ssh_compat.py +0 -0
  60. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ssh_display_diagnostics.py +0 -0
  61. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ssh_doctor.py +0 -0
  62. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/system_deps.py +0 -0
  63. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/terminal_status.py +0 -0
  64. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/tmux_binding_manager.py +0 -0
  65. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/tmux_ctl.py +0 -0
  66. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/tmux_health.py +0 -0
  67. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/tmux_server.py +0 -0
  68. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/tool_panes.py +0 -0
  69. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/transcript.py +0 -0
  70. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/__init__.py +0 -0
  71. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/_widgets.py +0 -0
  72. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/app.py +0 -0
  73. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/keymap.py +0 -0
  74. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/modals.py +0 -0
  75. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/projects_pane.py +0 -0
  76. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/running_pane.py +0 -0
  77. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/sessions_pane.py +0 -0
  78. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/sidebar.py +0 -0
  79. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/statusbar.py +0 -0
  80. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/ui/workspace.py +0 -0
  81. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_attach_relay.py +0 -0
  82. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_bootstrap.py +0 -0
  83. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_install_log.py +0 -0
  84. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_pacman.py +0 -0
  85. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_tmux_lifecycle.py +0 -0
  86. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux/windows_ui_transition.py +0 -0
  87. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux.egg-info/dependency_links.txt +0 -0
  88. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux.egg-info/entry_points.txt +0 -0
  89. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux.egg-info/requires.txt +0 -0
  90. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/src/railmux.egg-info/top_level.txt +0 -0
  91. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_agent_workspace.py +0 -0
  92. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_atomic_file.py +0 -0
  93. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_background_index.py +0 -0
  94. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_cli.py +0 -0
  95. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_codex_delete.py +0 -0
  96. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_codex_index.py +0 -0
  97. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_codex_lineage.py +0 -0
  98. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_codex_transcript.py +0 -0
  99. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_config.py +0 -0
  100. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_config_cli.py +0 -0
  101. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_denested_transport_benchmark.py +0 -0
  102. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_diagnostics.py +0 -0
  103. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_discovery.py +0 -0
  104. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_display_transport.py +0 -0
  105. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_fast_full_window.py +0 -0
  106. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_focus_highlight.py +0 -0
  107. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_fullscreen_binding.py +0 -0
  108. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_fuzzy.py +0 -0
  109. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_help_agent.py +0 -0
  110. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_help_workspace.py +0 -0
  111. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_history_cleanup.py +0 -0
  112. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_keymap.py +0 -0
  113. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_launcher.py +0 -0
  114. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_layout_profile.py +0 -0
  115. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_legacy_sessions.py +0 -0
  116. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_local_clipboard.py +0 -0
  117. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_local_open.py +0 -0
  118. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_modals.py +0 -0
  119. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_mode_selection.py +0 -0
  120. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_modes.py +0 -0
  121. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_mouse_manager.py +0 -0
  122. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_new_project.py +0 -0
  123. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_options.py +0 -0
  124. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_orphan_recovery.py +0 -0
  125. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_pane_surface.py +0 -0
  126. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_path_codec.py +0 -0
  127. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_projects_pane.py +0 -0
  128. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_provider_paths.py +0 -0
  129. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_release_notes.py +0 -0
  130. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_release_workflow.py +0 -0
  131. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_remote_config.py +0 -0
  132. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_renames.py +0 -0
  133. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_restart_state.py +0 -0
  134. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_running_pane.py +0 -0
  135. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_running_sort.py +0 -0
  136. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_runtime_config.py +0 -0
  137. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_scroll_agent.py +0 -0
  138. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_scroll_manager.py +0 -0
  139. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_selection_isolation.py +0 -0
  140. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_self_update.py +0 -0
  141. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_session_cache.py +0 -0
  142. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_session_index.py +0 -0
  143. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_session_lease.py +0 -0
  144. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_sessions_pane.py +0 -0
  145. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_setting_contracts.py +0 -0
  146. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_settings.py +0 -0
  147. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_sidebar.py +0 -0
  148. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_soft_quit.py +0 -0
  149. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_ssh_args.py +0 -0
  150. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_ssh_compat.py +0 -0
  151. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_ssh_display_benchmark.py +0 -0
  152. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_ssh_display_diagnostics.py +0 -0
  153. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_ssh_doctor.py +0 -0
  154. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_status.py +0 -0
  155. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_statusbar.py +0 -0
  156. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_system_deps.py +0 -0
  157. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_terminal_size.py +0 -0
  158. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_terminal_status.py +0 -0
  159. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_termux_local_touch.py +0 -0
  160. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_binding_manager.py +0 -0
  161. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_ctl.py +0 -0
  162. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_health.py +0 -0
  163. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_integration.py +0 -0
  164. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_server.py +0 -0
  165. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tmux_status.py +0 -0
  166. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_tool_panes.py +0 -0
  167. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_transcript.py +0 -0
  168. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_utf8_state.py +0 -0
  169. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_web_demo_recorder.py +0 -0
  170. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_wheel_smoke.py +0 -0
  171. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_widgets.py +0 -0
  172. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_attach_relay.py +0 -0
  173. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_bootstrap.py +0 -0
  174. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_pacman.py +0 -0
  175. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_tmux_lifecycle.py +0 -0
  176. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_ui_transition.py +0 -0
  177. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_windows_wrapper_contract.py +0 -0
  178. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tests/test_yolo_prompt.py +0 -0
  179. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tools/denested_transport_benchmark.py +0 -0
  180. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tools/release_notes.py +0 -0
  181. {railmux-0.4.0.dev26 → railmux-0.4.0.dev27}/tools/wheel_smoke.py +0 -0
@@ -1,3 +1,4 @@
1
1
  include tools/denested_transport_benchmark.py
2
2
  include tools/release_notes.py
3
3
  include tools/wheel_smoke.py
4
+ include tools/windows_archive_smoke.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev26
3
+ Version: 0.4.0.dev27
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.dev27"
@@ -80,6 +80,7 @@ MSYS2_BASE_LINEAGE_SHA256 = (
80
80
  "6fe0cc8154132040e034ff4daface2a4163a9d1f6ebaaa1133394bff460bd5cf"
81
81
  )
82
82
  MSYS2_RUNTIME_ID = f"msys2-{MSYS2_RELEASE}"
83
+ _NATIVE_WINDOWS = os.name == "nt"
83
84
  RUNTIME_SCHEMA = 1
84
85
 
85
86
  _RUNTIME_OVERRIDE = "RAILMUX_MSYS2_ROOT"
@@ -621,6 +622,16 @@ def _ensure_safe_archive_directory(root: Path, parts: Sequence[str]) -> Path:
621
622
  return current
622
623
 
623
624
 
625
+ def _apply_archive_mode(path: Path, mode: int) -> None:
626
+ if _NATIVE_WINDOWS:
627
+ # Windows chmod maps a missing owner-write bit to the NTFS read-only
628
+ # attribute; applying tar's POSIX 0444/0555 modes would prevent pacman
629
+ # from replacing those files during the first base update.
630
+ os.chmod(path, stat.S_IWRITE)
631
+ else:
632
+ os.chmod(path, mode)
633
+
634
+
624
635
  def _extract_msys2_archive(
625
636
  archive: Path,
626
637
  destination: Path,
@@ -696,7 +707,7 @@ def _extract_msys2_archive(
696
707
  )
697
708
  output.write(chunk)
698
709
  remaining -= len(chunk)
699
- os.chmod(target, member.mode & 0o777)
710
+ _apply_archive_mode(target, member.mode & 0o777)
700
711
  except RuntimeInstallError:
701
712
  raise
702
713
  except OSError as exc:
@@ -715,7 +726,7 @@ def _extract_msys2_archive(
715
726
  "the verified MSYS2 archive expanded to an unexpected size"
716
727
  )
717
728
  for directory, mode in reversed(directories):
718
- os.chmod(directory, mode)
729
+ _apply_archive_mode(directory, mode)
719
730
  except RuntimeInstallError:
720
731
  reporter.note(
721
732
  "MSYS2 archive extraction failed; the temporary base was not "
@@ -1613,6 +1624,12 @@ def _run_checked(
1613
1624
  if returncode:
1614
1625
  if allow_failure:
1615
1626
  return returncode
1627
+ if _NATIVE_WINDOWS and returncode & 0xFFFFFFFF == 0xC0000135:
1628
+ reporter.note(
1629
+ "Windows reported STATUS_DLL_NOT_FOUND. Check whether security "
1630
+ "software quarantined a file in the private MSYS2 staging tree.",
1631
+ level="error",
1632
+ )
1616
1633
  reporter.command_failed(label, returncode)
1617
1634
  raise RuntimeInstallError(f"{label} failed with exit code {returncode}")
1618
1635
  reporter.command_succeeded()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.4.0.dev26
3
+ Version: 0.4.0.dev27
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
@@ -174,4 +174,5 @@ tests/test_windows_wrapper_contract.py
174
174
  tests/test_yolo_prompt.py
175
175
  tools/denested_transport_benchmark.py
176
176
  tools/release_notes.py
177
- tools/wheel_smoke.py
177
+ tools/wheel_smoke.py
178
+ tools/windows_archive_smoke.py
@@ -2,8 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import io
4
4
  import os
5
+ import subprocess
5
6
  import sys
6
7
 
8
+ import pytest
9
+
10
+ from railmux import windows_msys2
7
11
  from railmux.windows_install_log import InstallReporter, install_log_path
8
12
  from railmux.windows_msys2 import _run_checked
9
13
 
@@ -194,6 +198,27 @@ def test_default_command_runner_uses_no_stdin_and_captures_utf8(tmp_path):
194
198
  assert "captured 中文" in path.read_text(encoding="utf-8")
195
199
 
196
200
 
201
+ def test_windows_missing_dll_status_is_explained(tmp_path, monkeypatch):
202
+ stream = io.StringIO()
203
+ path = tmp_path / "install.log"
204
+ monkeypatch.setattr(windows_msys2, "_NATIVE_WINDOWS", True)
205
+
206
+ with InstallReporter(path, verbose=False, stream=stream) as reporter:
207
+ with pytest.raises(windows_msys2.RuntimeInstallError, match="3221225781"):
208
+ _run_checked(
209
+ ["fixture.exe"],
210
+ env={},
211
+ reporter=reporter,
212
+ runner=lambda *_args, **_kwargs: subprocess.CompletedProcess(
213
+ [], 0xC0000135
214
+ ),
215
+ label="MSYS2 base update",
216
+ )
217
+
218
+ assert "STATUS_DLL_NOT_FOUND" in stream.getvalue()
219
+ assert "security software" in path.read_text(encoding="utf-8")
220
+
221
+
197
222
  def test_compact_reporter_surfaces_extraction_and_package_progress(tmp_path):
198
223
  stream = io.StringIO()
199
224
  path = tmp_path / "install-0.4.0.dev8-20260803T000000Z-7.log"
@@ -3,7 +3,9 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  import io
5
5
  import json
6
+ import os
6
7
  import re
8
+ import stat
7
9
  import subprocess
8
10
  import tarfile
9
11
  from contextlib import contextmanager
@@ -1291,12 +1293,16 @@ def test_tar_xz_extraction_is_internal_bounded_and_progress_visible(
1291
1293
  _tar_directory("msys64"),
1292
1294
  _tar_directory("msys64/usr"),
1293
1295
  _tar_file("msys64/usr/bash.exe", b"verified bash"),
1296
+ _tar_file("msys64/usr/read-only-upstream", b"must stay writable"),
1294
1297
  ]
1298
+ entries[-1][0].mode = 0o444
1295
1299
  archive = tmp_path / MSYS2_ARCHIVE_NAME
1296
1300
  _write_test_tar(archive, entries)
1297
- monkeypatch.setattr(windows_msys2, "MSYS2_ARCHIVE_MEMBER_COUNT", 3)
1301
+ monkeypatch.setattr(windows_msys2, "MSYS2_ARCHIVE_MEMBER_COUNT", 4)
1298
1302
  monkeypatch.setattr(
1299
- windows_msys2, "MSYS2_ARCHIVE_UNPACKED_SIZE", len(b"verified bash")
1303
+ windows_msys2,
1304
+ "MSYS2_ARCHIVE_UNPACKED_SIZE",
1305
+ len(b"verified bash") + len(b"must stay writable"),
1300
1306
  )
1301
1307
  destination = tmp_path / "stage"
1302
1308
  destination.mkdir()
@@ -1309,12 +1315,34 @@ def test_tar_xz_extraction_is_internal_bounded_and_progress_visible(
1309
1315
  assert (destination / "msys64" / "usr" / "bash.exe").read_bytes() == (
1310
1316
  b"verified bash"
1311
1317
  )
1318
+ package_owned = destination / "msys64" / "usr" / "read-only-upstream"
1319
+ if os.name == "nt":
1320
+ readonly = getattr(stat, "FILE_ATTRIBUTE_READONLY", 0x1)
1321
+ assert not getattr(package_owned.stat(), "st_file_attributes", 0) & readonly
1322
+ else:
1323
+ assert stat.S_IMODE(package_owned.stat().st_mode) == 0o444
1312
1324
  rendered = output.getvalue()
1313
- assert "Extracting private runtime: 1/3 files (33%)" in rendered
1314
- assert "Extracting private runtime: 3/3 files (100%)" in rendered
1325
+ assert "Extracting private runtime: 1/4 files (25%)" in rendered
1326
+ assert "Extracting private runtime: 4/4 files (100%)" in rendered
1315
1327
  assert "MSYS2 archive extraction" in log.read_text(encoding="utf-8")
1316
1328
 
1317
1329
 
1330
+ def test_native_windows_extraction_clears_posix_read_only_modes(
1331
+ tmp_path, monkeypatch,
1332
+ ):
1333
+ path = tmp_path / "package-owned"
1334
+ path.write_bytes(b"fixture")
1335
+ applied = []
1336
+ monkeypatch.setattr(windows_msys2, "_NATIVE_WINDOWS", True)
1337
+ monkeypatch.setattr(
1338
+ windows_msys2.os, "chmod", lambda target, mode: applied.append((target, mode))
1339
+ )
1340
+
1341
+ windows_msys2._apply_archive_mode(path, 0o444)
1342
+
1343
+ assert applied == [(path, stat.S_IWRITE)]
1344
+
1345
+
1318
1346
  @pytest.mark.parametrize(
1319
1347
  ("member", "message"),
1320
1348
  [
@@ -0,0 +1,67 @@
1
+ """Exercise the pinned MSYS2 archive with native Windows process loading."""
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ import os
6
+ import stat
7
+ import subprocess
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ from railmux.windows_install_log import InstallReporter
12
+ from railmux.windows_msys2 import (
13
+ MSYS2_ARCHIVE_SHA256,
14
+ Msys2Runtime,
15
+ _extract_msys2_archive,
16
+ download_from_sources,
17
+ )
18
+
19
+
20
+ def main() -> int:
21
+ if os.name != "nt":
22
+ raise SystemExit("the Windows archive smoke test requires native Windows")
23
+ with tempfile.TemporaryDirectory(prefix="railmux-archive-smoke-") as raw:
24
+ stage = Path(raw)
25
+ archive = stage / "msys2-base.tar.xz"
26
+ download_from_sources(archive, MSYS2_ARCHIVE_SHA256)
27
+ extraction = stage / "extract"
28
+ extraction.mkdir()
29
+ with InstallReporter(
30
+ stage / "install.log", verbose=False, stream=io.StringIO()
31
+ ) as reporter:
32
+ _extract_msys2_archive(archive, extraction, reporter=reporter)
33
+ runtime = Msys2Runtime(extraction / "msys64", managed=False)
34
+ bashbug = runtime.root / "usr" / "bin" / "bashbug"
35
+ readonly = getattr(stat, "FILE_ATTRIBUTE_READONLY", 0x1)
36
+ if getattr(bashbug.stat(), "st_file_attributes", 0) & readonly:
37
+ raise SystemExit("POSIX archive mode became an NTFS read-only attribute")
38
+ with bashbug.open("r+b") as stream:
39
+ first = stream.read(1)
40
+ stream.seek(0)
41
+ stream.write(first)
42
+ result = subprocess.run(
43
+ [
44
+ str(runtime.bash),
45
+ "--noprofile",
46
+ "--norc",
47
+ "-c",
48
+ "cygpath --version >/dev/null && pacman --version >/dev/null",
49
+ ],
50
+ env=runtime.environment(os.environ),
51
+ stdin=subprocess.DEVNULL,
52
+ stdout=subprocess.PIPE,
53
+ stderr=subprocess.STDOUT,
54
+ timeout=30,
55
+ check=False,
56
+ )
57
+ if result.returncode:
58
+ output = result.stdout.decode("utf-8", errors="replace")[-2000:]
59
+ raise SystemExit(
60
+ f"extracted MSYS2 executables failed ({result.returncode}):\n{output}"
61
+ )
62
+ print("Native Windows archive extraction and executable loading passed.")
63
+ return 0
64
+
65
+
66
+ if __name__ == "__main__":
67
+ raise SystemExit(main())
@@ -1 +0,0 @@
1
- __version__ = "0.4.0.dev26"
File without changes
File without changes
File without changes