railmux 0.4.0.dev2__tar.gz → 0.4.0.dev4__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 (206) hide show
  1. {railmux-0.4.0.dev2/src/railmux.egg-info → railmux-0.4.0.dev4}/PKG-INFO +24 -19
  2. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/README.md +18 -10
  3. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/pyproject.toml +6 -9
  4. railmux-0.4.0.dev4/src/railmux/__init__.py +1 -0
  5. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/__main__.py +1 -1
  6. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/cli.py +2 -40
  7. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/codex_index.py +20 -9
  8. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/config.py +3 -4
  9. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/discovery.py +7 -66
  10. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/display_transport.py +18 -0
  11. railmux-0.4.0.dev4/src/railmux/entrypoint.py +21 -0
  12. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fast_display_client.py +24 -11
  13. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fast_display_server.py +35 -8
  14. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/favorites.py +1 -2
  15. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/help_workspace.py +7 -2
  16. railmux-0.4.0.dev4/src/railmux/local_clipboard.py +47 -0
  17. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/local_open.py +10 -37
  18. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/modes.py +0 -8
  19. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/mouse_manager.py +11 -7
  20. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/orphan_marker.py +14 -9
  21. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/path_codec.py +16 -0
  22. railmux-0.4.0.dev4/src/railmux/provider_paths.py +41 -0
  23. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/renames.py +1 -2
  24. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/restart_state.py +12 -7
  25. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/runtime_config.py +1 -4
  26. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/scroll_manager.py +5 -3
  27. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/self_update.py +5 -0
  28. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ssh_display_diagnostics.py +5 -3
  29. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/tmux_binding_manager.py +3 -7
  30. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/tmux_ctl.py +82 -7
  31. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/transcript.py +83 -28
  32. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/app.py +413 -350
  33. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/modals.py +1 -1
  34. railmux-0.4.0.dev4/src/railmux/windows_bootstrap.py +174 -0
  35. railmux-0.4.0.dev4/src/railmux/windows_msys2.py +431 -0
  36. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4/src/railmux.egg-info}/PKG-INFO +24 -19
  37. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux.egg-info/SOURCES.txt +9 -36
  38. railmux-0.4.0.dev4/src/railmux.egg-info/entry_points.txt +2 -0
  39. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux.egg-info/requires.txt +7 -7
  40. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_cli.py +0 -15
  41. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_codex_delete.py +3 -7
  42. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_codex_index.py +19 -0
  43. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_config.py +8 -1
  44. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_discovery.py +0 -24
  45. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_display_transport.py +21 -1
  46. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_focus_highlight.py +135 -8
  47. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_local_open.py +0 -26
  48. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_modes.py +0 -2
  49. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_mouse_manager.py +17 -0
  50. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_orphan_recovery.py +2 -2
  51. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_path_codec.py +17 -1
  52. railmux-0.4.0.dev4/tests/test_provider_paths.py +29 -0
  53. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_release_workflow.py +25 -17
  54. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_runtime_config.py +0 -12
  55. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_self_update.py +11 -0
  56. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_soft_quit.py +83 -2
  57. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_ctl.py +28 -0
  58. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_integration.py +10 -2
  59. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_transcript.py +84 -0
  60. railmux-0.4.0.dev4/tests/test_utf8_state.py +111 -0
  61. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_wheel_smoke.py +1 -6
  62. railmux-0.4.0.dev4/tests/test_windows_bootstrap.py +143 -0
  63. railmux-0.4.0.dev4/tests/test_windows_msys2.py +216 -0
  64. railmux-0.4.0.dev4/tests/test_windows_wrapper_contract.py +73 -0
  65. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tools/wheel_smoke.py +6 -12
  66. railmux-0.4.0.dev2/src/railmux/__init__.py +0 -1
  67. railmux-0.4.0.dev2/src/railmux/local_clipboard.py +0 -101
  68. railmux-0.4.0.dev2/src/railmux/mux/__init__.py +0 -7
  69. railmux-0.4.0.dev2/src/railmux/mux/backend.py +0 -75
  70. railmux-0.4.0.dev2/src/railmux/mux/tmux_backend.py +0 -165
  71. railmux-0.4.0.dev2/src/railmux/platform/__init__.py +0 -44
  72. railmux-0.4.0.dev2/src/railmux/platform/config_paths.py +0 -30
  73. railmux-0.4.0.dev2/src/railmux/platform/console.py +0 -165
  74. railmux-0.4.0.dev2/src/railmux/platform/file_security.py +0 -34
  75. railmux-0.4.0.dev2/src/railmux/platform/filelock.py +0 -42
  76. railmux-0.4.0.dev2/src/railmux/platform/multiplex.py +0 -132
  77. railmux-0.4.0.dev2/src/railmux/platform/process.py +0 -21
  78. railmux-0.4.0.dev2/src/railmux/platform/runtime_paths.py +0 -75
  79. railmux-0.4.0.dev2/src/railmux/terminal_emulator.py +0 -180
  80. railmux-0.4.0.dev2/src/railmux/ui/status_chrome.py +0 -149
  81. railmux-0.4.0.dev2/src/railmux/winlocal/__init__.py +0 -2
  82. railmux-0.4.0.dev2/src/railmux/winlocal/backend.py +0 -1088
  83. railmux-0.4.0.dev2/src/railmux/winlocal/client.py +0 -299
  84. railmux-0.4.0.dev2/src/railmux/winlocal/compositor.py +0 -287
  85. railmux-0.4.0.dev2/src/railmux/winlocal/conpty.py +0 -200
  86. railmux-0.4.0.dev2/src/railmux/winlocal/daemon.py +0 -398
  87. railmux-0.4.0.dev2/src/railmux/winlocal/history_viewer.py +0 -93
  88. railmux-0.4.0.dev2/src/railmux/winlocal/ipc.py +0 -88
  89. railmux-0.4.0.dev2/src/railmux/winlocal/session_store.py +0 -96
  90. railmux-0.4.0.dev2/src/railmux/winlocal/virtual_screen.py +0 -105
  91. railmux-0.4.0.dev2/src/railmux.egg-info/entry_points.txt +0 -2
  92. railmux-0.4.0.dev2/tests/test_mux_backend.py +0 -60
  93. railmux-0.4.0.dev2/tests/test_platform.py +0 -248
  94. railmux-0.4.0.dev2/tests/test_status_chrome.py +0 -87
  95. railmux-0.4.0.dev2/tests/test_windows_parity_contract.py +0 -79
  96. railmux-0.4.0.dev2/tests/test_winlocal_backend.py +0 -577
  97. railmux-0.4.0.dev2/tests/test_winlocal_compositor.py +0 -67
  98. railmux-0.4.0.dev2/tests/test_winlocal_conpty.py +0 -60
  99. railmux-0.4.0.dev2/tests/test_winlocal_daemon.py +0 -203
  100. railmux-0.4.0.dev2/tests/test_winlocal_history_viewer.py +0 -43
  101. railmux-0.4.0.dev2/tests/test_winlocal_ipc.py +0 -39
  102. railmux-0.4.0.dev2/tests/test_winlocal_real_conpty.py +0 -94
  103. railmux-0.4.0.dev2/tests/test_winlocal_session_store.py +0 -42
  104. railmux-0.4.0.dev2/tests/test_winlocal_virtual_screen.py +0 -62
  105. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/LICENSE +0 -0
  106. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/MANIFEST.in +0 -0
  107. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/setup.cfg +0 -0
  108. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/atomic_file.py +0 -0
  109. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/background_index.py +0 -0
  110. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/config_cli.py +0 -0
  111. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/diagnostics.py +0 -0
  112. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fast_display_history.py +0 -0
  113. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fast_display_input.py +0 -0
  114. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fast_display_protocol.py +0 -0
  115. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/fuzzy.py +0 -0
  116. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/launcher.py +0 -0
  117. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/legacy_sessions.py +0 -0
  118. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/models.py +0 -0
  119. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/pane_surface.py +0 -0
  120. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/remote_config.py +0 -0
  121. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/scroll_agent.py +0 -0
  122. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/selection_isolation.py +0 -0
  123. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/session_cache.py +0 -0
  124. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/session_index.py +0 -0
  125. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/setting_contracts.py +0 -0
  126. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/settings.py +0 -0
  127. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ssh_args.py +0 -0
  128. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ssh_compat.py +0 -0
  129. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ssh_doctor.py +0 -0
  130. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/system_deps.py +0 -0
  131. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/terminal_status.py +0 -0
  132. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/tmux_health.py +0 -0
  133. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/tmux_server.py +0 -0
  134. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/tool_panes.py +0 -0
  135. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/__init__.py +0 -0
  136. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/_widgets.py +0 -0
  137. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/keymap.py +0 -0
  138. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/projects_pane.py +0 -0
  139. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/running_pane.py +0 -0
  140. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/sessions_pane.py +0 -0
  141. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/sidebar.py +0 -0
  142. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/statusbar.py +0 -0
  143. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux/ui/workspace.py +0 -0
  144. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux.egg-info/dependency_links.txt +0 -0
  145. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/src/railmux.egg-info/top_level.txt +0 -0
  146. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_agent_workspace.py +0 -0
  147. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_atomic_file.py +0 -0
  148. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_background_index.py +0 -0
  149. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_codex_lineage.py +0 -0
  150. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_codex_transcript.py +0 -0
  151. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_config_cli.py +0 -0
  152. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_denested_transport_benchmark.py +0 -0
  153. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_diagnostics.py +0 -0
  154. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_fast_full_window.py +0 -0
  155. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_fullscreen_binding.py +0 -0
  156. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_fuzzy.py +0 -0
  157. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_help_agent.py +0 -0
  158. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_help_workspace.py +0 -0
  159. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_history_cleanup.py +0 -0
  160. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_keymap.py +0 -0
  161. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_launcher.py +0 -0
  162. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_layout_profile.py +0 -0
  163. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_legacy_sessions.py +0 -0
  164. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_local_clipboard.py +0 -0
  165. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_modals.py +0 -0
  166. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_mode_selection.py +0 -0
  167. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_new_project.py +0 -0
  168. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_options.py +0 -0
  169. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_pane_surface.py +0 -0
  170. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_projects_pane.py +0 -0
  171. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_release_notes.py +0 -0
  172. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_remote_config.py +0 -0
  173. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_renames.py +0 -0
  174. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_restart_state.py +0 -0
  175. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_running_pane.py +0 -0
  176. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_running_sort.py +0 -0
  177. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_scroll_agent.py +0 -0
  178. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_scroll_manager.py +0 -0
  179. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_selection_isolation.py +0 -0
  180. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_session_cache.py +0 -0
  181. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_session_index.py +0 -0
  182. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_sessions_pane.py +0 -0
  183. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_setting_contracts.py +0 -0
  184. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_settings.py +0 -0
  185. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_sidebar.py +0 -0
  186. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_ssh_args.py +0 -0
  187. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_ssh_compat.py +0 -0
  188. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_ssh_display_benchmark.py +0 -0
  189. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_ssh_display_diagnostics.py +0 -0
  190. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_ssh_doctor.py +0 -0
  191. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_status.py +0 -0
  192. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_statusbar.py +0 -0
  193. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_system_deps.py +0 -0
  194. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_terminal_size.py +0 -0
  195. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_terminal_status.py +0 -0
  196. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_termux_local_touch.py +0 -0
  197. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_binding_manager.py +0 -0
  198. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_health.py +0 -0
  199. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_server.py +0 -0
  200. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tmux_status.py +0 -0
  201. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_tool_panes.py +0 -0
  202. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_web_demo_recorder.py +0 -0
  203. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_widgets.py +0 -0
  204. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tests/test_yolo_prompt.py +0 -0
  205. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/tools/denested_transport_benchmark.py +0 -0
  206. {railmux-0.4.0.dev2 → railmux-0.4.0.dev4}/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.dev2
3
+ Version: 0.4.0.dev4
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
@@ -21,21 +21,18 @@ Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
23
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Programming Language :: Python :: 3.14
25
24
  Classifier: Topic :: Software Development
26
25
  Classifier: Topic :: Terminals
27
26
  Classifier: Topic :: Utilities
28
27
  Requires-Python: >=3.9
29
28
  Description-Content-Type: text/markdown
30
29
  License-File: LICENSE
31
- Requires-Dist: packaging>=23.0
32
- Requires-Dist: tomlkit<1,>=0.13.2
33
- Requires-Dist: urwid>=2.6.16
34
- Requires-Dist: tomli>=2.0.1; python_version < "3.11"
35
- Requires-Dist: pyte<0.9,>=0.8.2; sys_platform == "win32"
36
- Requires-Dist: pywinpty<4,>=3.0.5; sys_platform == "win32"
30
+ Requires-Dist: packaging>=23.0; sys_platform != "win32"
31
+ Requires-Dist: tomlkit<1,>=0.13.2; sys_platform != "win32"
32
+ Requires-Dist: urwid>=2.6.16; sys_platform != "win32"
33
+ Requires-Dist: tomli>=2.0.1; python_version < "3.11" and sys_platform != "win32"
37
34
  Provides-Extra: ssh
38
- Requires-Dist: pyte<0.9,>=0.8.2; extra == "ssh"
35
+ Requires-Dist: pyte<0.9,>=0.8.2; sys_platform != "win32" and extra == "ssh"
39
36
  Provides-Extra: dev
40
37
  Requires-Dist: pytest>=8.0; extra == "dev"
41
38
  Requires-Dist: build>=1.2; extra == "dev"
@@ -173,12 +170,14 @@ for focus and layout limits.
173
170
  |--------|--------|
174
171
  | Left-click (non-running) | Preview session history in the Target pane |
175
172
  | Left-click (running) | Switch the Target pane to that session |
173
+ | Wheel up over a live agent | Open its canonical current-branch history |
176
174
  | Double-click | Open/attach in the Target pane and move focus there |
177
175
  | Right-click | Context menu (Open, Preview, Info, Rename, Star, Copy title, Kill, Term, Delete) |
178
176
 
179
177
  Unavailable context-menu actions are hidden for the selected session state.
180
- Single-click and `␣` act in the Target pane without moving keyboard focus;
181
- double-click and `Enter` also move focus there. The terminal must report mouse
178
+ Single-click and `␣` act in the Target pane without moving keyboard focus, but
179
+ `␣` always means history while a click on a running row means live attach.
180
+ Double-click and `Enter` also move focus there. The terminal must report mouse
182
181
  buttons to applications for these actions to reach Railmux. Right-click
183
182
  reporting is sometimes a separate setting from ordinary mouse reporting; see
184
183
  [FAQ 2](#2-mouse-buttons-or-f8f9-dont-work--whats-wrong).
@@ -211,7 +210,7 @@ see [FAQ 1](#1-how-do-i-copy-text-from-the-agent-pane).
211
210
  | `T` | Return to that agent's managed Vim viewer, when open |
212
211
  | `m` | Switch agent mode (Claude Code ⇄ Codex) |
213
212
  | `o` | Open persistent Railmux options |
214
- | `␣` | Preview stopped or switch running target (like single-click) |
213
+ | `␣` | Preview canonical history for the selected stopped or running session |
215
214
  | `F8` | Cycle agent layout: single → side-by-side → stacked |
216
215
  | `F9` | Fullscreen the focused agent, managed terminal, or Vim (toggle) |
217
216
  | `?` | Full help popup with all keybindings |
@@ -303,7 +302,8 @@ to single leaves Pane 2's agent running in the background.
303
302
 
304
303
  In a split, focus an agent pane to make it the Target pane. After focus returns
305
304
  to the sidebar, single-click or `␣` acts in that pane without moving keyboard
306
- focus; double-click or `Enter` opens there and transfers focus. The status bar
305
+ focus; a running-row click attaches live while `␣` previews history.
306
+ Double-click or `Enter` opens there and transfers focus. The status bar
307
307
  at the bottom-left shows the current layout and Target pane:
308
308
 
309
309
  | Symbol | Meaning |
@@ -367,15 +367,20 @@ sessions move ahead of the other results.
367
367
  ## History preview
368
368
 
369
369
  For a stopped session, left-click or press `␣` to view conversation history in
370
- the Target pane without starting or resuming the agent. Preview is read-only: it
371
- cannot send a message or change the session. User and assistant messages, tool
372
- calls, and abbreviated tool output are colour-coded, while internal context and
373
- encrypted reasoning are hidden.
370
+ the Target pane without starting or resuming the agent. For a running session,
371
+ press `␣`, choose **Preview**, or wheel up over its live pane. Preview is
372
+ read-only: it cannot send a message or change the session. User and assistant
373
+ messages, tool calls, and abbreviated tool output are colour-coded, while
374
+ internal context and encrypted reasoning are hidden. Codex rewind lineages and
375
+ Claude Code `parentUuid` branches are projected onto the provider's current
376
+ conversation: the retained prefix and replacement suffix remain visible, while
377
+ the abandoned suffix is hidden.
374
378
 
375
379
  Preview opens at the latest activity in `less`; large sessions are limited to
376
380
  their latest 2,000 saved records. Press `/` to search, `n`/`N` to move between
377
- matches, and `q` to exit and restore the pane. Double-click to skip preview and
378
- open the session directly.
381
+ matches, and `q` to exit. If preview began from a live pane, `q` restores that
382
+ exact session; otherwise it restores the prior Target pane. Double-click to
383
+ skip preview and open the session directly.
379
384
 
380
385
  ## Status indicators
381
386
 
@@ -127,12 +127,14 @@ for focus and layout limits.
127
127
  |--------|--------|
128
128
  | Left-click (non-running) | Preview session history in the Target pane |
129
129
  | Left-click (running) | Switch the Target pane to that session |
130
+ | Wheel up over a live agent | Open its canonical current-branch history |
130
131
  | Double-click | Open/attach in the Target pane and move focus there |
131
132
  | Right-click | Context menu (Open, Preview, Info, Rename, Star, Copy title, Kill, Term, Delete) |
132
133
 
133
134
  Unavailable context-menu actions are hidden for the selected session state.
134
- Single-click and `␣` act in the Target pane without moving keyboard focus;
135
- double-click and `Enter` also move focus there. The terminal must report mouse
135
+ Single-click and `␣` act in the Target pane without moving keyboard focus, but
136
+ `␣` always means history while a click on a running row means live attach.
137
+ Double-click and `Enter` also move focus there. The terminal must report mouse
136
138
  buttons to applications for these actions to reach Railmux. Right-click
137
139
  reporting is sometimes a separate setting from ordinary mouse reporting; see
138
140
  [FAQ 2](#2-mouse-buttons-or-f8f9-dont-work--whats-wrong).
@@ -165,7 +167,7 @@ see [FAQ 1](#1-how-do-i-copy-text-from-the-agent-pane).
165
167
  | `T` | Return to that agent's managed Vim viewer, when open |
166
168
  | `m` | Switch agent mode (Claude Code ⇄ Codex) |
167
169
  | `o` | Open persistent Railmux options |
168
- | `␣` | Preview stopped or switch running target (like single-click) |
170
+ | `␣` | Preview canonical history for the selected stopped or running session |
169
171
  | `F8` | Cycle agent layout: single → side-by-side → stacked |
170
172
  | `F9` | Fullscreen the focused agent, managed terminal, or Vim (toggle) |
171
173
  | `?` | Full help popup with all keybindings |
@@ -257,7 +259,8 @@ to single leaves Pane 2's agent running in the background.
257
259
 
258
260
  In a split, focus an agent pane to make it the Target pane. After focus returns
259
261
  to the sidebar, single-click or `␣` acts in that pane without moving keyboard
260
- focus; double-click or `Enter` opens there and transfers focus. The status bar
262
+ focus; a running-row click attaches live while `␣` previews history.
263
+ Double-click or `Enter` opens there and transfers focus. The status bar
261
264
  at the bottom-left shows the current layout and Target pane:
262
265
 
263
266
  | Symbol | Meaning |
@@ -321,15 +324,20 @@ sessions move ahead of the other results.
321
324
  ## History preview
322
325
 
323
326
  For a stopped session, left-click or press `␣` to view conversation history in
324
- the Target pane without starting or resuming the agent. Preview is read-only: it
325
- cannot send a message or change the session. User and assistant messages, tool
326
- calls, and abbreviated tool output are colour-coded, while internal context and
327
- encrypted reasoning are hidden.
327
+ the Target pane without starting or resuming the agent. For a running session,
328
+ press `␣`, choose **Preview**, or wheel up over its live pane. Preview is
329
+ read-only: it cannot send a message or change the session. User and assistant
330
+ messages, tool calls, and abbreviated tool output are colour-coded, while
331
+ internal context and encrypted reasoning are hidden. Codex rewind lineages and
332
+ Claude Code `parentUuid` branches are projected onto the provider's current
333
+ conversation: the retained prefix and replacement suffix remain visible, while
334
+ the abandoned suffix is hidden.
328
335
 
329
336
  Preview opens at the latest activity in `less`; large sessions are limited to
330
337
  their latest 2,000 saved records. Press `/` to search, `n`/`N` to move between
331
- matches, and `q` to exit and restore the pane. Double-click to skip preview and
332
- open the session directly.
338
+ matches, and `q` to exit. If preview began from a live pane, `q` restores that
339
+ exact session; otherwise it restores the prior Target pane. Double-click to
340
+ skip preview and open the session directly.
333
341
 
334
342
  ## Status indicators
335
343
 
@@ -27,23 +27,20 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.11",
28
28
  "Programming Language :: Python :: 3.12",
29
29
  "Programming Language :: Python :: 3.13",
30
- "Programming Language :: Python :: 3.14",
31
30
  "Topic :: Software Development",
32
31
  "Topic :: Terminals",
33
32
  "Topic :: Utilities",
34
33
  ]
35
34
  dependencies = [
36
- "packaging>=23.0",
37
- "tomlkit>=0.13.2,<1",
38
- "urwid>=2.6.16",
39
- "tomli>=2.0.1; python_version < '3.11'",
40
- "pyte>=0.8.2,<0.9; sys_platform == 'win32'",
41
- "pywinpty>=3.0.5,<4; sys_platform == 'win32'",
35
+ "packaging>=23.0; sys_platform != 'win32'",
36
+ "tomlkit>=0.13.2,<1; sys_platform != 'win32'",
37
+ "urwid>=2.6.16; sys_platform != 'win32'",
38
+ "tomli>=2.0.1; python_version < '3.11' and sys_platform != 'win32'",
42
39
  ]
43
40
 
44
41
  [project.optional-dependencies]
45
42
  ssh = [
46
- "pyte>=0.8.2,<0.9",
43
+ "pyte>=0.8.2,<0.9; sys_platform != 'win32'",
47
44
  ]
48
45
  dev = [
49
46
  "pytest>=8.0",
@@ -54,7 +51,7 @@ dev = [
54
51
  ]
55
52
 
56
53
  [project.scripts]
57
- railmux = "railmux.cli:main"
54
+ railmux = "railmux.entrypoint:main"
58
55
 
59
56
  [project.urls]
60
57
  Homepage = "https://rightglow.github.io/Railmux/"
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0.dev4"
@@ -1,4 +1,4 @@
1
- from railmux.cli import main
1
+ from railmux.entrypoint import main
2
2
 
3
3
  if __name__ == "__main__":
4
4
  raise SystemExit(main())
@@ -1,18 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
- import os
5
4
  import shutil
6
5
  import subprocess
7
6
  import sys
7
+ import termios
8
8
  import time
9
9
  from dataclasses import replace
10
10
  from pathlib import Path
11
11
 
12
12
  from railmux import __version__
13
13
  from railmux.config import ConfigError, default_config_path, load_config
14
+ from railmux.diagnostics import is_ssh_session, run_doctor
14
15
  from railmux.pane_surface import render_startup_surface
15
- from railmux.platform import python_support_error
16
16
  from railmux.runtime_config import (
17
17
  activate_runtime_environment,
18
18
  check_executable,
@@ -37,29 +37,9 @@ _LOCAL_WATCHDOG_INTERVAL = 5.0
37
37
  _LOCAL_WATCHDOG_FAILURES = 3
38
38
 
39
39
 
40
- def _native_windows() -> bool:
41
- return os.name == "nt"
42
-
43
-
44
- def is_ssh_session(environment: dict[str, str] | None = None) -> bool:
45
- """Lazy compatibility wrapper that keeps POSIX diagnostics import-clean."""
46
- from railmux.diagnostics import is_ssh_session as detect
47
-
48
- return detect(environment)
49
-
50
-
51
- def run_doctor(*args: object, **kwargs: object) -> int:
52
- """Lazy compatibility wrapper for the established CLI test/repair hook."""
53
- from railmux.diagnostics import run_doctor as doctor
54
-
55
- return doctor(*args, **kwargs)
56
-
57
-
58
40
  def _restore_terminal(attributes: list | None) -> None:
59
41
  if attributes is None:
60
42
  return
61
- import termios
62
-
63
43
  try:
64
44
  termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attributes)
65
45
  except (OSError, termios.error):
@@ -87,8 +67,6 @@ def _run_tmux_client_with_watchdog(
87
67
  """Keep one monitor outside tmux so a frozen server cannot trap the TTY."""
88
68
  attributes = None
89
69
  if sys.stdin.isatty():
90
- import termios
91
-
92
70
  try:
93
71
  attributes = termios.tcgetattr(sys.stdin.fileno())
94
72
  except (OSError, termios.error):
@@ -174,10 +152,6 @@ def _run_tmux_client_with_watchdog(
174
152
 
175
153
 
176
154
  def main(argv: list[str] | None = None) -> int:
177
- support_error = python_support_error()
178
- if support_error is not None:
179
- print(f"error: {support_error}", file=sys.stderr)
180
- return 2
181
155
  raw_args = list(sys.argv[1:] if argv is None else argv)
182
156
  if raw_args and raw_args[0] == "config":
183
157
  from railmux.config_cli import main as config_main
@@ -364,18 +338,6 @@ def main(argv: list[str] | None = None) -> int:
364
338
  file=sys.stderr,
365
339
  )
366
340
  return 2
367
- if _native_windows():
368
- # Native Windows owns no local tmux server. The detached daemon keeps
369
- # ConPTY handles alive while this terminal remains a disposable client.
370
- activate_runtime_environment(replace(config, tmux_binary="tmux"))
371
- if not args.inside_tmux:
372
- from railmux.self_update import maybe_upgrade_before_launch
373
- from railmux.settings import Settings
374
-
375
- maybe_upgrade_before_launch(raw_args, Settings())
376
- from railmux.winlocal.client import main as native_main
377
-
378
- return native_main(raw_args, config=config)
379
341
  if config.tmux_binary != "tmux":
380
342
  tmux_check = check_executable("tmux", config.tmux_binary)
381
343
  if not tmux_check.valid:
@@ -13,21 +13,19 @@ import hashlib
13
13
  import json
14
14
  import math
15
15
  import os
16
+ import stat
16
17
  import time
17
18
  from dataclasses import dataclass, replace
18
19
  from pathlib import Path
19
20
 
20
21
  from railmux.atomic_file import atomic_write_text
21
- from railmux.platform.file_security import (
22
- prepare_private_directory,
23
- private_regular_file,
24
- )
25
22
  from railmux.models import (
26
23
  AttentionCategory,
27
24
  AttentionState,
28
25
  Project,
29
26
  SessionMeta,
30
27
  )
28
+ from railmux.provider_paths import provider_path
31
29
  from railmux.renames import Renames
32
30
 
33
31
  # Codex has no reliable provider-neutral signal that distinguishes a long
@@ -100,9 +98,10 @@ class HiddenCodexStatus:
100
98
  def persistent_cache_path(codex_home: Path) -> Path:
101
99
  """Return a private cache path unique to one Codex sessions root."""
102
100
  raw_base = os.environ.get("XDG_CACHE_HOME")
101
+ requested_base = provider_path(raw_base).expanduser() if raw_base else None
103
102
  base = (
104
- Path(raw_base).expanduser()
105
- if raw_base and Path(raw_base).expanduser().is_absolute()
103
+ requested_base
104
+ if requested_base is not None and requested_base.is_absolute()
106
105
  else Path.home() / ".cache"
107
106
  )
108
107
  try:
@@ -460,7 +459,9 @@ class CodexIndex:
460
459
  try:
461
460
  info = path.lstat()
462
461
  if (
463
- not private_regular_file(info)
462
+ not stat.S_ISREG(info.st_mode)
463
+ or info.st_uid != os.getuid()
464
+ or info.st_mode & 0o077
464
465
  or info.st_size > _PERSISTENT_CACHE_MAX_BYTES
465
466
  ):
466
467
  return
@@ -515,7 +516,17 @@ class CodexIndex:
515
516
  path = self._cache_path
516
517
  if path is None:
517
518
  return False
518
- return prepare_private_directory(path.parent)
519
+ parent = path.parent
520
+ try:
521
+ parent.mkdir(mode=0o700, parents=True, exist_ok=True)
522
+ info = parent.lstat()
523
+ if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid():
524
+ return False
525
+ if info.st_mode & 0o077:
526
+ os.chmod(parent, stat.S_IMODE(info.st_mode) & ~0o077)
527
+ return True
528
+ except OSError:
529
+ return False
519
530
 
520
531
  def _save_persistent_cache(self) -> None:
521
532
  path = self._cache_path
@@ -1027,7 +1038,7 @@ def _parse_codex_session(
1027
1038
  cwd_str = payload.get("cwd")
1028
1039
  if not isinstance(cwd_str, str) or not cwd_str.strip():
1029
1040
  return None
1030
- cwd = Path(cwd_str)
1041
+ cwd = provider_path(cwd_str)
1031
1042
  forked_from = payload.get("forked_from_id")
1032
1043
  forked_from_id = (
1033
1044
  forked_from
@@ -6,8 +6,6 @@ from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
- from railmux.platform.config_paths import default_config_path as _platform_config_path
10
-
11
9
  try:
12
10
  import tomllib
13
11
  except ModuleNotFoundError: # Python 3.9-3.10
@@ -20,6 +18,7 @@ from railmux.setting_contracts import (
20
18
  bounds_for,
21
19
  choices_for,
22
20
  )
21
+ from railmux.provider_paths import provider_path
23
22
 
24
23
 
25
24
  class ConfigError(ValueError):
@@ -49,7 +48,7 @@ class Config:
49
48
  and relative paths are made absolute before a launched Codex changes to
50
49
  its project cwd. The directory is not required to exist.
51
50
  """
52
- path = Path(self.codex_home).expanduser()
51
+ path = provider_path(self.codex_home).expanduser()
53
52
  try:
54
53
  return path.resolve()
55
54
  except OSError:
@@ -57,7 +56,7 @@ class Config:
57
56
 
58
57
 
59
58
  def default_config_path() -> Path:
60
- return _platform_config_path()
59
+ return Path.home() / ".config" / "railmux" / "config.toml"
61
60
 
62
61
 
63
62
  def _table(data: dict[str, Any], name: str) -> dict[str, Any]:
@@ -7,8 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from railmux.atomic_file import atomic_write_text
9
9
  from railmux.models import Project
10
- from railmux.path_codec import _claude_encode_path, decode
11
- from railmux.platform.config_paths import config_dir
10
+ from railmux.path_codec import decode, is_encoded_project_name
12
11
  from railmux.session_index import _looks_like_uuid, _scan_session
13
12
 
14
13
 
@@ -38,7 +37,7 @@ def _path_cache_file() -> Path:
38
37
  Decoding an encoded project name is expensive on an NFS home (see
39
38
  path_codec), and the mapping is stable, so we persist it across launches.
40
39
  """
41
- return config_dir() / "path-cache.json"
40
+ return Path.home() / ".config" / "railmux" / "path-cache.json"
42
41
 
43
42
 
44
43
  def _load_path_cache() -> dict[str, str]:
@@ -95,7 +94,7 @@ def list_projects(claude_home: Path) -> list[Project]:
95
94
  for entry in scan:
96
95
  if not entry.is_dir(follow_symlinks=False):
97
96
  continue
98
- if os.name != "nt" and not entry.name.startswith("-"):
97
+ if not is_encoded_project_name(entry.name):
99
98
  continue
100
99
  seen.add(entry.name)
101
100
 
@@ -107,16 +106,10 @@ def list_projects(claude_home: Path) -> list[Project]:
107
106
  if cached is not None and Path(cached).is_dir():
108
107
  real_path = Path(cached)
109
108
  else:
110
- decoded = None
111
- if os.name == "nt":
112
- decoded = _windows_project_path_from_transcript(
113
- Path(entry.path), entry.name
114
- )
115
- else:
116
- try:
117
- decoded = decode(entry.name)
118
- except ValueError:
119
- pass
109
+ try:
110
+ decoded = decode(entry.name)
111
+ except ValueError:
112
+ decoded = None
120
113
  # Skip projects whose original directory no longer exists on
121
114
  # this machine (deleted or moved). They can't be launched —
122
115
  # `cd` into a missing dir fails — so don't list them, and drop
@@ -163,58 +156,6 @@ def list_projects(claude_home: Path) -> list[Project]:
163
156
  return results
164
157
 
165
158
 
166
- def _windows_project_path_from_transcript(
167
- claude_dir: Path,
168
- encoded_name: str,
169
- ) -> Path | None:
170
- """Recover a Windows project cwd from provider-owned transcript metadata.
171
-
172
- Drive letters and UNC roots do not fit the POSIX path backtracker. Claude
173
- records the exact cwd in its JSONL, so validate that bounded value against
174
- the enclosing encoded directory instead of guessing a Windows path.
175
- """
176
- try:
177
- candidates = sorted(
178
- claude_dir.glob("*.jsonl"),
179
- key=lambda path: path.stat().st_mtime_ns,
180
- reverse=True,
181
- )[:8]
182
- except OSError:
183
- return None
184
- for path in candidates:
185
- try:
186
- with path.open("r", encoding="utf-8", errors="replace") as stream:
187
- for _line_number in range(64):
188
- line = stream.readline(64 * 1024 + 1)
189
- if not line:
190
- break
191
- if len(line) > 64 * 1024:
192
- continue
193
- try:
194
- record = json.loads(line)
195
- except (json.JSONDecodeError, ValueError):
196
- continue
197
- raw_cwd = record.get("cwd") if isinstance(record, dict) else None
198
- if (
199
- not isinstance(raw_cwd, str)
200
- or not raw_cwd
201
- or len(raw_cwd) > 32768
202
- or "\0" in raw_cwd
203
- ):
204
- continue
205
- candidate = Path(raw_cwd)
206
- if (
207
- candidate.is_absolute()
208
- and candidate.is_dir()
209
- and _claude_encode_path(str(candidate)).casefold()
210
- == encoded_name.casefold()
211
- ):
212
- return candidate
213
- except OSError:
214
- continue
215
- return None
216
-
217
-
218
159
  def _count_and_latest_mtime(claude_dir: Path, real_path: Path) -> tuple[int, float]:
219
160
  """Count sessions that pass every ``_scan_session`` filter and return the max
220
161
  JSONL mtime. Uses ``_scan_session`` so the count exactly matches the
@@ -738,6 +738,22 @@ class AgentDisplayTransport:
738
738
  def _with_phase(state: SwapState, phase: str) -> SwapState:
739
739
  return SwapState(**{**asdict(state), "phase": phase})
740
740
 
741
+ @staticmethod
742
+ def _fit_home_to_display(state: SwapState) -> None:
743
+ """Best-effort preserve the provider's visible geometry at home.
744
+
745
+ While a real pane is displayed, its named session contains only the
746
+ inert placeholder. Resize that detached home window to the real
747
+ pane's current size before swapping back. Otherwise background output
748
+ is formatted at the placeholder's older (often much narrower) launch
749
+ geometry and later appears as a narrow discontinuity in scrollback.
750
+
751
+ Geometry is presentation-only: failure must never weaken or abort the
752
+ identity-pinned return transaction.
753
+ """
754
+ tmux_ctl.fit_session_to_pane(
755
+ state.agent_tmux_name, state.agent_pane_id)
756
+
741
757
  def park(self, slot: AgentSlot) -> bool:
742
758
  """Move one displayed swap agent home without forgetting its slot.
743
759
 
@@ -764,6 +780,7 @@ class AgentDisplayTransport:
764
780
  parking = self._with_phase(state, "parking")
765
781
  if not _write_marker_pair(parking, slot.key):
766
782
  return False
783
+ self._fit_home_to_display(state)
767
784
  if not tmux_ctl.swap_panes(
768
785
  state.agent_pane_id, state.placeholder_pane_id):
769
786
  _write_marker_pair(state, slot.key)
@@ -857,6 +874,7 @@ class AgentDisplayTransport:
857
874
  return False
858
875
  returning = SwapState(**{**asdict(state), "phase": "returning"})
859
876
  _write_marker_pair(returning, slot.key)
877
+ self._fit_home_to_display(state)
860
878
  if not tmux_ctl.swap_panes(
861
879
  state.agent_pane_id, state.placeholder_pane_id):
862
880
  return False
@@ -0,0 +1,21 @@
1
+ """Platform-safe public console entry point."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from collections.abc import Sequence
6
+
7
+
8
+ def main(
9
+ argv: Sequence[str] | None = None,
10
+ *,
11
+ platform_name: str = os.name,
12
+ ) -> int:
13
+ """Dispatch before importing modules that require a POSIX terminal."""
14
+ if platform_name == "nt":
15
+ from railmux.windows_bootstrap import main as windows_main
16
+
17
+ return windows_main(argv)
18
+
19
+ from railmux.cli import main as posix_main
20
+
21
+ return posix_main(list(argv) if argv is not None else None)