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.
- railmux-0.1.0/LICENSE +21 -0
- railmux-0.1.0/PKG-INFO +152 -0
- railmux-0.1.0/README.md +121 -0
- railmux-0.1.0/pyproject.toml +57 -0
- railmux-0.1.0/setup.cfg +4 -0
- railmux-0.1.0/src/railmux/__init__.py +1 -0
- railmux-0.1.0/src/railmux/__main__.py +4 -0
- railmux-0.1.0/src/railmux/atomic_file.py +38 -0
- railmux-0.1.0/src/railmux/cli.py +78 -0
- railmux-0.1.0/src/railmux/codex_index.py +333 -0
- railmux-0.1.0/src/railmux/config.py +39 -0
- railmux-0.1.0/src/railmux/discovery.py +222 -0
- railmux-0.1.0/src/railmux/favorites.py +59 -0
- railmux-0.1.0/src/railmux/fuzzy.py +28 -0
- railmux-0.1.0/src/railmux/launcher.py +41 -0
- railmux-0.1.0/src/railmux/models.py +53 -0
- railmux-0.1.0/src/railmux/path_codec.py +282 -0
- railmux-0.1.0/src/railmux/renames.py +72 -0
- railmux-0.1.0/src/railmux/scroll_agent.py +147 -0
- railmux-0.1.0/src/railmux/scroll_manager.py +233 -0
- railmux-0.1.0/src/railmux/session_cache.py +120 -0
- railmux-0.1.0/src/railmux/session_index.py +201 -0
- railmux-0.1.0/src/railmux/tmux_ctl.py +745 -0
- railmux-0.1.0/src/railmux/transcript.py +254 -0
- railmux-0.1.0/src/railmux/ui/__init__.py +0 -0
- railmux-0.1.0/src/railmux/ui/_widgets.py +183 -0
- railmux-0.1.0/src/railmux/ui/app.py +2746 -0
- railmux-0.1.0/src/railmux/ui/keymap.py +133 -0
- railmux-0.1.0/src/railmux/ui/modals.py +594 -0
- railmux-0.1.0/src/railmux/ui/projects_pane.py +163 -0
- railmux-0.1.0/src/railmux/ui/running_pane.py +148 -0
- railmux-0.1.0/src/railmux/ui/sessions_pane.py +294 -0
- railmux-0.1.0/src/railmux/ui/statusbar.py +237 -0
- railmux-0.1.0/src/railmux.egg-info/PKG-INFO +152 -0
- railmux-0.1.0/src/railmux.egg-info/SOURCES.txt +67 -0
- railmux-0.1.0/src/railmux.egg-info/dependency_links.txt +1 -0
- railmux-0.1.0/src/railmux.egg-info/entry_points.txt +2 -0
- railmux-0.1.0/src/railmux.egg-info/requires.txt +6 -0
- railmux-0.1.0/src/railmux.egg-info/top_level.txt +1 -0
- railmux-0.1.0/tests/test_atomic_file.py +32 -0
- railmux-0.1.0/tests/test_cli.py +69 -0
- railmux-0.1.0/tests/test_codex_index.py +410 -0
- railmux-0.1.0/tests/test_codex_transcript.py +197 -0
- railmux-0.1.0/tests/test_config.py +29 -0
- railmux-0.1.0/tests/test_discovery.py +133 -0
- railmux-0.1.0/tests/test_focus_highlight.py +455 -0
- railmux-0.1.0/tests/test_fullscreen_binding.py +124 -0
- railmux-0.1.0/tests/test_fuzzy.py +99 -0
- railmux-0.1.0/tests/test_history_cleanup.py +74 -0
- railmux-0.1.0/tests/test_keymap.py +152 -0
- railmux-0.1.0/tests/test_launcher.py +56 -0
- railmux-0.1.0/tests/test_modals.py +261 -0
- railmux-0.1.0/tests/test_path_codec.py +227 -0
- railmux-0.1.0/tests/test_projects_pane.py +261 -0
- railmux-0.1.0/tests/test_renames.py +173 -0
- railmux-0.1.0/tests/test_running_pane.py +146 -0
- railmux-0.1.0/tests/test_running_sort.py +73 -0
- railmux-0.1.0/tests/test_scroll_agent.py +65 -0
- railmux-0.1.0/tests/test_scroll_manager.py +126 -0
- railmux-0.1.0/tests/test_session_cache.py +195 -0
- railmux-0.1.0/tests/test_session_index.py +109 -0
- railmux-0.1.0/tests/test_sessions_pane.py +412 -0
- railmux-0.1.0/tests/test_soft_quit.py +513 -0
- railmux-0.1.0/tests/test_status.py +418 -0
- railmux-0.1.0/tests/test_statusbar.py +333 -0
- railmux-0.1.0/tests/test_tmux_ctl.py +321 -0
- railmux-0.1.0/tests/test_tmux_status.py +244 -0
- railmux-0.1.0/tests/test_transcript.py +305 -0
- 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
|
+
```
|
railmux-0.1.0/README.md
ADDED
|
@@ -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"]
|
railmux-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -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())
|