optmux 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.
optmux-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: optmux
3
+ Version: 0.1.0
4
+ Summary: Optimal, opinionated, batteries-included TMUX that's neat and easy for any project
5
+ Author: Jaeho Shin
6
+ Author-email: Jaeho Shin <netj@sparcs.org>
7
+ License-Expression: MIT
8
+ Requires-Dist: tmuxp
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+
12
+ # optmux
13
+
14
+ <p align="center">
15
+ <img src="optmux.svg" width="192" alt="optmux logo">
16
+ </p>
17
+
18
+ Optimal, opinionated, batteries-included TMUX that's neat and easy for any project.
19
+
20
+ A [tmuxp](https://tmuxp.git-pull.com) wrapper that creates per-project tmux config directories with [TPM](https://github.com/tmux-plugins/tpm) and plugins pre-configured.
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # run optmux anywhere (installs on first use via uv)
26
+ uvx optmux
27
+
28
+ # strongly recommended: install wtcode + lazygit for the full experience
29
+ brew install netj/tap/optmux
30
+ ```
31
+
32
+ Try the included example:
33
+
34
+ ```bash
35
+ git clone https://github.com/netj/optmux.git && cd optmux
36
+ ./example.optmux.yaml
37
+ ```
38
+
39
+ On first run, optmux will:
40
+
41
+ 1. Create `.example.optmux.d/tmux/` next to the YAML file
42
+ 2. Seed a default `tmux.conf` with TPM and plugins
43
+ 3. Install TPM and all plugins (visible in window 0)
44
+ 4. Launch tmuxp with an isolated tmux server
45
+
46
+ ## Usage
47
+
48
+ ### With a tmuxp YAML file
49
+
50
+ Supports `.optmux.yaml`, `.tmuxp.yaml`, and `.optmuxp.yaml` extensions:
51
+
52
+ ```bash
53
+ optmux myproject.optmux.yaml
54
+ optmux myproject.tmuxp.yaml
55
+ ```
56
+
57
+ ### Without arguments
58
+
59
+ ```bash
60
+ optmux
61
+ ```
62
+
63
+ Opens plain `tmux` using `.optmux.d/` in the current directory — useful for a quick, isolated tmux session with the bundled config.
64
+
65
+ ### As a shebang
66
+
67
+ Write a [tmuxp YAML config](https://tmuxp.git-pull.com/configuration/) with the optmux shebang line and make it executable:
68
+
69
+ ```yaml
70
+ #!/usr/bin/env -S uvx optmux
71
+ session_name: myproject
72
+ windows:
73
+ - window_name: editor
74
+ panes:
75
+ - vim .
76
+ - window_name: shell
77
+ panes:
78
+ - ""
79
+ ```
80
+
81
+ ```bash
82
+ chmod +x myproject.optmux.yaml
83
+ ./myproject.optmux.yaml
84
+ ```
85
+
86
+ ## Example tmuxp YAML
87
+
88
+ Here's the included [`example.optmux.yaml`](example.optmux.yaml) showing shortcuts, tmux config, and window layout:
89
+
90
+ ```yaml
91
+ #!/usr/bin/env -S uvx optmux
92
+ session_name: example
93
+ start_directory: .
94
+
95
+ optmux:
96
+ shortcuts:
97
+ C-M-b: gh browse .
98
+ C-M-e:
99
+ command: ${VISUAL:-${EDITOR:-vim}} README.md # exec directly (default for str, no latency)
100
+ window: true # in a new-window
101
+ E:
102
+ send-keys: ${VISUAL:-${EDITOR:-vim}} . # send-keys (given command is run in a new shell)
103
+ zoom: false # do not zoom (defaults to zoom when split-window)
104
+ tmux_config:
105
+ project-settings: |
106
+ set -g status-style bg=blue
107
+
108
+ windows:
109
+ - window_name: editor
110
+ panes:
111
+ - vim .
112
+ - window_name: shell
113
+ panes:
114
+ - ""
115
+ - window_name: logs
116
+ panes:
117
+ - tail -f /var/log/system.log
118
+ ```
119
+
120
+ ## Config directory
121
+
122
+ Each project gets its own `.$NAME.optmux.d/` directory:
123
+
124
+ | Path | Purpose |
125
+ |---|---|
126
+ | `tmux/tmux.conf` | Main tmux config (editable after creation) |
127
+ | `tmux/tmux.*.conf` | Additional config files you can add |
128
+ | `tmux/tmux.sock` | Tmux server socket (isolates this project) |
129
+ | `tmux/plugins/` | TPM plugin directory |
130
+ | `tmux/plugins-update.sh` | Run manually to update all plugins |
131
+
132
+ ## optmux YAML config
133
+
134
+ Add an `optmux:` section to your tmuxp YAML to configure shortcuts and tmux settings:
135
+
136
+ ```yaml
137
+ optmux:
138
+ shortcuts:
139
+ C-M-b: gh browse . # Ctrl-Alt-b: run command directly
140
+ C-M-e:
141
+ command: ${VISUAL:-${EDITOR:-vim}} README.md # exec directly (no shell)
142
+ window: true # open in a new-window
143
+ E:
144
+ send-keys: ${VISUAL:-${EDITOR:-vim}} . # send-keys (runs in a new shell)
145
+ zoom: false # do not zoom (default: true for splits)
146
+ tmux_config:
147
+ project-settings: |
148
+ set -g status-style bg=blue
149
+ ```
150
+
151
+ ### Shortcuts
152
+
153
+ Shortcuts bind tmux keys to commands:
154
+
155
+ - **`C-M-*` keys** are bound globally (no prefix needed)
156
+ - **Other keys** require the tmux prefix (`C-t`)
157
+ - **`command:`** executes directly (default for string values)
158
+ - **`send-keys:`** sends the command to a new shell (supports shell expansion)
159
+ - **`window: true`** opens in a new window instead of a split
160
+ - **`zoom: false`** disables auto-zoom on splits (default: true)
161
+
162
+ ### tmux_config
163
+
164
+ Entries under `tmux_config:` are written as `tmux.optmux-extras.{name}.conf` files and auto-sourced by tmux.
165
+
166
+ ### Personal config (`~/.optmux.yaml`)
167
+
168
+ Create `~/.optmux.yaml` to define personal defaults that apply to all optmux sessions:
169
+
170
+ ```yaml
171
+ optmux:
172
+ shortcuts:
173
+ C-M-g: lazygit
174
+ tmux_config:
175
+ my-defaults: |
176
+ set -g status-style bg=green
177
+ ```
178
+
179
+ Personal config is merged with per-project config. When both define the same key, **personal settings take precedence**.
180
+
181
+ ### Customization
182
+
183
+ - Edit `tmux/tmux.conf` to change tmux settings
184
+ - Drop `tmux/tmux.mysetup.conf` files for additional config (auto-sourced)
185
+ - Run `tmux/plugins-update.sh` from inside tmux to update plugins
186
+ - Press `prefix + R` to reload the config
187
+
188
+ ### Environment variables
189
+
190
+ optmux sets these before launching tmux/tmuxp:
191
+
192
+ | Variable | Value |
193
+ |---|---|
194
+ | `OPTMUX_DIR` | Absolute path to the `.$NAME.optmux.d/` directory |
195
+ | `OPTMUX_NAME` | Name derived from YAML filename or cwd (e.g., `myproject`) |
196
+ | `TMUX_PLUGIN_MANAGER_PATH` | `$OPTMUX_DIR/tmux/plugins` |
197
+
198
+ ## Development
199
+
200
+ ```bash
201
+ # install the latest main branch
202
+ uvx git+https://github.com/netj/optmux.git
203
+
204
+ # local editable install for development
205
+ uv tool install -e .
206
+
207
+ # test any local changes directly (best for testing branches)
208
+ uv run optmux ./example.optmux.yaml
209
+ ```
210
+
211
+ ## License
212
+
213
+ [MIT](LICENSE)
optmux-0.1.0/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # optmux
2
+
3
+ <p align="center">
4
+ <img src="optmux.svg" width="192" alt="optmux logo">
5
+ </p>
6
+
7
+ Optimal, opinionated, batteries-included TMUX that's neat and easy for any project.
8
+
9
+ A [tmuxp](https://tmuxp.git-pull.com) wrapper that creates per-project tmux config directories with [TPM](https://github.com/tmux-plugins/tpm) and plugins pre-configured.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # run optmux anywhere (installs on first use via uv)
15
+ uvx optmux
16
+
17
+ # strongly recommended: install wtcode + lazygit for the full experience
18
+ brew install netj/tap/optmux
19
+ ```
20
+
21
+ Try the included example:
22
+
23
+ ```bash
24
+ git clone https://github.com/netj/optmux.git && cd optmux
25
+ ./example.optmux.yaml
26
+ ```
27
+
28
+ On first run, optmux will:
29
+
30
+ 1. Create `.example.optmux.d/tmux/` next to the YAML file
31
+ 2. Seed a default `tmux.conf` with TPM and plugins
32
+ 3. Install TPM and all plugins (visible in window 0)
33
+ 4. Launch tmuxp with an isolated tmux server
34
+
35
+ ## Usage
36
+
37
+ ### With a tmuxp YAML file
38
+
39
+ Supports `.optmux.yaml`, `.tmuxp.yaml`, and `.optmuxp.yaml` extensions:
40
+
41
+ ```bash
42
+ optmux myproject.optmux.yaml
43
+ optmux myproject.tmuxp.yaml
44
+ ```
45
+
46
+ ### Without arguments
47
+
48
+ ```bash
49
+ optmux
50
+ ```
51
+
52
+ Opens plain `tmux` using `.optmux.d/` in the current directory — useful for a quick, isolated tmux session with the bundled config.
53
+
54
+ ### As a shebang
55
+
56
+ Write a [tmuxp YAML config](https://tmuxp.git-pull.com/configuration/) with the optmux shebang line and make it executable:
57
+
58
+ ```yaml
59
+ #!/usr/bin/env -S uvx optmux
60
+ session_name: myproject
61
+ windows:
62
+ - window_name: editor
63
+ panes:
64
+ - vim .
65
+ - window_name: shell
66
+ panes:
67
+ - ""
68
+ ```
69
+
70
+ ```bash
71
+ chmod +x myproject.optmux.yaml
72
+ ./myproject.optmux.yaml
73
+ ```
74
+
75
+ ## Example tmuxp YAML
76
+
77
+ Here's the included [`example.optmux.yaml`](example.optmux.yaml) showing shortcuts, tmux config, and window layout:
78
+
79
+ ```yaml
80
+ #!/usr/bin/env -S uvx optmux
81
+ session_name: example
82
+ start_directory: .
83
+
84
+ optmux:
85
+ shortcuts:
86
+ C-M-b: gh browse .
87
+ C-M-e:
88
+ command: ${VISUAL:-${EDITOR:-vim}} README.md # exec directly (default for str, no latency)
89
+ window: true # in a new-window
90
+ E:
91
+ send-keys: ${VISUAL:-${EDITOR:-vim}} . # send-keys (given command is run in a new shell)
92
+ zoom: false # do not zoom (defaults to zoom when split-window)
93
+ tmux_config:
94
+ project-settings: |
95
+ set -g status-style bg=blue
96
+
97
+ windows:
98
+ - window_name: editor
99
+ panes:
100
+ - vim .
101
+ - window_name: shell
102
+ panes:
103
+ - ""
104
+ - window_name: logs
105
+ panes:
106
+ - tail -f /var/log/system.log
107
+ ```
108
+
109
+ ## Config directory
110
+
111
+ Each project gets its own `.$NAME.optmux.d/` directory:
112
+
113
+ | Path | Purpose |
114
+ |---|---|
115
+ | `tmux/tmux.conf` | Main tmux config (editable after creation) |
116
+ | `tmux/tmux.*.conf` | Additional config files you can add |
117
+ | `tmux/tmux.sock` | Tmux server socket (isolates this project) |
118
+ | `tmux/plugins/` | TPM plugin directory |
119
+ | `tmux/plugins-update.sh` | Run manually to update all plugins |
120
+
121
+ ## optmux YAML config
122
+
123
+ Add an `optmux:` section to your tmuxp YAML to configure shortcuts and tmux settings:
124
+
125
+ ```yaml
126
+ optmux:
127
+ shortcuts:
128
+ C-M-b: gh browse . # Ctrl-Alt-b: run command directly
129
+ C-M-e:
130
+ command: ${VISUAL:-${EDITOR:-vim}} README.md # exec directly (no shell)
131
+ window: true # open in a new-window
132
+ E:
133
+ send-keys: ${VISUAL:-${EDITOR:-vim}} . # send-keys (runs in a new shell)
134
+ zoom: false # do not zoom (default: true for splits)
135
+ tmux_config:
136
+ project-settings: |
137
+ set -g status-style bg=blue
138
+ ```
139
+
140
+ ### Shortcuts
141
+
142
+ Shortcuts bind tmux keys to commands:
143
+
144
+ - **`C-M-*` keys** are bound globally (no prefix needed)
145
+ - **Other keys** require the tmux prefix (`C-t`)
146
+ - **`command:`** executes directly (default for string values)
147
+ - **`send-keys:`** sends the command to a new shell (supports shell expansion)
148
+ - **`window: true`** opens in a new window instead of a split
149
+ - **`zoom: false`** disables auto-zoom on splits (default: true)
150
+
151
+ ### tmux_config
152
+
153
+ Entries under `tmux_config:` are written as `tmux.optmux-extras.{name}.conf` files and auto-sourced by tmux.
154
+
155
+ ### Personal config (`~/.optmux.yaml`)
156
+
157
+ Create `~/.optmux.yaml` to define personal defaults that apply to all optmux sessions:
158
+
159
+ ```yaml
160
+ optmux:
161
+ shortcuts:
162
+ C-M-g: lazygit
163
+ tmux_config:
164
+ my-defaults: |
165
+ set -g status-style bg=green
166
+ ```
167
+
168
+ Personal config is merged with per-project config. When both define the same key, **personal settings take precedence**.
169
+
170
+ ### Customization
171
+
172
+ - Edit `tmux/tmux.conf` to change tmux settings
173
+ - Drop `tmux/tmux.mysetup.conf` files for additional config (auto-sourced)
174
+ - Run `tmux/plugins-update.sh` from inside tmux to update plugins
175
+ - Press `prefix + R` to reload the config
176
+
177
+ ### Environment variables
178
+
179
+ optmux sets these before launching tmux/tmuxp:
180
+
181
+ | Variable | Value |
182
+ |---|---|
183
+ | `OPTMUX_DIR` | Absolute path to the `.$NAME.optmux.d/` directory |
184
+ | `OPTMUX_NAME` | Name derived from YAML filename or cwd (e.g., `myproject`) |
185
+ | `TMUX_PLUGIN_MANAGER_PATH` | `$OPTMUX_DIR/tmux/plugins` |
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ # install the latest main branch
191
+ uvx git+https://github.com/netj/optmux.git
192
+
193
+ # local editable install for development
194
+ uv tool install -e .
195
+
196
+ # test any local changes directly (best for testing branches)
197
+ uv run optmux ./example.optmux.yaml
198
+ ```
199
+
200
+ ## License
201
+
202
+ [MIT](LICENSE)
@@ -0,0 +1 @@
1
+ """optmux — tmuxp wrapper with per-workflow tmux config directories."""
@@ -0,0 +1,149 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ from importlib.resources import files
6
+ from pathlib import Path
7
+
8
+ import yaml
9
+
10
+ def load_optmux_conf():
11
+ """Load personal optmux config from ~/.optmux.yaml if it exists."""
12
+ conf_path = Path.home() / ".optmux.yaml"
13
+ if conf_path.exists():
14
+ with open(conf_path) as f:
15
+ data = yaml.safe_load(f) or {}
16
+ return data.get("optmux") or {}
17
+ return {}
18
+
19
+
20
+ def merge_optmux(project, personal):
21
+ """Merge project and personal optmux configs (personal wins)."""
22
+ merged = {}
23
+ for key in ("shortcuts", "tmux_config"):
24
+ proj = project.get(key) or {}
25
+ pers = personal.get(key) or {}
26
+ if proj or pers:
27
+ merged[key] = {**proj, **pers}
28
+ return merged
29
+
30
+
31
+ def generate_tmux_conf_files(tmux_dir, optmux):
32
+ """Generate tmux conf files from merged optmux config."""
33
+ # clear all managed files first to avoid stale configs
34
+ for conf_file in tmux_dir.glob("tmux.optmux-*.conf"):
35
+ conf_file.unlink()
36
+
37
+ # optmux.shortcuts → tmux.optmux-shortcuts.conf
38
+ shortcuts = optmux.get("shortcuts") or {}
39
+ if shortcuts:
40
+ lines = []
41
+ for key, value in shortcuts.items():
42
+ bind = "bind -n" if key.startswith("C-M-") else "bind"
43
+ # normalize str to dict
44
+ if isinstance(value, str):
45
+ opts = {"command": value}
46
+ elif isinstance(value, dict):
47
+ opts = value
48
+ else:
49
+ continue
50
+ use_window = opts.get("new-window", False)
51
+ use_zoom = opts.get("zoom", True)
52
+ open_cmd = "new-window" if use_window else "split-window -v"
53
+ # build the tmux action
54
+ if "send-keys" in opts:
55
+ escaped = opts["send-keys"].replace("'", "'\\''")
56
+ action = f"{open_cmd} -c '#{{pane_current_path}}' \\; send-keys '{escaped}' Enter"
57
+ elif "command" in opts:
58
+ escaped = opts["command"].replace("'", "'\\''")
59
+ action = f"{open_cmd} -c '#{{pane_current_path}}' '{escaped}'"
60
+ else:
61
+ continue
62
+ if use_zoom and not use_window:
63
+ action += " \\; resize-pane -Z"
64
+ lines.append(f"{bind} {key} {action}\n")
65
+ (tmux_dir / "tmux.optmux-shortcuts.conf").write_text("".join(lines))
66
+
67
+ # optmux.tmux_config → tmux.optmux-extras.{name}.conf for each entry
68
+ tmux_config = optmux.get("tmux_config") or {}
69
+ for conf_name, content in tmux_config.items():
70
+ (tmux_dir / f"tmux.optmux-extras.{conf_name}.conf").write_text(content)
71
+
72
+
73
+ def main():
74
+ if len(sys.argv) > 1:
75
+ # optmux NAME.optmux.yaml [TMUXP_ARGS...]
76
+ tmuxp_yaml = sys.argv[1]
77
+ remaining_args = sys.argv[2:]
78
+
79
+ yaml_path = Path(tmuxp_yaml).resolve()
80
+ yaml_dir = yaml_path.parent
81
+
82
+ # strip .yaml, then .tmuxp, then .optmux suffixes
83
+ name = yaml_path.stem
84
+ for suffix in (".tmuxp", ".optmux", ".optmuxp"):
85
+ if name.endswith(suffix):
86
+ name = name[: -len(suffix)]
87
+
88
+ optmux_dir = yaml_dir / f".{name}.optmux.d"
89
+ else:
90
+ # optmux (no args) — just open tmux in cwd
91
+ name = Path.cwd().name
92
+ optmux_dir = Path.cwd() / ".optmux.d"
93
+
94
+ tmux_dir = optmux_dir / "tmux"
95
+ tmux_dir.mkdir(parents=True, exist_ok=True)
96
+
97
+ # seed bundled files if not present
98
+ bundled = files("optmux").joinpath("data")
99
+ tmux_conf = tmux_dir / "tmux.conf"
100
+ shutil.copy2(bundled / "tmux.conf", tmux_conf) # always regenerated; use tmux.*.conf for customizations
101
+ setup_script = tmux_dir / "plugins-update.sh"
102
+ if not setup_script.exists():
103
+ shutil.copy2(bundled / "plugins-update.sh", setup_script)
104
+ setup_script.chmod(0o755)
105
+ tips_script = tmux_dir / "tips.sh"
106
+ if not tips_script.exists():
107
+ shutil.copy2(bundled / "tips.sh", tips_script)
108
+ tips_script.chmod(0o755)
109
+
110
+ # generate tmux conf files from optmux YAML merged with personal config
111
+ personal = load_optmux_conf()
112
+ if len(sys.argv) > 1:
113
+ with open(yaml_path) as f:
114
+ data = yaml.safe_load(f) or {}
115
+ project = data.get("optmux") or {}
116
+ optmux = merge_optmux(project, personal)
117
+ else:
118
+ optmux = personal
119
+ generate_tmux_conf_files(tmux_dir, optmux)
120
+
121
+ # ensure scripts from optmux's own venv (e.g., tmuxp) are on PATH
122
+ venv_bin = str(Path(sys.executable).parent)
123
+ os.environ["PATH"] = venv_bin + os.pathsep + os.environ.get("PATH", "")
124
+
125
+ os.environ["OPTMUX_DIR"] = str(optmux_dir)
126
+ os.environ["OPTMUX_NAME"] = name
127
+ os.environ["TMUX_PLUGIN_MANAGER_PATH"] = str(tmux_dir / "plugins")
128
+
129
+ # bootstrap TPM (clone only); plugin install happens inside tmux via tmux.conf
130
+ subprocess.run([str(setup_script)], check=True)
131
+
132
+ sock = str(tmux_dir / "tmux.sock")
133
+ conf = str(tmux_conf)
134
+
135
+ if len(sys.argv) > 1:
136
+ os.execvp(
137
+ "tmuxp",
138
+ ["tmuxp", "load", "--yes", "-S", sock, "-f", conf, tmuxp_yaml, *remaining_args],
139
+ )
140
+ else:
141
+ # attach to existing session on this socket, or create a new one
142
+ has_session = subprocess.run(
143
+ ["tmux", "-S", sock, "has-session"],
144
+ capture_output=True,
145
+ ).returncode == 0
146
+ if has_session:
147
+ os.execvp("tmux", ["tmux", "-S", sock, "attach-session"])
148
+ else:
149
+ os.execvp("tmux", ["tmux", "-S", sock, "-f", conf, "new-session", "-s", f"optmux {name}"])
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # optmux tmux plugin setup/update script
3
+ # Bootstraps TPM, installs missing plugins, and updates all plugins.
4
+ # Run manually to update: ./path/to/workflow.optmux.d/tmux/plugins-update.sh
5
+ set -euo pipefail
6
+
7
+ : ${OPTMUX_DIR:="$(cd "$(dirname "$0")/.."; pwd)"}
8
+ : ${TMUX_PLUGIN_MANAGER_PATH:="$OPTMUX_DIR/tmux/plugins"}
9
+ export TMUX_PLUGIN_MANAGER_PATH
10
+ export XDG_CONFIG_HOME="$OPTMUX_DIR"
11
+
12
+ tpm=netj/tpm # XXX using netj/tpm fork; TODO switch back to tmux-plugins/tpm
13
+
14
+ if [[ ! -x "$TMUX_PLUGIN_MANAGER_PATH"/$tpm/tpm ]]; then
15
+ echo "optmux: installing TPM ($tpm)..."
16
+ git clone https://github.com/$tpm "$TMUX_PLUGIN_MANAGER_PATH"/$tpm
17
+ fi
18
+
19
+ if [[ -n "${TMUX:-}" ]]; then
20
+ echo "optmux: installing/updating tmux plugins..."
21
+ "$TMUX_PLUGIN_MANAGER_PATH"/$tpm/bin/install_plugins
22
+ "$TMUX_PLUGIN_MANAGER_PATH"/$tpm/bin/update_plugins all
23
+ # reload config to activate newly installed/updated plugins
24
+ tmux source-file "$OPTMUX_DIR/tmux/tmux.conf"
25
+ fi
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # optmux tips — shows key binding cheatsheet and hints
3
+ set -euo pipefail
4
+
5
+ : ${OPTMUX_DIR:="$(cd "$(dirname "$0")/.."; pwd)"}
6
+ dismissed="$OPTMUX_DIR/tmux/.tips-dismissed"
7
+
8
+ # check suppression
9
+ if [[ -e "$dismissed" ]]; then
10
+ if grep -q '^forever$' "$dismissed" 2>/dev/null; then
11
+ exit 0
12
+ fi
13
+ # skip if dismissed less than 7 days ago
14
+ if find "$dismissed" -mtime -7 -print -quit 2>/dev/null | grep -q .; then
15
+ exit 0
16
+ fi
17
+ fi
18
+
19
+ # nerd font hint
20
+ nerd_font_tip=""
21
+ if ! fc-list : family 2>/dev/null | grep -qi 'Nerd Font'; then
22
+ nerd_font_tip="
23
+ [!] Install a Nerd Font for best experience
24
+ https://www.nerdfonts.com"
25
+ fi
26
+
27
+ clear
28
+ cat <<EOF
29
+
30
+
31
+ optmux tips
32
+
33
+ Prefix: Ctrl+T (C-t)
34
+
35
+ Workflow: C-M-c wtcode or C-M-f find file to open editor
36
+ -> C-M-g lazygit to check diff/commits
37
+ -> C-M-o cycle between panes or q to return
38
+ -> C-M-s shell in same dir (run tests, one-off commands)
39
+
40
+ Install: brew install netj/tap/wtcode https://github.com/netj/wtcode
41
+ brew install lazygit https://github.com/jesseduffield/lazygit
42
+
43
+ C-t C-t last window C-M-h/j/k/l navigate panes
44
+ C-t C-c new window C-M-z quick toggle zoom
45
+ C-t C-n/p next/prev window C-M-\\ last pane
46
+ C-t n/p next/prev w/ bell C-M-o prev pane + zoom
47
+ C-t z toggle zoom
48
+ C-t o cycle panes
49
+ C-t R reload config
50
+
51
+ C-t t send prefix to nested tmux
52
+ C-t T swap prefix (for nested tmux)
53
+ copy-mode yank auto-copies to system clipboard (OSC 52)
54
+ ${nerd_font_tip}
55
+
56
+ q/Enter: dismiss d: dismiss for a week D: dismiss forever
57
+
58
+ EOF
59
+
60
+ # wait for user input
61
+ while true; do
62
+ read -rsn1 key
63
+ case "$key" in
64
+ q|"")
65
+ break
66
+ ;;
67
+ d)
68
+ touch "$dismissed"
69
+ break
70
+ ;;
71
+ D)
72
+ echo "forever" > "$dismissed"
73
+ break
74
+ ;;
75
+ esac
76
+ done
@@ -0,0 +1,141 @@
1
+ # optmux provided "optimal" TMUX configuration for most workflows
2
+ # Origin: https://github.com/netj/dotfiles/blob/main/.tmux.conf
3
+
4
+ ###############################################################################
5
+ # Essential config
6
+ ###############################################################################
7
+ # C-t as prefix not C-b b/c it's TMUX not BMUX; moreover, C-b/C-f is essential for EMACS-mode readline in BASH/etc.
8
+ set -g prefix C-t; unbind C-b
9
+ set -g default-command '$SHELL -l' # ensure starting a login shell
10
+
11
+ set -g base-index 1
12
+ set -g pane-base-index 1
13
+
14
+ set -g display-time 2500
15
+ setw -g clock-mode-style 24
16
+ setw -g wrap-search off
17
+ setw -g mouse on
18
+ set -g set-clipboard on # emit OSC 52 on yank so terminal sets system clipboard
19
+ set -g allow-passthrough on # let nested/inner programs' OSC 52 reach outer terminal (tmux 3.3+)
20
+ setw -g monitor-activity on
21
+ set -g set-titles on
22
+
23
+ set -g set-titles-string "#T - #I:#W#F#S^#h"
24
+ set -g status-left "#T"
25
+ set -g status-left-length 48
26
+ set -g status-right "#[fg=white,bright]#S#[default]^#[fg=white]#h#[default]"
27
+ set -g status-right-length 16
28
+ set -g status-style bg=black
29
+ set -ga status-style fg=brightblack
30
+ set -g status-left-style fg=green,bright
31
+ setw -g window-status-style fg=brightblack
32
+ setw -g window-status-current-style fg=green,bright
33
+ setw -g window-status-activity-style fg=yellow,bright
34
+ setw -g window-status-bell-style fg=red,bright
35
+ setw -g window-status-bell-style fg=red,bright,blink
36
+ setw -g window-status-format " #I:#{b:pane_current_path}#F "
37
+ setw -g window-status-current-format " #I:#{b:pane_current_path}#F "
38
+
39
+ set -g pane-border-status top
40
+ set -g pane-border-format " #P: #{pane_current_command} | #T "
41
+
42
+ ###############################################################################
43
+ # TPM Plugins
44
+ ###############################################################################
45
+ set -g @plugin 'tmux-plugins/tmux-sensible' # hx limit, mouse, C-n/C-p over awkward n/p
46
+
47
+ # vim-tmux-navigator with C-M-h/j/k/l (preserving custom bindings)
48
+ set -g @plugin 'tmux-plugins/tmux-pain-control' # move around panes with j and k, a bit like vim; resize panes like vim
49
+ set -g @plugin 'christoomey/vim-tmux-navigator'
50
+ set -g @vim_navigator_mapping_left "C-M-h"
51
+ set -g @vim_navigator_mapping_down "C-M-j"
52
+ set -g @vim_navigator_mapping_up "C-M-k"
53
+ set -g @vim_navigator_mapping_right "C-M-l"
54
+ set -g @vim_navigator_mapping_prev ""
55
+ set -g @vim_navigator_prefix_mapping_clear_screen ""
56
+
57
+ set -g @plugin 'tmux-plugins/tmux-copycat'
58
+ set -g @plugin 'tmux-plugins/tmux-yank'
59
+ set -g @plugin 'nhdaly/tmux-better-mouse-mode'
60
+
61
+ # resurrect
62
+ set -g @plugin 'tmux-plugins/tmux-resurrect'
63
+ set -g @resurrect-strategy-vim 'session'
64
+ set -g @resurrect-capture-pane-contents 'on'
65
+
66
+ # continuum
67
+ #set -g @plugin 'tmux-plugins/tmux-continuum'
68
+ #set -g @continuum-restore 'on'
69
+
70
+ set -g @plugin 'netj/claritmux'
71
+ set -g @claritmux_status_workdir 0
72
+ set -g @claritmux_status_date 0
73
+ set -g @claritmux_status_time 0
74
+ set -g @claritmux_status_hostname 1
75
+
76
+ ## Initialize TPM (bootstrap is handled by tmux/plugins-update.sh before tmux starts)
77
+ set -g @tpm 'netj/tpm' # XXX using my fork netj/tpm for now; TODO switch back to tmux-plugins/tpm once merging outstanding improvements
78
+ set -gF @plugin '#{@tpm}'
79
+ set-environment -g OPTMUX_DIR "$OPTMUX_DIR"
80
+ set-environment -g TMUX_PLUGIN_MANAGER_PATH "$OPTMUX_DIR/tmux/plugins"
81
+ run 'export XDG_CONFIG_HOME="$OPTMUX_DIR"; tpm=$(tmux show -gv @tpm); "$TMUX_PLUGIN_MANAGER_PATH"/$tpm/tpm'
82
+ # Window 0: plugins update + tips pane
83
+ set-hook -g session-created 'new-window -t 0 -n "optmux" "$OPTMUX_DIR/tmux/tips.sh" ; split-window -t 0 -v "$OPTMUX_DIR/tmux/plugins-update.sh"'
84
+
85
+ ###############################################################################
86
+ # Essential config, continued; also compensating plugin
87
+ ###############################################################################
88
+
89
+ ## reload optmux config (overrides tmux-sensible's R which reloads ~/.tmux.conf)
90
+ bind R source-file "$OPTMUX_DIR/tmux/tmux.conf" \; display-message "optmux: reloaded $OPTMUX_DIR/tmux/tmux.conf"
91
+
92
+ ## easier session/window actions
93
+ unbind ^D; bind ^D detach # easier detach w/ Ctrl
94
+ unbind ^C; bind ^C new-window -c "#{pane_current_path}" # easier new window
95
+ unbind C; bind C new-session # easier new session
96
+ bind Tab attach-session -d # easier to detach all other clients to make this client the only one
97
+ bind ` confirm-before kill-session # easier kill-session
98
+
99
+ ## settings for TMUX in TMUX windows/panes
100
+ # C-t t to send prefix is a more sensible escaping scheme than TMUX or tmux-plugins/tmux-sensible's default
101
+ # b/c when using TMUX in TMUX (double-decker or more), you really don't want to press C-t 2^k times but just press C-t once followed by the t key only k times for sending the prefix key to the k-th inner/nested TMUX sessions
102
+ unbind C-t; bind C-t last-window
103
+ unbind t; bind t send-prefix
104
+ # swap outer TMUX prefix to a different one to let the usual prefix directly reach the inner TMUX
105
+ bind T \
106
+ if-shell "[ \"`tmux show-options prefix`\" = 'prefix C-t' ]" \
107
+ "set prefix C-s; unbind -n M-Up; unbind -n M-PageUp" \
108
+ "set prefix C-t; bind -n M-Up copy-mode; bind -n M-PageUp copy-mode" \
109
+ #
110
+
111
+ ## easier window selection with an alert ("bell") via lifting Ctrl (NOTE prefix C-n/C-p in tmux-sensible selects all windows)
112
+ bind n run-shell 'wc=#{window_index} ws=$(tmux list-windows -F "##{?window_bell_flag, ,@}##{window_index}"); wb=$({ echo "$ws"; echo "$ws"; } | grep -A9999 ".$wc\$" | tail -n +2 | grep "^ " | head -1); if [[ -n $wb ]]; then tmux select-window -t $wb; else tmux next-window; fi'
113
+ bind p run-shell 'wc=#{window_index} ws=$(tmux list-windows -F "##{?window_bell_flag, ,@}##{window_index}"); wb=$({ echo "$ws"; echo "$ws"; } | grep -B9999 ".$wc\$" | head -n -1 | grep "^ " | tail -1); if [[ -n $wb ]]; then tmux select-window -t $wb; else tmux previous-window; fi'
114
+
115
+ ## easier pane selection/rotation (default is not symmetric and a bit awkward)
116
+ bind C-o select-pane -t :.+
117
+ bind o select-pane -t :.-
118
+ bind O rotate-window
119
+ bind M-o rotate-window -D
120
+
121
+ ## Ctrl+Opt/Alt: quicker access to frequently used prefix combos
122
+ bind -n C-M-z resize-pane -Z # quick toggle zoom pane (normally prefix z)
123
+ bind -n C-M-\\ select-pane -l \; resize-pane -Z # quick switching btwn last panes (normally prefix ;) cf. vim_navigator_mapping_prev
124
+ bind -n C-M-o select-pane -t :.-\; resize-pane -Z # switch to prev pane and also zoom
125
+ # quicker split
126
+ bind -n C-M-s split-window -v -c "#{pane_current_path}" \; resize-pane -Z
127
+
128
+ # quick commands in new pane with Ctrl+Opt
129
+ bind -n C-M-c split-window -v -c "#{pane_current_path}" wtcode \; resize-pane -Z
130
+ bind -n C-M-f split-window -v -c "#{pane_current_path}" '${VISUAL:-${EDITOR:-vim}} $(fzf || echo .)' \; resize-pane -Z
131
+ bind -n C-M-g split-window -v -c "#{pane_current_path}" lazygit \; resize-pane -Z
132
+
133
+
134
+ ###############################################################################
135
+ # Local config
136
+ ###############################################################################
137
+ # Source all tmux.*.conf files from the optmux dir
138
+ if-shell "ls \"$OPTMUX_DIR\"/tmux/tmux.*.conf >/dev/null 2>&1" \
139
+ "source-file $OPTMUX_DIR/tmux/tmux.*.conf"
140
+
141
+ # vim:ft=tmux
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "optmux"
3
+ version = "0.1.0"
4
+ description = "Optimal, opinionated, batteries-included TMUX that's neat and easy for any project"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Jaeho Shin", email = "netj@sparcs.org" }
8
+ ]
9
+ license = "MIT"
10
+ requires-python = ">=3.12"
11
+ dependencies = [
12
+ "tmuxp",
13
+ ]
14
+
15
+ [project.scripts]
16
+ optmux = "optmux.cli:main"
17
+
18
+ [build-system]
19
+ requires = ["uv_build>=0.10.10,<0.11.0"]
20
+ build-backend = "uv_build"
21
+
22
+ [tool.uv.build-backend]
23
+ module-root = ""