railmux 0.3.2.dev4__tar.gz → 0.3.3.dev1__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 (194) hide show
  1. {railmux-0.3.2.dev4/src/railmux.egg-info → railmux-0.3.3.dev1}/PKG-INFO +1 -5
  2. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/pyproject.toml +0 -4
  3. railmux-0.3.3.dev1/src/railmux/__init__.py +1 -0
  4. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/cli.py +2 -40
  5. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/codex_index.py +15 -6
  6. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/config.py +1 -3
  7. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/discovery.py +7 -66
  8. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fast_display_client.py +24 -11
  9. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fast_display_server.py +35 -8
  10. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/favorites.py +1 -2
  11. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/help_workspace.py +7 -2
  12. railmux-0.3.3.dev1/src/railmux/local_clipboard.py +47 -0
  13. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/local_open.py +10 -37
  14. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/modes.py +0 -8
  15. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/mouse_manager.py +3 -7
  16. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/orphan_marker.py +14 -9
  17. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/renames.py +1 -2
  18. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/restart_state.py +12 -7
  19. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/runtime_config.py +1 -4
  20. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/scroll_manager.py +5 -3
  21. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ssh_display_diagnostics.py +5 -3
  22. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/tmux_binding_manager.py +109 -10
  23. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/tmux_ctl.py +396 -1
  24. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/app.py +393 -319
  25. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1/src/railmux.egg-info}/PKG-INFO +1 -5
  26. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux.egg-info/SOURCES.txt +2 -33
  27. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux.egg-info/requires.txt +0 -4
  28. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_cli.py +0 -15
  29. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_codex_delete.py +3 -7
  30. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_config.py +0 -1
  31. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_discovery.py +0 -24
  32. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_local_open.py +0 -26
  33. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_modes.py +0 -2
  34. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_orphan_recovery.py +2 -2
  35. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_release_workflow.py +14 -9
  36. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_runtime_config.py +0 -12
  37. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_soft_quit.py +2 -2
  38. railmux-0.3.3.dev1/tests/test_termux_local_touch.py +120 -0
  39. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_binding_manager.py +81 -4
  40. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_ctl.py +130 -0
  41. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_integration.py +159 -9
  42. railmux-0.3.3.dev1/tests/test_utf8_state.py +111 -0
  43. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_wheel_smoke.py +1 -6
  44. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tools/wheel_smoke.py +6 -12
  45. railmux-0.3.2.dev4/src/railmux/__init__.py +0 -1
  46. railmux-0.3.2.dev4/src/railmux/local_clipboard.py +0 -101
  47. railmux-0.3.2.dev4/src/railmux/mux/__init__.py +0 -5
  48. railmux-0.3.2.dev4/src/railmux/mux/backend.py +0 -59
  49. railmux-0.3.2.dev4/src/railmux/mux/tmux_backend.py +0 -161
  50. railmux-0.3.2.dev4/src/railmux/platform/__init__.py +0 -44
  51. railmux-0.3.2.dev4/src/railmux/platform/config_paths.py +0 -30
  52. railmux-0.3.2.dev4/src/railmux/platform/console.py +0 -165
  53. railmux-0.3.2.dev4/src/railmux/platform/file_security.py +0 -34
  54. railmux-0.3.2.dev4/src/railmux/platform/filelock.py +0 -42
  55. railmux-0.3.2.dev4/src/railmux/platform/multiplex.py +0 -132
  56. railmux-0.3.2.dev4/src/railmux/platform/process.py +0 -21
  57. railmux-0.3.2.dev4/src/railmux/platform/runtime_paths.py +0 -75
  58. railmux-0.3.2.dev4/src/railmux/terminal_emulator.py +0 -180
  59. railmux-0.3.2.dev4/src/railmux/winlocal/__init__.py +0 -2
  60. railmux-0.3.2.dev4/src/railmux/winlocal/backend.py +0 -897
  61. railmux-0.3.2.dev4/src/railmux/winlocal/client.py +0 -275
  62. railmux-0.3.2.dev4/src/railmux/winlocal/compositor.py +0 -254
  63. railmux-0.3.2.dev4/src/railmux/winlocal/conpty.py +0 -200
  64. railmux-0.3.2.dev4/src/railmux/winlocal/daemon.py +0 -359
  65. railmux-0.3.2.dev4/src/railmux/winlocal/history_viewer.py +0 -93
  66. railmux-0.3.2.dev4/src/railmux/winlocal/ipc.py +0 -88
  67. railmux-0.3.2.dev4/src/railmux/winlocal/session_store.py +0 -96
  68. railmux-0.3.2.dev4/src/railmux/winlocal/virtual_screen.py +0 -105
  69. railmux-0.3.2.dev4/tests/test_mux_backend.py +0 -60
  70. railmux-0.3.2.dev4/tests/test_platform.py +0 -248
  71. railmux-0.3.2.dev4/tests/test_winlocal_backend.py +0 -176
  72. railmux-0.3.2.dev4/tests/test_winlocal_compositor.py +0 -48
  73. railmux-0.3.2.dev4/tests/test_winlocal_conpty.py +0 -60
  74. railmux-0.3.2.dev4/tests/test_winlocal_daemon.py +0 -150
  75. railmux-0.3.2.dev4/tests/test_winlocal_history_viewer.py +0 -43
  76. railmux-0.3.2.dev4/tests/test_winlocal_ipc.py +0 -39
  77. railmux-0.3.2.dev4/tests/test_winlocal_real_conpty.py +0 -61
  78. railmux-0.3.2.dev4/tests/test_winlocal_session_store.py +0 -42
  79. railmux-0.3.2.dev4/tests/test_winlocal_virtual_screen.py +0 -62
  80. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/LICENSE +0 -0
  81. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/MANIFEST.in +0 -0
  82. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/README.md +0 -0
  83. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/setup.cfg +0 -0
  84. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/__main__.py +0 -0
  85. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/atomic_file.py +0 -0
  86. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/background_index.py +0 -0
  87. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/config_cli.py +0 -0
  88. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/diagnostics.py +0 -0
  89. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/display_transport.py +0 -0
  90. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fast_display_history.py +0 -0
  91. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fast_display_input.py +0 -0
  92. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fast_display_protocol.py +0 -0
  93. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/fuzzy.py +0 -0
  94. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/launcher.py +0 -0
  95. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/legacy_sessions.py +0 -0
  96. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/models.py +0 -0
  97. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/pane_surface.py +0 -0
  98. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/path_codec.py +0 -0
  99. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/remote_config.py +0 -0
  100. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/scroll_agent.py +0 -0
  101. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/selection_isolation.py +0 -0
  102. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/self_update.py +0 -0
  103. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/session_cache.py +0 -0
  104. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/session_index.py +0 -0
  105. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/setting_contracts.py +0 -0
  106. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/settings.py +0 -0
  107. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ssh_args.py +0 -0
  108. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ssh_compat.py +0 -0
  109. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ssh_doctor.py +0 -0
  110. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/system_deps.py +0 -0
  111. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/terminal_status.py +0 -0
  112. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/tmux_health.py +0 -0
  113. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/tmux_server.py +0 -0
  114. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/tool_panes.py +0 -0
  115. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/transcript.py +0 -0
  116. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/__init__.py +0 -0
  117. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/_widgets.py +0 -0
  118. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/keymap.py +0 -0
  119. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/modals.py +0 -0
  120. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/projects_pane.py +0 -0
  121. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/running_pane.py +0 -0
  122. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/sessions_pane.py +0 -0
  123. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/sidebar.py +0 -0
  124. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/statusbar.py +0 -0
  125. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux/ui/workspace.py +0 -0
  126. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux.egg-info/dependency_links.txt +0 -0
  127. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux.egg-info/entry_points.txt +0 -0
  128. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/src/railmux.egg-info/top_level.txt +0 -0
  129. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_agent_workspace.py +0 -0
  130. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_atomic_file.py +0 -0
  131. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_background_index.py +0 -0
  132. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_codex_index.py +0 -0
  133. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_codex_lineage.py +0 -0
  134. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_codex_transcript.py +0 -0
  135. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_config_cli.py +0 -0
  136. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_denested_transport_benchmark.py +0 -0
  137. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_diagnostics.py +0 -0
  138. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_display_transport.py +0 -0
  139. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_fast_full_window.py +0 -0
  140. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_focus_highlight.py +0 -0
  141. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_fullscreen_binding.py +0 -0
  142. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_fuzzy.py +0 -0
  143. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_help_agent.py +0 -0
  144. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_help_workspace.py +0 -0
  145. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_history_cleanup.py +0 -0
  146. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_keymap.py +0 -0
  147. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_launcher.py +0 -0
  148. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_layout_profile.py +0 -0
  149. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_legacy_sessions.py +0 -0
  150. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_local_clipboard.py +0 -0
  151. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_modals.py +0 -0
  152. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_mode_selection.py +0 -0
  153. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_mouse_manager.py +0 -0
  154. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_new_project.py +0 -0
  155. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_options.py +0 -0
  156. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_pane_surface.py +0 -0
  157. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_path_codec.py +0 -0
  158. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_projects_pane.py +0 -0
  159. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_release_notes.py +0 -0
  160. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_remote_config.py +0 -0
  161. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_renames.py +0 -0
  162. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_restart_state.py +0 -0
  163. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_running_pane.py +0 -0
  164. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_running_sort.py +0 -0
  165. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_scroll_agent.py +0 -0
  166. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_scroll_manager.py +0 -0
  167. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_selection_isolation.py +0 -0
  168. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_self_update.py +0 -0
  169. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_session_cache.py +0 -0
  170. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_session_index.py +0 -0
  171. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_sessions_pane.py +0 -0
  172. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_setting_contracts.py +0 -0
  173. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_settings.py +0 -0
  174. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_sidebar.py +0 -0
  175. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_ssh_args.py +0 -0
  176. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_ssh_compat.py +0 -0
  177. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_ssh_display_benchmark.py +0 -0
  178. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_ssh_display_diagnostics.py +0 -0
  179. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_ssh_doctor.py +0 -0
  180. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_status.py +0 -0
  181. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_statusbar.py +0 -0
  182. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_system_deps.py +0 -0
  183. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_terminal_size.py +0 -0
  184. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_terminal_status.py +0 -0
  185. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_health.py +0 -0
  186. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_server.py +0 -0
  187. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tmux_status.py +0 -0
  188. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_tool_panes.py +0 -0
  189. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_transcript.py +0 -0
  190. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_web_demo_recorder.py +0 -0
  191. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_widgets.py +0 -0
  192. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tests/test_yolo_prompt.py +0 -0
  193. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tools/denested_transport_benchmark.py +0 -0
  194. {railmux-0.3.2.dev4 → railmux-0.3.3.dev1}/tools/release_notes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.3.2.dev4
3
+ Version: 0.3.3.dev1
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
@@ -14,14 +14,12 @@ Classifier: Environment :: Console :: Curses
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Operating System :: POSIX :: Linux
16
16
  Classifier: Operating System :: MacOS
17
- Classifier: Operating System :: Microsoft :: Windows
18
17
  Classifier: Programming Language :: Python :: 3
19
18
  Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Programming Language :: Python :: 3.14
25
23
  Classifier: Topic :: Software Development
26
24
  Classifier: Topic :: Terminals
27
25
  Classifier: Topic :: Utilities
@@ -32,8 +30,6 @@ Requires-Dist: packaging>=23.0
32
30
  Requires-Dist: tomlkit<1,>=0.13.2
33
31
  Requires-Dist: urwid>=2.6.16
34
32
  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"
37
33
  Provides-Extra: ssh
38
34
  Requires-Dist: pyte<0.9,>=0.8.2; extra == "ssh"
39
35
  Provides-Extra: dev
@@ -20,14 +20,12 @@ classifiers = [
20
20
  "Intended Audience :: Developers",
21
21
  "Operating System :: POSIX :: Linux",
22
22
  "Operating System :: MacOS",
23
- "Operating System :: Microsoft :: Windows",
24
23
  "Programming Language :: Python :: 3",
25
24
  "Programming Language :: Python :: 3.9",
26
25
  "Programming Language :: Python :: 3.10",
27
26
  "Programming Language :: Python :: 3.11",
28
27
  "Programming Language :: Python :: 3.12",
29
28
  "Programming Language :: Python :: 3.13",
30
- "Programming Language :: Python :: 3.14",
31
29
  "Topic :: Software Development",
32
30
  "Topic :: Terminals",
33
31
  "Topic :: Utilities",
@@ -37,8 +35,6 @@ dependencies = [
37
35
  "tomlkit>=0.13.2,<1",
38
36
  "urwid>=2.6.16",
39
37
  "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'",
42
38
  ]
43
39
 
44
40
  [project.optional-dependencies]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.3.dev1"
@@ -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,15 +13,12 @@ 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,
@@ -460,7 +457,9 @@ class CodexIndex:
460
457
  try:
461
458
  info = path.lstat()
462
459
  if (
463
- not private_regular_file(info)
460
+ not stat.S_ISREG(info.st_mode)
461
+ or info.st_uid != os.getuid()
462
+ or info.st_mode & 0o077
464
463
  or info.st_size > _PERSISTENT_CACHE_MAX_BYTES
465
464
  ):
466
465
  return
@@ -515,7 +514,17 @@ class CodexIndex:
515
514
  path = self._cache_path
516
515
  if path is None:
517
516
  return False
518
- return prepare_private_directory(path.parent)
517
+ parent = path.parent
518
+ try:
519
+ parent.mkdir(mode=0o700, parents=True, exist_ok=True)
520
+ info = parent.lstat()
521
+ if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid():
522
+ return False
523
+ if info.st_mode & 0o077:
524
+ os.chmod(parent, stat.S_IMODE(info.st_mode) & ~0o077)
525
+ return True
526
+ except OSError:
527
+ return False
519
528
 
520
529
  def _save_persistent_cache(self) -> None:
521
530
  path = self._cache_path
@@ -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
@@ -57,7 +55,7 @@ class Config:
57
55
 
58
56
 
59
57
  def default_config_path() -> Path:
60
- return _platform_config_path()
58
+ return Path.home() / ".config" / "railmux" / "config.toml"
61
59
 
62
60
 
63
61
  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
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 entry.name.startswith("-"):
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
@@ -14,12 +14,15 @@ import base64
14
14
  import json
15
15
  import os
16
16
  import re
17
+ import select
17
18
  import selectors
18
19
  import shlex
19
20
  import shutil
20
21
  import subprocess
21
22
  import sys
23
+ import termios
22
24
  import time
25
+ import tty
23
26
  from collections.abc import Callable
24
27
  from dataclasses import dataclass
25
28
  from enum import Enum
@@ -78,8 +81,6 @@ from railmux.fast_display_protocol import (
78
81
  encode_path_open_request,
79
82
  encode_resize,
80
83
  )
81
- from railmux.platform.console import RawConsole
82
- from railmux.platform.multiplex import PortableSelector, wait_readable
83
84
  from railmux.pane_surface import render_startup_surface
84
85
  from railmux.ssh_compat import CompatibilityFacts, decide as decide_compatibility
85
86
  from railmux.ssh_args import AppendSshArgument, ExtendSshArguments
@@ -319,7 +320,7 @@ def await_remote_attach_status(
319
320
  readers = [process.stdout.fileno()]
320
321
  if cancel_fd is not None:
321
322
  readers.append(cancel_fd)
322
- readable = wait_readable(readers, remaining)
323
+ readable, _writable, _exceptional = select.select(readers, [], [], remaining)
323
324
  if not readable:
324
325
  return RemoteAttachKind.TIMEOUT
325
326
  if cancel_fd is not None and cancel_fd in readable:
@@ -415,7 +416,7 @@ def await_remote_startup(
415
416
  readers = [process.stdout.fileno()]
416
417
  if cancel_fd is not None:
417
418
  readers.append(cancel_fd)
418
- readable = wait_readable(readers, remaining)
419
+ readable, _writable, _exceptional = select.select(readers, [], [], remaining)
419
420
  if not readable:
420
421
  return RemoteStartup(RemoteStartKind.TIMEOUT)
421
422
  if cancel_fd is not None and cancel_fd in readable:
@@ -602,8 +603,20 @@ def _is_soft_keyboard_projection(
602
603
  )
603
604
 
604
605
 
605
- class RawTerminal(RawConsole):
606
- """Backward-compatible name for the platform raw-console port."""
606
+ class RawTerminal:
607
+ def __init__(self, fd: int) -> None:
608
+ self.fd = fd
609
+ self.saved: Optional[list[object]] = None
610
+
611
+ def __enter__(self) -> "RawTerminal":
612
+ self.saved = termios.tcgetattr(self.fd)
613
+ tty.setraw(self.fd)
614
+ return self
615
+
616
+ def __exit__(self, *_exc: object) -> None:
617
+ if self.saved is not None:
618
+ termios.tcsetattr(self.fd, termios.TCSADRAIN, self.saved)
619
+ self.saved = None
607
620
 
608
621
 
609
622
  class TerminalSurface:
@@ -1877,7 +1890,9 @@ def _wait_reconnect_delay(delay: float, cancel_fd: int) -> None:
1877
1890
  remaining = deadline - time.monotonic()
1878
1891
  if remaining <= 0:
1879
1892
  return
1880
- readable = wait_readable([cancel_fd], remaining)
1893
+ readable, _writable, _exceptional = select.select(
1894
+ [cancel_fd], [], [], remaining
1895
+ )
1881
1896
  if readable:
1882
1897
  _consume_reconnect_input(cancel_fd)
1883
1898
 
@@ -2398,7 +2413,7 @@ def run(args: argparse.Namespace) -> int:
2398
2413
  touch_keyboard = TermuxTouchKeyboard(
2399
2414
  enabled=not args.no_mouse and termux_touch
2400
2415
  )
2401
- selector = PortableSelector()
2416
+ selector = selectors.DefaultSelector()
2402
2417
  selector.register(process.stdout.fileno(), selectors.EVENT_READ, "remote")
2403
2418
  selector.register(sys.stdin.fileno(), selectors.EVENT_READ, "local")
2404
2419
  started = time.monotonic()
@@ -2943,9 +2958,7 @@ def run(args: argparse.Namespace) -> int:
2943
2958
  )
2944
2959
  history_info_until = time.monotonic() + _HISTORY_INFO_SECONDS
2945
2960
  try:
2946
- with RawTerminal(
2947
- sys.stdin.fileno(), output_fd=sys.stdout.fileno()
2948
- ):
2961
+ with RawTerminal(sys.stdin.fileno()):
2949
2962
  while True:
2950
2963
  observed_size = os.get_terminal_size(sys.stdout.fileno())
2951
2964
  if observed_size != local_size:
@@ -83,11 +83,6 @@ from railmux.fast_display_protocol import (
83
83
  encode_path_open_result,
84
84
  encode_update,
85
85
  )
86
- from railmux.terminal_emulator import (
87
- extended_pyte as _shared_extended_pyte,
88
- render_rows as _shared_render_rows,
89
- terminal_modes_for_screen as _shared_terminal_modes_for_screen,
90
- )
91
86
  from railmux.ui.workspace import (
92
87
  COMPACT_ENTER_HEIGHT,
93
88
  COMPACT_ENTER_WIDTH,
@@ -376,7 +371,9 @@ def _build_extended_pyte(pyte: object) -> object:
376
371
 
377
372
  def _extended_pyte(pyte: object) -> object:
378
373
  """Idempotently adapt one imported pyte module for tmux's VT stream."""
379
- return _shared_extended_pyte(pyte)
374
+ if getattr(pyte, "_railmux_extended", False):
375
+ return pyte
376
+ return _build_extended_pyte(pyte)
380
377
 
381
378
 
382
379
  @dataclass(frozen=True)
@@ -1890,12 +1887,42 @@ def _style_key(char: object) -> tuple[object, ...]:
1890
1887
 
1891
1888
  def render_rows(screen: object) -> tuple[bytes, ...]:
1892
1889
  """Render independently paintable rows with allowlisted SGR controls."""
1893
- return _shared_render_rows(screen)
1890
+ rendered_rows: list[bytes] = []
1891
+ for row_index in range(screen.lines):
1892
+ rendered = [b"\033[0m"]
1893
+ previous_style: tuple[object, ...] | None = None
1894
+ row = screen.buffer[row_index]
1895
+ for column in range(screen.columns):
1896
+ char = row[column]
1897
+ style = _style_key(char)
1898
+ if style != previous_style:
1899
+ rendered.append(_style(char))
1900
+ previous_style = style
1901
+ # pyte represents the second cell of a wide glyph as empty data;
1902
+ # the real terminal already advances two columns for the glyph.
1903
+ if char.data:
1904
+ safe_data = "".join(
1905
+ value
1906
+ if value >= " "
1907
+ and value != "\x7f"
1908
+ and not "\x80" <= value <= "\x9f"
1909
+ else "�"
1910
+ for value in char.data
1911
+ )
1912
+ rendered.append(safe_data.encode("utf-8", errors="replace"))
1913
+ rendered.append(b"\033[0m")
1914
+ rendered_rows.append(b"".join(rendered))
1915
+ return tuple(rendered_rows)
1894
1916
 
1895
1917
 
1896
1918
  def terminal_modes_for_screen(screen: object) -> TerminalMode:
1897
1919
  """Project pyte's private-mode set onto the bounded v14 wire allowlist."""
1898
- return _shared_terminal_modes_for_screen(screen)
1920
+ terminal_modes = TerminalMode.NONE
1921
+ if 2004 << 5 in screen.mode:
1922
+ terminal_modes |= TerminalMode.BRACKETED_PASTE
1923
+ if 1004 << 5 in screen.mode:
1924
+ terminal_modes |= TerminalMode.FOCUS_EVENTS
1925
+ return terminal_modes
1899
1926
 
1900
1927
 
1901
1928
  def _child_exited(pid: int) -> bool:
@@ -10,11 +10,10 @@ import json
10
10
  from pathlib import Path
11
11
 
12
12
  from railmux.atomic_file import atomic_write_text
13
- from railmux.platform.config_paths import config_dir
14
13
 
15
14
 
16
15
  def _favorites_path() -> Path:
17
- return config_dir() / "favorites.json"
16
+ return Path.home() / ".config" / "railmux" / "favorites.json"
18
17
 
19
18
 
20
19
  class Favorites:
@@ -1,12 +1,12 @@
1
1
  """Materialise the private, read-only context used by Ask Railmux."""
2
2
  from __future__ import annotations
3
3
 
4
+ import os
4
5
  from importlib import metadata
5
6
  from pathlib import Path
6
7
 
7
8
  from railmux import __version__
8
9
  from railmux.atomic_file import atomic_write_text
9
- from railmux.platform.config_paths import data_dir
10
10
 
11
11
 
12
12
  _FALLBACK_GUIDE = """# Railmux
@@ -35,7 +35,12 @@ permissions.
35
35
 
36
36
  def help_workspace_path() -> Path:
37
37
  """Return Railmux's per-user help workspace, following the XDG spec."""
38
- return data_dir() / "help"
38
+ raw = os.environ.get("XDG_DATA_HOME")
39
+ if raw:
40
+ candidate = Path(raw).expanduser()
41
+ if candidate.is_absolute():
42
+ return candidate / "railmux" / "help"
43
+ return Path.home() / ".local" / "share" / "railmux" / "help"
39
44
 
40
45
 
41
46
  def _source_readme() -> str | None:
@@ -0,0 +1,47 @@
1
+ """Best-effort native clipboard writers for explicit user copy actions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+
10
+
11
+ def command() -> tuple[str, ...] | None:
12
+ """Return a trusted platform clipboard command, if one is available."""
13
+ if sys.platform == "darwin":
14
+ if executable := shutil.which("pbcopy"):
15
+ return (executable,)
16
+ return None
17
+ if os.environ.get("WAYLAND_DISPLAY"):
18
+ if executable := shutil.which("wl-copy"):
19
+ return (executable,)
20
+ if os.environ.get("DISPLAY"):
21
+ if executable := shutil.which("xclip"):
22
+ return (executable, "-selection", "clipboard")
23
+ if executable := shutil.which("xsel"):
24
+ return (executable, "--clipboard", "--input")
25
+ if os.environ.get("WSL_DISTRO_NAME") or os.environ.get("WSL_INTEROP"):
26
+ if executable := shutil.which("clip.exe"):
27
+ return (executable,)
28
+ return None
29
+
30
+
31
+ def copy(data: bytes) -> bool:
32
+ """Write bytes to the local OS clipboard without invoking a shell."""
33
+ writer = command()
34
+ if writer is None:
35
+ return False
36
+ try:
37
+ result = subprocess.run(
38
+ writer,
39
+ input=data,
40
+ stdout=subprocess.DEVNULL,
41
+ stderr=subprocess.DEVNULL,
42
+ timeout=2.0,
43
+ check=False,
44
+ )
45
+ except (OSError, subprocess.SubprocessError):
46
+ return False
47
+ return result.returncode == 0
@@ -87,17 +87,14 @@ class OpenResult:
87
87
 
88
88
 
89
89
  def _detached_popen(argv: Sequence[str]) -> None:
90
- kwargs = {
91
- "stdin": subprocess.DEVNULL,
92
- "stdout": subprocess.DEVNULL,
93
- "stderr": subprocess.DEVNULL,
94
- "close_fds": True,
95
- }
96
- if os.name == "nt":
97
- kwargs["creationflags"] = 0x00000008 | 0x00000200
98
- else:
99
- kwargs["start_new_session"] = True
100
- subprocess.Popen(list(argv), **kwargs)
90
+ subprocess.Popen(
91
+ list(argv),
92
+ stdin=subprocess.DEVNULL,
93
+ stdout=subprocess.DEVNULL,
94
+ stderr=subprocess.DEVNULL,
95
+ start_new_session=True,
96
+ close_fds=True,
97
+ )
101
98
 
102
99
 
103
100
  def _url_opener() -> tuple[str, ...] | None:
@@ -120,17 +117,6 @@ def _url_opener() -> tuple[str, ...] | None:
120
117
 
121
118
  def open_url(url: str) -> OpenResult:
122
119
  """Open one already validated HTTP(S) URL without invoking a shell."""
123
- if os.name == "nt":
124
- try:
125
- os.startfile(url)
126
- except OSError:
127
- return OpenResult(
128
- False,
129
- "Could not open local browser · URL copied",
130
- "warning",
131
- url.encode("utf-8"),
132
- )
133
- return OpenResult(True, "Opened URL in local browser", "success")
134
120
  opener = _url_opener()
135
121
  if opener is None:
136
122
  return OpenResult(
@@ -274,13 +260,6 @@ def _wsl_terminal_argv(ssh_argv: Sequence[str]) -> tuple[str, ...] | None:
274
260
  )
275
261
 
276
262
 
277
- def _windows_terminal_argv(ssh_argv: Sequence[str]) -> tuple[str, ...] | None:
278
- terminal = shutil.which("wt.exe") or shutil.which("wt")
279
- if terminal is None:
280
- return None
281
- return (terminal, "new-tab", "--", *ssh_argv)
282
-
283
-
284
263
  def open_remote_path(
285
264
  destination: str,
286
265
  *,
@@ -301,14 +280,8 @@ def open_remote_path(
301
280
  line=line,
302
281
  column=column,
303
282
  )
304
- command = (
305
- subprocess.list2cmdline(ssh_argv)
306
- if os.name == "nt"
307
- else shlex.join(ssh_argv)
308
- )
309
- if os.name == "nt":
310
- terminal_argv = _windows_terminal_argv(ssh_argv)
311
- elif sys.platform == "darwin":
283
+ command = shlex.join(ssh_argv)
284
+ if sys.platform == "darwin":
312
285
  terminal_argv = _mac_terminal_argv(command)
313
286
  elif os.environ.get("WSL_DISTRO_NAME") or os.environ.get("WSL_INTEROP"):
314
287
  terminal_argv = _wsl_terminal_argv(ssh_argv)
@@ -80,14 +80,6 @@ class ModeRegistry:
80
80
  None,
81
81
  )
82
82
 
83
- def for_session_type(self, session_type: str) -> AgentMode | None:
84
- """Resolve launch ownership without consulting mutable UI mode state."""
85
- return next(
86
- (mode for mode in self._ordered
87
- if mode.session_type == session_type),
88
- None,
89
- )
90
-
91
83
 
92
84
  CLAUDE_MODE = AgentMode(
93
85
  key="claude",