railmux 0.1.0__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 (69) hide show
  1. railmux-0.1.0/LICENSE +21 -0
  2. railmux-0.1.0/PKG-INFO +152 -0
  3. railmux-0.1.0/README.md +121 -0
  4. railmux-0.1.0/pyproject.toml +57 -0
  5. railmux-0.1.0/setup.cfg +4 -0
  6. railmux-0.1.0/src/railmux/__init__.py +1 -0
  7. railmux-0.1.0/src/railmux/__main__.py +4 -0
  8. railmux-0.1.0/src/railmux/atomic_file.py +38 -0
  9. railmux-0.1.0/src/railmux/cli.py +78 -0
  10. railmux-0.1.0/src/railmux/codex_index.py +333 -0
  11. railmux-0.1.0/src/railmux/config.py +39 -0
  12. railmux-0.1.0/src/railmux/discovery.py +222 -0
  13. railmux-0.1.0/src/railmux/favorites.py +59 -0
  14. railmux-0.1.0/src/railmux/fuzzy.py +28 -0
  15. railmux-0.1.0/src/railmux/launcher.py +41 -0
  16. railmux-0.1.0/src/railmux/models.py +53 -0
  17. railmux-0.1.0/src/railmux/path_codec.py +282 -0
  18. railmux-0.1.0/src/railmux/renames.py +72 -0
  19. railmux-0.1.0/src/railmux/scroll_agent.py +147 -0
  20. railmux-0.1.0/src/railmux/scroll_manager.py +233 -0
  21. railmux-0.1.0/src/railmux/session_cache.py +120 -0
  22. railmux-0.1.0/src/railmux/session_index.py +201 -0
  23. railmux-0.1.0/src/railmux/tmux_ctl.py +745 -0
  24. railmux-0.1.0/src/railmux/transcript.py +254 -0
  25. railmux-0.1.0/src/railmux/ui/__init__.py +0 -0
  26. railmux-0.1.0/src/railmux/ui/_widgets.py +183 -0
  27. railmux-0.1.0/src/railmux/ui/app.py +2746 -0
  28. railmux-0.1.0/src/railmux/ui/keymap.py +133 -0
  29. railmux-0.1.0/src/railmux/ui/modals.py +594 -0
  30. railmux-0.1.0/src/railmux/ui/projects_pane.py +163 -0
  31. railmux-0.1.0/src/railmux/ui/running_pane.py +148 -0
  32. railmux-0.1.0/src/railmux/ui/sessions_pane.py +294 -0
  33. railmux-0.1.0/src/railmux/ui/statusbar.py +237 -0
  34. railmux-0.1.0/src/railmux.egg-info/PKG-INFO +152 -0
  35. railmux-0.1.0/src/railmux.egg-info/SOURCES.txt +67 -0
  36. railmux-0.1.0/src/railmux.egg-info/dependency_links.txt +1 -0
  37. railmux-0.1.0/src/railmux.egg-info/entry_points.txt +2 -0
  38. railmux-0.1.0/src/railmux.egg-info/requires.txt +6 -0
  39. railmux-0.1.0/src/railmux.egg-info/top_level.txt +1 -0
  40. railmux-0.1.0/tests/test_atomic_file.py +32 -0
  41. railmux-0.1.0/tests/test_cli.py +69 -0
  42. railmux-0.1.0/tests/test_codex_index.py +410 -0
  43. railmux-0.1.0/tests/test_codex_transcript.py +197 -0
  44. railmux-0.1.0/tests/test_config.py +29 -0
  45. railmux-0.1.0/tests/test_discovery.py +133 -0
  46. railmux-0.1.0/tests/test_focus_highlight.py +455 -0
  47. railmux-0.1.0/tests/test_fullscreen_binding.py +124 -0
  48. railmux-0.1.0/tests/test_fuzzy.py +99 -0
  49. railmux-0.1.0/tests/test_history_cleanup.py +74 -0
  50. railmux-0.1.0/tests/test_keymap.py +152 -0
  51. railmux-0.1.0/tests/test_launcher.py +56 -0
  52. railmux-0.1.0/tests/test_modals.py +261 -0
  53. railmux-0.1.0/tests/test_path_codec.py +227 -0
  54. railmux-0.1.0/tests/test_projects_pane.py +261 -0
  55. railmux-0.1.0/tests/test_renames.py +173 -0
  56. railmux-0.1.0/tests/test_running_pane.py +146 -0
  57. railmux-0.1.0/tests/test_running_sort.py +73 -0
  58. railmux-0.1.0/tests/test_scroll_agent.py +65 -0
  59. railmux-0.1.0/tests/test_scroll_manager.py +126 -0
  60. railmux-0.1.0/tests/test_session_cache.py +195 -0
  61. railmux-0.1.0/tests/test_session_index.py +109 -0
  62. railmux-0.1.0/tests/test_sessions_pane.py +412 -0
  63. railmux-0.1.0/tests/test_soft_quit.py +513 -0
  64. railmux-0.1.0/tests/test_status.py +418 -0
  65. railmux-0.1.0/tests/test_statusbar.py +333 -0
  66. railmux-0.1.0/tests/test_tmux_ctl.py +321 -0
  67. railmux-0.1.0/tests/test_tmux_status.py +244 -0
  68. railmux-0.1.0/tests/test_transcript.py +305 -0
  69. railmux-0.1.0/tests/test_widgets.py +271 -0
railmux-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saugat Regmi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
railmux-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: railmux
3
+ Version: 0.1.0
4
+ Summary: Terminal UI to navigate, resume, and start Claude Code sessions across all your projects
5
+ Author-email: Rightglow <zhang.taian@foxmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Rightglow/Railmux
8
+ Project-URL: Repository, https://github.com/Rightglow/Railmux
9
+ Project-URL: Issues, https://github.com/Rightglow/Railmux/issues
10
+ Keywords: claude,claude-code,tmux,tui,session-manager,terminal
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console :: Curses
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development
20
+ Classifier: Topic :: Terminals
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.12
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: urwid>=2.6.16
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: build>=1.2; extra == "dev"
29
+ Requires-Dist: twine>=5.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # railmux — Claude Code session manager
33
+
34
+ A terminal UI to navigate, resume, and start [Claude Code](https://claude.com/claude-code) sessions across all your projects from one place. railmux lives in the left pane of a tmux window; the right pane shows the currently-active claude. Each claude session runs as its own detached tmux session in the background, so switching between sessions preserves all in-progress work — no responses or tool calls are interrupted.
35
+
36
+ > **This is a fork** of [regmi-saugat/railmux](https://github.com/regmi-saugat/railmux) (v0.1.5), developed with agent-assisted programming using [Claude Code](https://claude.ai/claude-code).
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pip install railmux
42
+ ```
43
+
44
+ Requires Python 3.12+, `tmux`, and `less` on `PATH`.
45
+
46
+ ## Run
47
+
48
+ ```bash
49
+ railmux
50
+ ```
51
+
52
+ If you're not already inside a tmux session, railmux will launch one automatically. The most recent project is auto-selected on startup.
53
+
54
+ ## Keys
55
+
56
+ ### Navigation
57
+
58
+ | Key | Action |
59
+ |-----|--------|
60
+ | `↑` / `↓` | Move selection within the focused pane |
61
+ | `Tab` / `Shift-Tab` | Cycle focus through Projects, Sessions, Running panes |
62
+ | `Esc` | Move focus up: Running → Sessions → Projects |
63
+ | `/` | Filter the focused pane by name |
64
+
65
+ ### Session actions
66
+
67
+ | Key | Action |
68
+ |-----|--------|
69
+ | `Enter` | Resume or start the selected session |
70
+ | `n` | Start a fresh claude session in the current project |
71
+ | `i` | Popup with session details |
72
+ | `r` | Rename the focused session |
73
+ | `s` | Toggle star — starred sessions pinned to top with ⭐ |
74
+ | `k` | Kill the running Claude process (keeps session file) |
75
+ | `d` | Delete the focused session (prompts for confirmation) |
76
+ | `t` | Open a terminal in the active project directory |
77
+ | `F9` | Fullscreen the agent pane (toggle) for clean text selection |
78
+ | `?` | Full help popup with all keybindings |
79
+ | `q` or `Ctrl-C` | Quit with confirmation |
80
+
81
+ ### Mouse
82
+
83
+ | Action | Effect |
84
+ |--------|--------|
85
+ | Left-click (non-running) | Preview session history in the right pane |
86
+ | Left-click (running) | Attach to the running session (focus stays left) |
87
+ | Double-click | Open/attach and move focus to the right pane |
88
+ | Right-click | Context menu (Open, Info, Rename, Star, Kill, Term, Delete) |
89
+
90
+ ## History preview
91
+
92
+ Left-click a non-running session to view its conversation history in the right pane without starting Claude. The transcript is colour-coded (user = cyan, assistant = green, tool use = yellow) and displayed via `less`. Press `q` to exit — the right pane restores whatever was there before. Double-click to skip the preview and open the session directly.
93
+
94
+ Clicking a running session attaches to it immediately (focus stays left so you can keep browsing). Double-clicking steals focus to the right pane for both running and non-running sessions.
95
+
96
+ ## Status indicators
97
+
98
+ Each session shows a coloured ● reflecting its current state:
99
+
100
+ - **Green** — idle (assistant last responded normally)
101
+ - **Yellow** — busy (assistant is processing)
102
+ - **Red** — blocked (waiting for tool approval)
103
+
104
+ ## How it works
105
+
106
+ `railmux` reads `~/.claude/projects/*` (Claude's per-project session history) and lists everything. Pressing `Enter` on a session does two things: (1) if a detached tmux session running `claude --resume <id>` doesn't already exist, railmux creates one with `tmux new-session -d`; (2) railmux's right pane runs `tmux attach -t cc-<id>` so you see and interact with that claude. Switching sessions just respawns the right pane to attach to a different background tmux session — the detached claudes keep running with all their state intact.
107
+
108
+ ## SSH / remote use
109
+
110
+ railmux works over SSH and benefits from a few tweaks for responsiveness and scrollback:
111
+
112
+ **Server** (`~/.tmux.conf` on the remote machine):
113
+
114
+ ```tmux
115
+ set -sg escape-time 0 # eliminate delay after Escape key
116
+ set -g history-limit 10000 # generous scrollback per pane
117
+ ```
118
+
119
+ **Client** (`~/.ssh/config` on your local machine):
120
+
121
+ ```
122
+ Host your-server
123
+ Compression yes # smoother tmux pane scrolling over SSH
124
+ ```
125
+
126
+ ## Copying text from the Claude pane
127
+
128
+ Selecting text is awkward under tmux: the sidebar and Claude share the screen,
129
+ and over SSH your clipboard lives on the *local* machine.
130
+
131
+ - **OSC 52** (iTerm2, kitty, WezTerm, Alacritty, foot, Windows Terminal):
132
+ drag-select in the Claude pane — copies to local clipboard automatically,
133
+ even over SSH. No Shift needed. (iTerm2: enable *Settings → General →
134
+ Selection → "Applications in terminal may access clipboard"*.)
135
+ - **Without OSC 52** (Terminal.app, etc.): **F9** to fullscreen the agent →
136
+ **Shift-drag** to select → `Cmd/C` to copy → **F9** to return.
137
+ `Ctrl-B z` also toggles fullscreen (built into tmux) but zooms whichever pane
138
+ has focus, so it may fullscreen the sidebar instead.
139
+
140
+ ## Configuration
141
+
142
+ Optional config at `~/.config/railmux/config.toml`:
143
+
144
+ ```toml
145
+ [claude]
146
+ # Path to the claude binary (default: "claude")
147
+ binary = "claude"
148
+
149
+ [live]
150
+ # How often to refresh the session list (ms)
151
+ poll_interval_ms = 1000
152
+ ```
@@ -0,0 +1,121 @@
1
+ # railmux — Claude Code session manager
2
+
3
+ A terminal UI to navigate, resume, and start [Claude Code](https://claude.com/claude-code) sessions across all your projects from one place. railmux lives in the left pane of a tmux window; the right pane shows the currently-active claude. Each claude session runs as its own detached tmux session in the background, so switching between sessions preserves all in-progress work — no responses or tool calls are interrupted.
4
+
5
+ > **This is a fork** of [regmi-saugat/railmux](https://github.com/regmi-saugat/railmux) (v0.1.5), developed with agent-assisted programming using [Claude Code](https://claude.ai/claude-code).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install railmux
11
+ ```
12
+
13
+ Requires Python 3.12+, `tmux`, and `less` on `PATH`.
14
+
15
+ ## Run
16
+
17
+ ```bash
18
+ railmux
19
+ ```
20
+
21
+ If you're not already inside a tmux session, railmux will launch one automatically. The most recent project is auto-selected on startup.
22
+
23
+ ## Keys
24
+
25
+ ### Navigation
26
+
27
+ | Key | Action |
28
+ |-----|--------|
29
+ | `↑` / `↓` | Move selection within the focused pane |
30
+ | `Tab` / `Shift-Tab` | Cycle focus through Projects, Sessions, Running panes |
31
+ | `Esc` | Move focus up: Running → Sessions → Projects |
32
+ | `/` | Filter the focused pane by name |
33
+
34
+ ### Session actions
35
+
36
+ | Key | Action |
37
+ |-----|--------|
38
+ | `Enter` | Resume or start the selected session |
39
+ | `n` | Start a fresh claude session in the current project |
40
+ | `i` | Popup with session details |
41
+ | `r` | Rename the focused session |
42
+ | `s` | Toggle star — starred sessions pinned to top with ⭐ |
43
+ | `k` | Kill the running Claude process (keeps session file) |
44
+ | `d` | Delete the focused session (prompts for confirmation) |
45
+ | `t` | Open a terminal in the active project directory |
46
+ | `F9` | Fullscreen the agent pane (toggle) for clean text selection |
47
+ | `?` | Full help popup with all keybindings |
48
+ | `q` or `Ctrl-C` | Quit with confirmation |
49
+
50
+ ### Mouse
51
+
52
+ | Action | Effect |
53
+ |--------|--------|
54
+ | Left-click (non-running) | Preview session history in the right pane |
55
+ | Left-click (running) | Attach to the running session (focus stays left) |
56
+ | Double-click | Open/attach and move focus to the right pane |
57
+ | Right-click | Context menu (Open, Info, Rename, Star, Kill, Term, Delete) |
58
+
59
+ ## History preview
60
+
61
+ Left-click a non-running session to view its conversation history in the right pane without starting Claude. The transcript is colour-coded (user = cyan, assistant = green, tool use = yellow) and displayed via `less`. Press `q` to exit — the right pane restores whatever was there before. Double-click to skip the preview and open the session directly.
62
+
63
+ Clicking a running session attaches to it immediately (focus stays left so you can keep browsing). Double-clicking steals focus to the right pane for both running and non-running sessions.
64
+
65
+ ## Status indicators
66
+
67
+ Each session shows a coloured ● reflecting its current state:
68
+
69
+ - **Green** — idle (assistant last responded normally)
70
+ - **Yellow** — busy (assistant is processing)
71
+ - **Red** — blocked (waiting for tool approval)
72
+
73
+ ## How it works
74
+
75
+ `railmux` reads `~/.claude/projects/*` (Claude's per-project session history) and lists everything. Pressing `Enter` on a session does two things: (1) if a detached tmux session running `claude --resume <id>` doesn't already exist, railmux creates one with `tmux new-session -d`; (2) railmux's right pane runs `tmux attach -t cc-<id>` so you see and interact with that claude. Switching sessions just respawns the right pane to attach to a different background tmux session — the detached claudes keep running with all their state intact.
76
+
77
+ ## SSH / remote use
78
+
79
+ railmux works over SSH and benefits from a few tweaks for responsiveness and scrollback:
80
+
81
+ **Server** (`~/.tmux.conf` on the remote machine):
82
+
83
+ ```tmux
84
+ set -sg escape-time 0 # eliminate delay after Escape key
85
+ set -g history-limit 10000 # generous scrollback per pane
86
+ ```
87
+
88
+ **Client** (`~/.ssh/config` on your local machine):
89
+
90
+ ```
91
+ Host your-server
92
+ Compression yes # smoother tmux pane scrolling over SSH
93
+ ```
94
+
95
+ ## Copying text from the Claude pane
96
+
97
+ Selecting text is awkward under tmux: the sidebar and Claude share the screen,
98
+ and over SSH your clipboard lives on the *local* machine.
99
+
100
+ - **OSC 52** (iTerm2, kitty, WezTerm, Alacritty, foot, Windows Terminal):
101
+ drag-select in the Claude pane — copies to local clipboard automatically,
102
+ even over SSH. No Shift needed. (iTerm2: enable *Settings → General →
103
+ Selection → "Applications in terminal may access clipboard"*.)
104
+ - **Without OSC 52** (Terminal.app, etc.): **F9** to fullscreen the agent →
105
+ **Shift-drag** to select → `Cmd/C` to copy → **F9** to return.
106
+ `Ctrl-B z` also toggles fullscreen (built into tmux) but zooms whichever pane
107
+ has focus, so it may fullscreen the sidebar instead.
108
+
109
+ ## Configuration
110
+
111
+ Optional config at `~/.config/railmux/config.toml`:
112
+
113
+ ```toml
114
+ [claude]
115
+ # Path to the claude binary (default: "claude")
116
+ binary = "claude"
117
+
118
+ [live]
119
+ # How often to refresh the session list (ms)
120
+ poll_interval_ms = 1000
121
+ ```
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "railmux"
7
+ dynamic = ["version"]
8
+ description = "Terminal UI to navigate, resume, and start Claude Code sessions across all your projects"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ {name = "Rightglow", email = "zhang.taian@foxmail.com"},
15
+ ]
16
+ keywords = ["claude", "claude-code", "tmux", "tui", "session-manager", "terminal"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console :: Curses",
20
+ "Intended Audience :: Developers",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Operating System :: MacOS",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Software Development",
27
+ "Topic :: Terminals",
28
+ "Topic :: Utilities",
29
+ ]
30
+ dependencies = [
31
+ "urwid>=2.6.16",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.0",
37
+ "build>=1.2",
38
+ "twine>=5.0",
39
+ ]
40
+
41
+ [project.scripts]
42
+ railmux = "railmux.cli:main"
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/Rightglow/Railmux"
46
+ Repository = "https://github.com/Rightglow/Railmux"
47
+ Issues = "https://github.com/Rightglow/Railmux/issues"
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+
52
+ [tool.setuptools.dynamic]
53
+ version = {attr = "railmux.__version__"}
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from railmux.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,38 @@
1
+ """Small atomic-file helpers for railmux-owned state."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+
9
+ def atomic_write_text(
10
+ path: Path,
11
+ text: str,
12
+ *,
13
+ encoding: str = "utf-8",
14
+ ) -> None:
15
+ """Replace *path* atomically after writing *text* beside it."""
16
+ path.parent.mkdir(parents=True, exist_ok=True)
17
+ fd, raw_tmp = tempfile.mkstemp(
18
+ prefix=f".{path.name}.",
19
+ dir=path.parent,
20
+ )
21
+ tmp = Path(raw_tmp)
22
+ stream = None
23
+ try:
24
+ stream = os.fdopen(fd, "w", encoding=encoding)
25
+ fd = -1
26
+ with stream:
27
+ stream.write(text)
28
+ stream = None
29
+ os.replace(tmp, path)
30
+ finally:
31
+ if stream is not None:
32
+ stream.close()
33
+ if fd >= 0:
34
+ os.close(fd)
35
+ try:
36
+ tmp.unlink()
37
+ except OSError:
38
+ pass
@@ -0,0 +1,78 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from railmux import __version__
7
+ from railmux.config import load_config
8
+ from railmux import tmux_ctl
9
+
10
+
11
+ def is_ssh_session(environ: dict[str, str] | None = None) -> bool:
12
+ """Best-effort detection of a process reached through an SSH transport.
13
+
14
+ OpenSSH exports all three variables below. tmux normally refreshes
15
+ ``SSH_CONNECTION`` when a client attaches, so this also works when railmux is
16
+ launched from an existing tmux session. Explicit CLI flags remain available
17
+ for terminals or gateways that strip these variables.
18
+ """
19
+ env = os.environ if environ is None else environ
20
+ return any(env.get(name) for name in ("SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY"))
21
+
22
+
23
+ def main(argv: list[str] | None = None) -> int:
24
+ parser = argparse.ArgumentParser(prog="railmux", description="Claude Code session manager TUI")
25
+ parser.add_argument("--version", action="version", version=f"railmux {__version__}")
26
+ parser.add_argument("--project", help="Launch focused on a single project path")
27
+ parser.add_argument("--claude-home", default=str(Path.home() / ".claude"), help="Override ~/.claude location (testing)")
28
+ parser.add_argument("--inside-tmux", action="store_true", help="Internal: skip the auto-tmux-launch step")
29
+ scroll_group = parser.add_mutually_exclusive_group()
30
+ scroll_group.add_argument(
31
+ "--scroll-coalescing",
32
+ dest="scroll_coalescing",
33
+ action="store_true",
34
+ default=None,
35
+ help="Force-enable tmux copy-mode wheel event coalescing",
36
+ )
37
+ scroll_group.add_argument(
38
+ "--no-scroll-coalescing",
39
+ dest="scroll_coalescing",
40
+ action="store_false",
41
+ help="Force-disable tmux copy-mode wheel event coalescing",
42
+ )
43
+ args = parser.parse_args(argv)
44
+
45
+ # If we're not in tmux and not told we're already inside, re-exec ourselves under tmux.
46
+ if not args.inside_tmux and not tmux_ctl.in_tmux():
47
+ if not tmux_ctl.has_tmux():
48
+ print("error: tmux is required but not found on PATH. Install tmux to use railmux.", file=sys.stderr)
49
+ return 2
50
+ # Find this railmux binary to re-exec.
51
+ railmux_path = sys.argv[0]
52
+ cmd = ["tmux", "new-session", "-A", "-s", "railmux",
53
+ railmux_path, "--inside-tmux"]
54
+ # If extra args were passed, forward them.
55
+ for a in sys.argv[1:]:
56
+ cmd.append(a)
57
+ os.execvp("tmux", cmd)
58
+ # unreachable
59
+
60
+ # Inside tmux now.
61
+ config = load_config()
62
+ # Lazy import so non-TUI invocations (--version etc) don't pull urwid.
63
+ from railmux.ui.app import App
64
+ app = App(
65
+ claude_home=Path(args.claude_home),
66
+ config=config,
67
+ auto_launched=args.inside_tmux,
68
+ scroll_coalescing=(
69
+ is_ssh_session() if args.scroll_coalescing is None
70
+ else args.scroll_coalescing
71
+ ),
72
+ )
73
+ app.run()
74
+ return 0
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())