[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-96034":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":13,"stars7d":13,"stars30d":14,"stars90d":13,"forks30d":13,"starsTrendScore":13,"compositeScore":15,"rankGlobal":8,"rankLanguage":8,"license":8,"archived":16,"fork":16,"defaultBranch":17,"hasWiki":18,"hasPages":16,"topics":19,"createdAt":8,"pushedAt":8,"updatedAt":20,"readmeContent":21,"aiSummary":22,"trendingCount":13,"starSnapshotCount":13,"syncStatus":11,"lastSyncTime":23,"discoverSource":24},96034,"tmax","theo-kirby\u002Ftmax","theo-kirby",null,"Python",121,2,104,0,17,37.13,false,"main",true,[],"2026-09-20 04:01:32","# tmax\n\nQOL additions on top of tmux\n\ntmax allows for a single deduplicated tmux server to be accessed across all your devices\n\n![tmax session switcher](docs\u002Ftmax-example.png)\n\n\n## Status\n\nWorking and in daily use:\n\n- **Session switcher** (`prefix + Space`): fzf popup, vim-style normal and\n  insert modes, local and remote sessions in one list, hosts refreshed in the\n  background, creates a session when nothing matches.\n- **Remote sessions**: discovered over SSH and attached through tmux control\n  mode. New windows, splits, layouts, zoom, window rename and confirmed\n  pane\u002Fwindow kills are routed to the remote machine. Reconnects after a\n  dropped link.\n- **Native tree** (`prefix + s`): tmux's default session\n  tree showing current local sessions.\n\nKnown limits of the remote bridge:\n\n- Remote scrollback is not imported; copy-mode history starts when a pane is\n  opened locally.\n- Only the recognised bindings are translated. Commands typed at the `:`\n  prompt act on the local proxy, not the remote machine.\n- Each opened remote pane costs a small Python process and an SSH channel\n  (one SSH transport per host). Listing sessions costs nothing.\n- Exact restoration of every terminal mode and of mixed local\u002Fremote layouts\n  is not guaranteed.\n\nRequirements:\n\n| Where  | Needs                                                        |\n|--------|--------------------------------------------------------------|\n| local  | tmux 3.3+, Python 3.9+, fzf 0.74.3+ (`brew install fzf`), macOS bash 3.2 is fine |\n| remote | tmux 3.4+, Python 3.9+ for activity collection, and OpenSSH configured for key + account password |\n\nDeveloped on macOS with tmux 3.4 locally and macOS\u002FLinux hosts on tmux 3.4.\n\n## Install\n\nAdd this line to `~\u002F.tmux.conf`:\n\n```tmux\nrun-shell ~\u002Ftmax\u002Ftmax.tmux\n```\n\nThen reload tmux:\n\n```sh\ntmux source-file ~\u002F.tmux.conf\n```\n\nNo plugin manager is needed. (It also works with TPM, which runs every\n`*.tmux` file in a plugin folder.) Settings go **before** the `run-shell`\nline; see [Options](#options).\n\nFor remote machines, copy `remotes.example.json` to `remotes.json` and edit\nit; see [Remote computers](#remote-computers). A fresh checkout makes no\nremote connections.\n\n## Quick use\n\n| Key               | Action                                                  |\n|-------------------|---------------------------------------------------------|\n| `prefix + Space`  | session switcher popup                                  |\n| `prefix + s`      | tmux's default local session tree                      |\n\nIn the switcher: `j`\u002F`k` move, `Enter` goes, `h` folds a host, `H` toggles all\nremote hosts, `f` stars a session, `L` locks a host, `i` types a filter, and\n`Esc` or `q` closes.\nRemote sessions attach as you pick them. Once attached, they are ordinary tmux\nsessions: `prefix + (` and `)`, the tree, and the switcher all move between\nthem.\n\n## Session switcher\n\n`prefix + Space` opens a large popup with an [fzf](https:\u002F\u002Fgithub.com\u002Fjunegunn\u002Ffzf)\nlist of every session, local and remote, in the spirit of\n[tmux-fzf](https:\u002F\u002Fgithub.com\u002Fsainnhe\u002Ftmux-fzf). Press `p` to open a preview of\nthe selected session's active window on the right half; it refreshes once a second. The preview starts hidden.\n\n```\n╭─ sessions ─────────────────────────────────╮\n│ normal>                             11\u002F11  │\n│  ★ work              ● ●       laptop      │\n│▌ ▾ laptop            ● ●       laptop      │\n│    notes             ● ●       laptop      │\n│  ▸ desk pc           ●         desk pc     │\n│  ▾ gpu box           ●         gpu box     │\n│    train             ● ●       gpu box     │\n╰────────────────────────────────────────────╯\n```\n\nSessions are grouped under one heading per computer. Starred sessions are\npinned above the groups. Local comes first, then each host in the order of\n`remotes.json`. Both headings and session rows end with a computer-name badge\nwritten in the terminal's own colour:\nblue for local, then magenta, red and yellow for the hosts in order. On the\nhighlighted row the name takes the row's colour like the rest of the text.\nThe name is the entry's `label` in `remotes.json`, or the host key when\nthere is none; an optional `colour` (a tmux colour name, `colourN`, or\n`#rrggbb`) overrides the palette. A `local` entry without a `destination`\nnames this machine:\n\n```json\n{\n  \"local\": {\"label\": \"laptop\"},\n  \"desk\":  {\"destination\": \"desk\", \"tmux\": \"\u002Fopt\u002Fhomebrew\u002Fbin\u002Ftmux\", \"label\": \"desk pc\"},\n  \"gpu\":   {\"destination\": \"gpu-box\", \"tmux\": \"tmux\", \"label\": \"gpu box\", \"colour\": \"yellow\"}\n}\n```\n\n### Keys\n\nThe popup starts in a vim-like normal mode. Letters do nothing there except\nthe keys below, so `j` and `k` move without typing into the filter. Press\n`i` (or `\u002F`) for insert mode: type to filter on the session name, `Esc` goes\nback to normal mode with the filter kept.\n\n| Mode   | Key               | Action                                       |\n|--------|-------------------|----------------------------------------------|\n| normal | `j` `k` arrows    | move                                         |\n| normal | `g` `G`           | first \u002F last                                 |\n| normal | `Ctrl-d` `Ctrl-u` | half page down \u002F up                          |\n| normal | `h`               | collapse \u002F expand the current session's host |\n| normal | `H`               | hide \u002F show all remote hosts                  |\n| normal | `f`               | star \u002F unstar the current session             |\n| normal | `p`               | show \u002F hide the session preview               |\n| normal | `i` `\u002F`           | insert mode                                  |\n| normal | `L`               | lock the selected remote host |\n| normal | `Enter`           | unlock a locked host, or go to the session                            |\n| normal | `q` `Esc`         | close                                        |\n| insert | typing            | filter; `Ctrl-j` `Ctrl-k` still move         |\n| insert | `Backspace`       | edit the filter                              |\n| insert | `Enter`           | go to the session; with no match, create a local session named after the text |\n| insert | `Esc`             | back to normal mode                          |\n\nRemote sessions connect when selected. (Their local proxies are still named\n`host\u002Fsession` in the native tmux tree.)\n\n### Agent activity dots\n\nThe middle column uses one dot per **window** on a session row and one dot\nper **session** on a host row, in window-index and session-name order.\n\n- **White:** no agent running.\n- **Green:** an agent is working.\n- **Yellow:** an agent is waiting for a prompt, answer, approval, or usage reset.\n\nWaiting takes priority over working, which takes priority over an ordinary\nterminal. The same rule combines multiple panes within a window and multiple\nwindows within a session. For example, a shell, working Claude Code, and\nwaiting Codex produce white\u002Fgreen\u002Fyellow dots on the session row and one\nyellow dot on the host row. Collapsing hosts and starring sessions do not\nchange the aggregate. Locked hosts retain their `locked` label; disconnected\nhosts do not claim live agent activity.\n\nDots refresh while the popup stays open, including when the selected row\ndoes not change. Dots use bright white, green, and yellow, including on the\nhighlighted row. Highlighted text, including the host label, is black;\nunselected host labels retain their host colours.\n\nInstall the collector and integrations on each machine that runs agents:\n\n```sh\npython3 scripts\u002Finstall-agent-status.py\n# From local tmux, after unlocking a configured remote:\npython3 scripts\u002Finstall-agent-status.py --host laptop\n```\n\nInstallation preserves existing settings and hooks and backs up changed JSON\nfiles. Restart Claude Code, Codex, and Pi to load their integrations.\n**Review and trust the new hooks in Codex's `\u002Fhooks` UI**; tmax never changes\nCodex hook trust settings.\n\nClaude Code and Codex lifecycle hooks report prompt submission, tool use,\napproval requests, and completion. Pi uses agent lifecycle and blocking UI\nevents. These reporters observe state only: they do not approve tools, change\nprompts, or store conversation text. See the official\n[Claude Code hooks](https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fhooks),\n[Codex hooks](https:\u002F\u002Flearn.chatgpt.com\u002Fdocs\u002Fhooks), and\n[Pi extension events](https:\u002F\u002Fpi.dev\u002Fdocs\u002Flatest\u002Fextensions).\n\nAlready-running agents without loaded hooks use a\nbest-effort fallback: tmax identifies an agent process and reads its current\nterminal status lines. It never infers “working” merely from terminal output\nor CPU activity. An identified agent with no working indicator is shown as\nwaiting. This fallback can misclassify unfamiliar agent UIs; hooks give more\nprecise transitions. Dead processes and reused PIDs cannot retain old hook\nstate. Codex desktop sessions outside tmux are not window activity.\n\nRemote collection runs on the remote machine through the existing approved\nSSH master, returning only window states. Remote terminal text stays there.\nThe collector respects custom remote tmux executables and named sockets.\n\n### Behaviour\n\nThe list appears at once with what tmux already knows: local sessions and\nthe remote ones seen before. The configured hosts are then refreshed in the\nbackground while the popup is open. The list is reloaded only if something\nchanged, keeping the cursor on the same session. Hosts that are offline keep their cached\nentries. Polling runs every two seconds after a one-second pause in navigation; remote round trips\nadd latency and never block typing.\n\nThe top-right status lists every configured remote host: `●` is connected,\n`◌` is checking, `○` is offline, and a yellow `●` means locked. It updates when the background refresh\nfinishes; the normal fzf match count follows the host statuses.\n\nPressing `h` in normal mode collapses the host under the cursor to its one-line\nheading, or expands it again. `Enter` does the same on a host heading. Press\n`H` to hide or show every remote host. These choices are remembered for later\nopenings. Set `@tmax-switch-hosts` to `off` to start with only local sessions.\n\nPress `f` on any local or remote session to star it. Starred sessions move to\nthe top of the switcher and stay visible even when their host group is\ncollapsed; press `f` again to unstar them. Collapse and favorite state is kept\nin `$TMAX_STATE_DIR\u002Fswitcher.json`.\n\nThe popup has rounded corners. Its border and the highlighted line use the\ncolours of your status bar (`status-style`); the title is white. A status\nbar without a background colour leaves the border in the default colour.\n\nWithout fzf the key shows a short message in the status line. This binding\nreplaces tmux's default `prefix + Space` (`next-layout`); that command is\nstill available from the `:` prompt or by binding another key.\n\nOptions, in `~\u002F.tmux.conf` before the `run-shell` line:\n\n```tmux\nset -g @tmax-switch-key    \"Space\"  # prefix + key\nset -g @tmax-switch-width  \"75%\"    # popup size, columns or percent\nset -g @tmax-switch-height \"65%\"\nset -g @tmax-switch-hosts  \"on\"     # show remote-host sessions initially\n```\n\n## Remote computers\n\nHosts are entirely user-configured in `remotes.json` next to `tmax.tmux`\n(ignored by Git). Copy `remotes.example.json` and edit it:\n\n```json\n{\n  \"local\": {\"label\": \"this machine\"},\n  \"laptop\": {\n    \"destination\": \"user@laptop\",\n    \"tmux\": \"\u002Fopt\u002Fhomebrew\u002Fbin\u002Ftmux\",\n    \"label\": \"laptop\"\n  },\n  \"server\": {\"destination\": \"server\", \"tmux\": \"tmux\", \"label\": \"big server\", \"colour\": \"red\"}\n}\n```\n\n| Field          | Meaning                                                            |\n|----------------|--------------------------------------------------------------------|\n| `destination`  | what `ssh` gets; normal SSH configuration applies. Without it the entry is not a host (used for `local`) |\n| `tmux`         | path of the tmux executable on that machine                        |\n| `socket`       | optional; selects a nondefault tmux server there                   |\n| `label`        | name shown in the session switcher; defaults to the key            |\n| `colour`       | colour of that name; defaults to the palette                       |\n\nSet `TMAX_REMOTES_FILE` in the local tmux environment to use a different\nJSON file. An empty object disables remote hosts.\n\n### Password unlocks\n\nEach remote host starts locked. Select its heading in the popup and press\n`Enter` to unlock it with that machine's account password. OpenSSH reads the\npassword directly in the terminal; tmax never saves it. A host must require\nboth your SSH key and its account password. Key-only and password-only\nauthentication are rejected.\n\nConfigure each destination once from a regular terminal:\n\n```sh\npython3 scripts\u002Fsetup-host.py laptop\npython3 scripts\u002Fsetup-host.py server\n```\n\nUse the keys from your own `remotes.json`. The command connects over SSH and\nasks for the remote sudo password. It installs a policy for the SSH user,\nvalidates the configuration before and after installation, keeps a backup,\nand reloads SSH on Linux. macOS reads the new policy for new connections.\nKeep a working connection open until a fresh login succeeds. The command\nprints the backup path and rollback instructions. Review\n`scripts\u002Fconfigure-ssh.sh` for the exact server change.\n\nOnce unlocked, discovery and remote panes share one private SSH master.\nThe host locks again when the connection dies, after a reboot, or **24 hours\nafter authentication**, including time asleep. Activity does not extend the\ndeadline. Sleep alone does not lock a still-valid connection. Press `L` on\na remote host or one of its sessions to lock it immediately. Remote tmux\nsessions continue running when the transport closes.\n\nNo background command can fall back to a fresh key-based SSH login.\nPreviously configured external `control_path` sockets are no longer used.\n\nThe 24-hour deadline is enforced by a local watchdog and lease checks, not\nby `ControlPersist` (which is an idle timeout). This is not a server-enforced\nmaximum session lifetime: someone controlling the local account can disable\nthe watchdog, use an open connection, or capture a newly entered password.\nThe destination's key-plus-password policy prevents fresh key-only logins,\nbut cannot undo access or persistence established while authenticated.\nApply that policy on every machine that accepts incoming SSH if you want\nthe requirement in both directions.\n\n### How it works\n\nRemote sessions appear locally as proxy sessions named `host\u002Fsession` (the\nhost key, then the remote session name; they follow remote renames). Until\nselected, a proxy is a lightweight placeholder. Selecting it connects its\nwindows and panes through tmux control mode over SSH. There is one local\nstatus bar and one local prefix; the remote machine keeps its own tmux\nconfiguration and running programs. Agent activity uses the small collector\nand hooks installed in your remote user account.\n\nRecognised bindings for new windows, splitting, pane\u002Fwindow deletion, window\nrenaming, zoom and layouts are routed to the remote machine. Navigation,\ncopy mode, paste, and the switcher stay local. Window rename uses\na small prompt popup. Custom bindings and commands typed at the tmux `:`\nprompt are **not** translated; use the routed keys for remote creation and\ndeletion. Deleting a proxy through a direct local tmux command only removes\nthe local copy, and the synchroniser may recreate it.\n\nHidden panes keep their output subscriptions active so their local screen and\nscrollback remain intact when you return, without blocking remote programs.\nOpened sessions reconcile their windows and panes every 5 seconds while attached (15\nseconds while detached). A lost SSH master requires another unlock; keystrokes\ntyped while disconnected are discarded. If a host runs tmax itself, its own proxy\nsessions (for example the ones it holds for this machine) are skipped, so\nnothing shows up twice.\n\nDiagnostics go to `remote.log` in the private `tmax-UID-HASH` directory under\n`\u002Ftmp`.\n\n## Native session tree\n\n`prefix + s` opens tmux's default session tree (`choose-tree -Zs`) with current local sessions,\nwithout discovering remote sessions. Already-created local proxies for remote\nsessions still appear as `host\u002Fsession` entries. Use `prefix + Space` to\nbrowse and connect to remote sessions.\n\n## Options\n\nPut these in `~\u002F.tmux.conf` **before** the `run-shell` line. Defaults shown.\n\n```tmux\n# switcher\nset -g @tmax-switch-key    \"Space\"\nset -g @tmax-switch-width  \"75%\"\nset -g @tmax-switch-height \"65%\"\nset -g @tmax-switch-hosts  \"on\"     # H toggles all remote-host sessions\n```\n\nEnvironment variables (set with `tmux set-environment -g`): `TMAX_REMOTES_FILE`\n(hosts file) and `TMAX_STATE_DIR` (switcher preferences and state).\n\n## Files\n\n```\ntmax.tmux              entry point: key bindings and hooks\nscripts\u002Fremote.py      remote hosts over SSH control mode; the fzf session switcher\nscripts\u002Fauth.py        password-approved SSH masters and 24-hour expiry\nscripts\u002Fagent_status.py  lifecycle hooks and per-window activity collection\nscripts\u002Finstall-agent-status.py  merge agent integrations on this machine\nintegrations\u002Fpi-status.ts  Pi lifecycle extension\nscripts\u002Fsetup-host.py  interactive server policy setup\nscripts\u002Fconfigure-ssh.sh  validated key-plus-password SSH policy\nremotes.example.json   template for remotes.json\ntest\u002F                  throwaway-server tests (see below)\n```\n\n## How a tmux plugin works\n\n- A plugin is a folder with one `*.tmux` file at the top.\n- That file is a normal shell script. tmux runs it once at start.\n- The script calls `tmux bind-key`, `tmux set-option`, `tmux set-hook`.\n- Key bindings point to helper scripts in `scripts\u002F`.\n- State is kept in tmux user options. They start with `@`.\n\n## Debug\n\nRemote diagnostics are in `remote.log` in the private `tmax-UID-HASH`\ndirectory under `\u002Ftmp`.\n\n## Test\n\nEach test starts its own throwaway tmux server and never touches yours.\n\n```sh\npython3 test\u002Fagent_status_test.py  # lifecycle, aggregation, remote states, pane cleanup\npython3 test\u002Flive_activity_popup_test.py  # visible dot changes in an open popup\npython3 test\u002Fauth_test.py       # authentication, expiry, reboot, fail-closed behavior\npython3 test\u002Fpassword_popup_test.py  # interactive unlock\u002Flock\u002Fcancel with fake SSH\npython3 test\u002Fswitch_test.py     # prefix + Space popup: modes, filter, create, cancel\n```\n\nThe remote integration suite creates and removes its own tmux servers on\nboth ends. Run it in a terminal against a host configured for key + password;\nit prompts once for the account password:\n\n```sh\npython3 test\u002Fremote_integration_test.py --host user@test-host\n# For an executable outside the remote SSH PATH:\npython3 test\u002Fremote_integration_test.py --host user@laptop \\\n  --tmux \u002Fopt\u002Fhomebrew\u002Fbin\u002Ftmux\n```\n\nIt checks popup selection, the local session tree, interactive input\u002Foutput,\nremote windows\u002Fsplits\u002Fzoom, hidden output, reconnection, Vim restoration,\nconfirmed pane deletion, and cleanup of ended sessions. Existing remote sessions are not targeted.\n","tmax 是一个增强 tmux 会话管理体验的轻量级工具，支持本地与远程 tmux 会话的统一发现、切换与协同操作。其核心功能包括：基于 fzf 的交互式会话切换器（支持本地\u002F远程混合列表、后台主机刷新、模糊过滤与一键创建）；通过 SSH + tmux 控制模式实现远程会话透明接入（支持窗口\u002F面板操作、重连与主机锁定）；以及原生 tmux 会话树快捷调用。技术上依赖 tmux 3.3+、Python 3.9+ 与 fzf，采用纯 shell\u002Ftmux 脚本集成，无需插件管理器。适用于多设备开发场景，尤其适合需在 macOS\u002FLinux 工作站、服务器与云主机间频繁切换并协同维护 tmux 会话的终端重度用户。","2026-09-08 02:30:11","CREATED_QUERY"]