{"id":33863469,"url":"https://github.com/neurosnap/zmx","last_synced_at":"2026-10-19T08:49:23.491Z","repository":{"id":318984400,"uuid":"1073900202","full_name":"neurosnap/zmx","owner":"neurosnap","description":"Session attach/detach for the terminal","archived":false,"fork":false,"pushed_at":"2026-09-25T19:52:53.000Z","size":3900,"stargazers_count":2171,"open_issues_count":30,"forks_count":157,"subscribers_count":12,"default_branch":"main","last_synced_at":"2026-09-28T00:47:54.223Z","etag":null,"topics":["ghostty","session-persistence","tmux","zig"],"latest_commit_sha":null,"homepage":"https://zmx.sh","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neurosnap.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2025-10-10T19:51:40.000Z","updated_at":"2026-09-27T21:07:16.000Z","dependencies_parsed_at":"2026-08-11T17:23:41.169Z","dependency_job_id":null,"html_url":"https://github.com/neurosnap/zmx","commit_stats":null,"previous_names":["neurosnap/zmx"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/neurosnap/zmx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Fzmx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Fzmx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Fzmx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Fzmx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neurosnap","download_url":"https://codeload.github.com/neurosnap/zmx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neurosnap%2Fzmx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38352852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-19T02:00:06.414Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ghostty","session-persistence","tmux","zig"],"created_at":"2025-12-09T11:00:31.925Z","updated_at":"2026-10-19T08:49:23.470Z","avatar_url":"https://github.com/neurosnap.png","language":"Zig","funding_links":[],"categories":["Zig","Fundamentals","Core \u0026 Libraries"],"sub_categories":["Utility"],"readme":"\u003ch1\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./logo.png\" alt=\"Logo\" width=\"128\"\u003e\n  \u003cbr\u003ezmx\n\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  Session attach/detach for the terminal.\n  \u003cbr /\u003e\n  \u003ca href=\"https://zmx.sh\"\u003eDocs\u003c/a\u003e\n  ·\n  \u003ca href=\"https://bower.sh/you-might-not-need-tmux\"\u003eYou might not need tmux\u003c/a\u003e\n  ·\n  Sponsored by \u003ca href=\"https://pico.sh\"\u003epico.sh\u003c/a\u003e\n\u003c/p\u003e\n\n## features\n\n- Persist terminal shell sessions\n- Ability to attach and detach from a shell session without it being killed\n- Native terminal scrollback\n- Multiple clients can connect to the same session\n- Re-attaching to a session restores previous terminal state and output\n- Send commands to a session without attaching to it\n- Print scrollback history of a terminal session in plain text\n- Works on mac and linux\n- This project does **NOT** provide windows, tabs, or splits\n\n## demos\n\n- [zmx - intro](https://youtu.be/UIXj0_rhPgI)\n- [zmx - ai portal](https://youtu.be/CV3skPYHP4Q)\n\n## install\n\n### binaries\n\n- https://zmx.sh/a/zmx-0.8.1-linux-aarch64.tar.gz\n- https://zmx.sh/a/zmx-0.8.1-linux-x86_64.tar.gz\n- https://zmx.sh/a/zmx-0.8.1-macos-aarch64.tar.gz\n- https://zmx.sh/a/zmx-0.8.1-macos-x86_64.tar.gz\n\n### homebrew\n\n```bash\nbrew install neurosnap/tap/zmx\n```\n\n### mise-en-place\n\n```bash\nmise use zmx\n```\n\n### NixOS / nixpkgs\n\nRun immediately without installation:\n\n```sh\nnix run github:NixOS/nixpkgs/nixpkgs-unstable#zmx\n# or build main yourself\nnix run github:neurosnap/zmx\n```\n\nStart a shell with zmx available while it runs:\n\n```sh\nnix shell github:NixOS/nixpkgs/nixpkgs-unstable#zmx\n# or built main yourself\nnix run github:neurosnap/zmx\n```\n\n### packages (unofficial)\n\n- [Alpine Linux](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/zmx)\n- [Arch AUR tracking releases](https://aur.archlinux.org/packages/zmx)\n- [Arch AUR tracking git](https://aur.archlinux.org/packages/zmx-git)\n- [openSUSE Tumbleweed](https://software.opensuse.org/package/zmx)\n- [Gentoo (overlay)](https://codeberg.org/samuelhautamaki/samuelhautamaki-gentoo)\n- [zmx-rpm packaging](https://github.com/engie/zmx-rpm/)\n\n### src\n\n- Requires zig `v0.16`\n- Clone the repo\n- Run build cmd\n\nBe sure to add `~/.local/bin` to your `PATH`:\n\n```bash\nzig build -Doptimize=ReleaseSafe --prefix ~/.local\n```\n\n## usage\n\n\u003e [!IMPORTANT]\n\u003e We recommend closing the terminal window to detach from the session but you can also press `ctrl+\\` or run `zmx detach`. If you need `ctrl+\\` for something else (e.g. vim's `ctrl+\\ ctrl+n` to escape its own `:terminal`), set `ZMX_NO_DETACH_KEY` to disable the shortcut and rely on `zmx detach` or closing the window instead.\n\nRun `zmx help` for more information on usage, with examples.\n\n```\nUsage: zmx \u003ccommand\u003e [args...]\n\nCommands:\n  [a]ttach \u003cname\u003e [command...]                             Attach to session, creating if needed\n  [r]un \u003cname\u003e [-d] [command...]                           Send command without attaching\n  [s]end \u003cname\u003e \u003ctext...\u003e                                  Send raw input to session PTY\n  [p]rint \u003cname\u003e \u003ctext...\u003e                                 Inject text into session display\n  [wr]ite \u003cname\u003e \u003cfile_path\u003e                               Write stdin to file_path through the session\n  [d]etach                                                 Detach all clients (ctrl+\\\\ for current client)\n  [l]ist|ls [--short]                                      List active sessions\n  [g]et \u003cname\u003e                                             Get session labels\n  set \u003cname\u003e k=v ...                                       Set session labels\n  [cl]ear \u003cname\u003e                                           Clear all session labels\n  print-env [-s] \u003cname\u003e [key]                              Print tracked environment variables\n  [k]ill \u003cname\u003e... [--force]                               Kill session and all attached clients\n  [hi]story \u003cname\u003e [--vt|--html] [--screen|scrollback]     Output session scrollback\n  [w]ait \u003cname\u003e...                                         Wait for session tasks to complete\n  [t]ail \u003cname\u003e...                                         Follow session output\n  [c]ompletions \u003cshell\u003e                                    Shell completions (bash, zsh, fish, nu, yash)\n  [v]ersion                                                Show version and metadata (socket dir, log dir)\n  [h]elp                                                   Show this help\n```\n\n## nested sessions\n\nNested sessions are not supported. Inside a session `ZMX_SESSION` is set, and `attach` reads it: instead of creating another client it switches the calling terminal to the session you named.\n\nThat matters when the variable is inherited rather than chosen. A script, build tool, or coding agent started inside a session runs with `ZMX_SESSION` set, so `zmx attach other` from there moves the terminal somebody was using, and the session it was showing is left with no client.\n\nUnset it in anything that attaches on its own behalf:\n\n```bash\nenv -u ZMX_SESSION zmx attach other\n```\n\nFor non-interactive work prefer `zmx run`, which never switches the caller.\n\n## shell prompt\n\nWhen you attach to a `zmx` session, we don't provide any indication that you are inside `zmx`. We do provide an environment variable `ZMX_SESSION` which contains the session name.\n\nWe recommend checking for that env var inside your prompt and displaying some indication there.\n\n### fish\n\nPlace this file in `~/.config/fish/config.fish`:\n\n```fish\nfunctions -c fish_prompt _original_fish_prompt 2\u003e/dev/null\n\nfunction fish_prompt --description 'Write out the prompt'\n  if set -q ZMX_SESSION\n    echo -n \"[$ZMX_SESSION] \"\n  end\n  _original_fish_prompt\nend\n```\n\n### bash and zsh\n\nDepending on the shell, place this in either `.bashrc` or `.zshrc`:\n\n```bash\nif [[ -n $ZMX_SESSION ]]; then\n  export PS1=\"[$ZMX_SESSION] ${PS1}\"\nfi\n```\n\n### powerlevel10k zsh theme\n\n[powerlevel10k](https://github.com/romkatv/powerlevel10k) is a theme for zsh that overwrites the default prompt statusline.\n\nPlace this in `.zshrc`:\n\n```bash\nfunction prompt_my_zmx_session() {\n  if [[ -n $ZMX_SESSION ]]; then\n    p10k segment -b '%k' -f '%f' -t \"[$ZMX_SESSION]\"\n  fi\n}\nPOWERLEVEL9K_RIGHT_PROMPT_ELEMENTS+=my_zmx_session\n```\n\n### oh-my-posh\n\n[oh-my-posh](https://ohmyposh.dev) is a popular shell themeing and prompt engine. This code will display an icon and session name as part of the prompt if (and only if) you have zmx active:\n\n```toml\n[[blocks.segments]]\n   template = '{{ if .Env.ZMX_SESSION }} {{ .Env.ZMX_SESSION }}{{ end }}'\n   foreground = 'p:orange'\n   background = 'p:black'\n   type = 'text'\n   style = 'plain'\n```\n\n### Starship\n\n[Starship](https://starship.rs) is a popular shell themeing and prompt engine. This code will display an icon and session name as part of the prompt if (and only if) you have zmx active:\n\n```toml\nformat = \"\"\"\n${env_var.ZMX_SESSION}\\\n...\n\"\"\"\n\n[env_var.ZMX_SESSION]\nsymbol = \" \"\nformat = \"[$symbol$env_value]($style) \"\ndescription = \"zmx session name\"\nstyle = \"bold magenta\"\n```\n\n## environment variables\n\nA session's environment is initialized when the session is created. When re-attaching from a new terminal or a different SSH connection, environment variables describing the client connection (such as `SSH_AUTH_SOCK`, `DISPLAY`, or terminal remote control variables like `KITTY_LISTEN_ON`, `KITTY_PID`, `KITTY_WINDOW_ID`) become outdated in the running shell.\n\n`zmx` tracks these environment variables from attaching clients. You can inspect the current leader client's environment with `zmx print-env`:\n\n```bash\n# Print all tracked environment variables (set variables as KEY=VALUE, unset as -KEY)\nzmx print-env .\nzmx print-env dev\n\n# Print POSIX shell commands (export ... / unset ...) suitable for eval\nzmx print-env -s .\n\n# Print a specific variable value\nzmx print-env . SSH_AUTH_SOCK\n```\n\nYou can automatically update your shell environment before each prompt:\n\n### bash\n\nAdd to `~/.bashrc`:\n\n```bash\n_zmx_env_hook() {\n  if [[ -n $ZMX_SESSION ]]; then\n    eval \"$(zmx print-env -s .)\"\n  fi\n}\nPROMPT_COMMAND=\"_zmx_env_hook${PROMPT_COMMAND:+; $PROMPT_COMMAND}\"\n```\n\n### zsh\n\nAdd to `~/.zshrc`:\n\n```zsh\nprecmd() {\n  if [[ -n $ZMX_SESSION ]]; then\n    eval \"$(zmx print-env -s .)\"\n  fi\n}\n```\n\n### fish\n\nAdd to `~/.config/fish/config.fish`:\n\n```fish\nfunction _zmx_env_hook --on-event fish_prompt\n  if test -n \"$ZMX_SESSION\"\n    for line in (zmx print-env .)\n      if string match -q -- \"-*\" $line\n        set -e (string sub -s 2 $line)\n      else\n        set -gx (string split -m 1 \"=\" $line)\n      end\n    end\n  end\nend\n```\n\n### configuring tracked variables\n\nBy default, `zmx` tracks:\n\n- `DISPLAY`\n- `SSH_AUTH_SOCK`\n- `SSH_AGENT_PID`\n- `SSH_CONNECTION`\n- `WINDOWID`\n- `XAUTHORITY`\n- `KITTY_LISTEN_ON`\n- `KITTY_PID`\n- `KITTY_WINDOW_ID`\n\nYou can customize this list by setting `ZMX_TRACK_ENV` to a comma-separated list of variable names:\n\n```bash\nexport ZMX_TRACK_ENV=\"DISPLAY,SSH_AUTH_SOCK,GPG_AGENT_INFO\"\n```\n\n## shell completion\n\nShell auto-completion for `zmx` commands and session names can be enabled using the `completions` subcommand. Once configured, you'll get auto-complete for both local `zmx` commands and sessions:\n\n```bash\nssh remote-server zmx attach session-na\u003cTAB\u003e\n# \u003c- auto-complete suggestions appear here\n```\n\n\u003e NOTICE: when installing `zmx` with `homebrew` completions are automatically installed.\n\n### bash\n\nAdd this to your `.bashrc` file:\n\n```bash\nif command -v zmx \u0026\u003e /dev/null; then\n  eval \"$(zmx completions bash)\"\nfi\n```\n\n### zsh\n\nAdd this to your `.zshrc` file:\n\n```zsh\nif command -v zmx \u0026\u003e /dev/null; then\n  eval \"$(zmx completions zsh)\"\nfi\n```\n\n### fish\n\nAdd this to `~/.config/fish/completions/zmx.fish`:\n\n```fish\nif type -q zmx\n  zmx completions fish | source\nend\n```\n\n### yash\n\nAdd this to your `~/.config/yash/rc`:\n\n```sh\nif command -v zmx \u003e /dev/null 2\u003e\u00261; then\n  eval \"$(zmx completions yash)\"\nfi\n```\n\nOr save as an autoloaded script in your completion directory:\n\n```sh\nzmx completions yash \u003e ~/.config/yash/completion/zmx\n```\n\n### nu\n\nAdd this to your Nushell config (`config.nu`):\n\n```nu\nuse (\"~/.cache/zmx/completions.nu\" | path expand)\n# Or generate dynamically:\nzmx completions nu | save -f ~/.cache/zmx/completions.nu\n```\n\n## session picker\n\nYou can add an interactive session picker to your shell that lets you fuzzy-find existing sessions, preview their scrollback history, or create new ones -- all from a single prompt. This is especially useful for remote SSH workflows: add it to your shell startup so that connecting to a machine immediately presents the picker.\n\nRequires [fzf](https://github.com/junegunn/fzf).\n\n- **Enter** selects a matched session (or creates one if no sessions exist)\n- **Ctrl-N** creates a new session using the typed query, even when a fuzzy match is highlighted\n\n\u003cdetails\u003e\n\u003csummary\u003ebash and zsh\u003c/summary\u003e\n\n```bash\nzmx-select() {\n  local display\n  local prefix=\"${ZMX_SESSION_PREFIX:-}\"\n  display=$(zmx list 2\u003e/dev/null | while IFS=$'\\t' read -r name pid clients created dir; do\n    name=${name#*name=}\n    pid=${pid#*pid=}\n    clients=${clients#*clients=}\n    dir=${dir#*start_dir=}\n    if [[ -n \"$prefix\" ]]; then\n      [[ \"$name\" == \"$prefix\"* ]] || continue\n      name=${name#\"$prefix\"}\n    fi\n    printf \"%-20s  pid:%-8s  clients:%-2s  %s\\n\" \"$name\" \"$pid\" \"$clients\" \"$dir\"\n  done)\n\n  local output query key selected session_name\n  output=$({ [[ -n \"$display\" ]] \u0026\u0026 echo \"$display\"; } | fzf \\\n    --print-query \\\n    --expect=ctrl-n \\\n    --height=80% \\\n    --reverse \\\n    --prompt=\"zmx\u003e \" \\\n    --header=\"Enter: select | Ctrl-N: create new\" \\\n    --preview='zmx history {1}' \\\n    --preview-window=right:60%:follow \\\n  )\n  local rc=$?\n\n  query=$(echo \"$output\" | sed -n '1p')\n  key=$(echo \"$output\" | sed -n '2p')\n  selected=$(echo \"$output\" | sed -n '3p')\n\n  if [[ \"$key\" == \"ctrl-n\" \u0026\u0026 -n \"$query\" ]]; then\n    session_name=\"$query\"\n  elif [[ $rc -eq 0 \u0026\u0026 -n \"$selected\" ]]; then\n    session_name=$(echo \"$selected\" | awk '{print $1}')\n  elif [[ -n \"$query\" ]]; then\n    session_name=\"$query\"\n  else\n    return 130\n  fi\n\n  zmx attach \"$session_name\"\n}\n```\n\nYou can call `zmx-select` manually, bind it to a key, or auto-launch it on shell startup when outside a zmx session. With `\u0026\u0026 exit`, the normal flow becomes: connect via SSH → pick a session → work → detach or exit the session → SSH disconnects automatically. Cancelling the picker with **Ctrl-C** drops you into a regular shell as an escape hatch.\n\n```bash\nif command -v zmx \u0026\u003e /dev/null \u0026\u0026 command -v fzf \u0026\u003e /dev/null \u0026\u0026 [[ -z \"$ZMX_SESSION\" ]]; then\n  zmx-select \u0026\u0026 exit\nfi\n```\n\n#### Alternative: gentle hint (server use)\n\nIf you use zmx on a shared server and SSH in frequently for quick operations, auto-launching the picker on every connection may be too aggressive. Instead, show a one-line reminder when active sessions exist:\n\n```bash\nif command -v zmx \u0026\u003e /dev/null \u0026\u0026 [[ -z \"$ZMX_SESSION\" ]]; then\n  local count\n  count=$(zmx ls --short 2\u003e/dev/null | wc -l)\n  if [[ \"$count\" -gt 0 ]]; then\n    echo \"zmx: $count session(s) active — \\`zmx-select\\` to attach\" \u003e\u00262\n  fi\nfi\n```\n\nChoose the auto-launch pattern for dedicated dev machines, and the hint pattern for shared servers where you frequently run quick commands.\n\n\u003c/details\u003e\n\n## session prefix\n\nWe allow users to set an environment variable `ZMX_SESSION_PREFIX` which will prefix the name of the session for all commands. This means if that variable is set, every command that accepts a session will be prefixed with it.\n\n```bash\nexport ZMX_SESSION_PREFIX=\"d.\"\nzmx a runner # ZMX_SESSION=d.runner\nzmx a tests  # ZMX_SESSION=d.tests\nzmx k tests  # kills d.tests\nzmx wait     # suspends until all tasks prefixed with \"d.\" are complete\n```\n\n## philosophy\n\nThe entire argument for `zmx` instead of something like `tmux` that has windows, panes, splits, etc. is that job should be handled by your os window manager. By using something like `tmux` you now have redundant functionality in your dev stack: a window manager for your os and a window manager for your terminal. Further, in order to use modern terminal features, your terminal emulator **and** `tmux` need to have support for them. This holds back the terminal enthusiast community and feature development.\n\nInstead, this tool specifically focuses on session persistence and defers window management to your os wm.\n\n## ssh workflow\n\n### Try it out quickly\n\nIf you'd like to try out `zmx` and `ssh` without fiddling your `ssh` config, make sure to pass the `-t` option to `ssh`. Here's an example:\n\n```bash\nssh -t dev-box zmx attach default\n```\n\nWithout `-t`, the remote shell will not know it's talking to a terminal, and the display will likely get messed up.\n\nThis option isn't needed if you follow the configuration steps below, because `RequestTTY yes` does the same thing.\n\n### Configure it for regular use\n\nUsing `zmx` with `ssh` is a first-class citizen. Instead of using `ssh` to remote into your system with a single terminal and `n` tmux panes, you open `n` terminals and run `ssh` for all of them. This might sound tedious, but there are tools to make this a delightful workflow.\n\nFirst, create an `ssh` config entry for your remote dev server:\n\n```bash\nHost = d.*\n    HostName 192.168.1.xxx\n\n    RemoteCommand zmx attach %k\n    RequestTTY yes\n    ControlPath ~/.ssh/cm-%r@%h:%p\n    ControlMaster auto\n    ControlPersist 10m\n```\n\nArchitecturally, `ssh` supports multiplexing multiple channels of communication within a single connection to a server. `ControlMaster` is the setting that tells `ssh` to multiplex multiple PTY sessions to a single server over one tcp connection. Neat!\n\nNow you can spawn as many terminal sessions as you'd like:\n\n```bash\nssh d.term\nssh d.irc\nssh d.pico\nssh d.dotfiles\n```\n\nBecause the `attach` command is essentially an \"upsert\", this will create or attach to each session.\n\nNow you can use the [`autossh`](https://linux.die.net/man/1/autossh) tool to make your ssh connections auto-reconnect. For example, if you have a laptop and close/open your lid it will automatically reconnect all your ssh connections:\n\n```bash\nautossh -M 0 -q d.term\n```\n\nOr create an `alias`/`abbr`:\n\n```fish\nabbr -a ash \"autossh -M 0 -q\"\n```\n\n```bash\nash d.term\nash d.irc\nash d.pico\nash d.dotifles\n```\n\nWow! Now you can setup all your os tiling windows how you like them for your project and have as many windows as you'd like, almost replicating exactly what `tmux` does but with native windows, tabs, splits, and scrollback! It also has the added benefit of supporting all the terminal features your emulator supports, no longer restricted by what `tmux` supports.\n\nThe end-game here would be to leverage your window manager's ability to automatically arrange your windows for each project with a single command.\n\n## socket file location\n\nEach session gets its own unix socket file. The default location depends on your environment variables (checked in priority order):\n\n1. `ZMX_DIR` =\u003e uses exact path (e.g., `/custom/path`)\n1. `XDG_RUNTIME_DIR` =\u003e uses `{XDG_RUNTIME_DIR}/zmx` (recommended on Linux, typically results in `/run/user/{uid}/zmx`)\n1. `TMPDIR` =\u003e uses `{TMPDIR}/zmx-{uid}` (appends uid for multi-user safety)\n1. `/tmp` =\u003e uses `/tmp/zmx-{uid}` (default fallback, appends uid for multi-user safety)\n\n## permissions\n\nYou can configure the permissions for the socket directory and log files using the following environment variables:\n\n- `ZMX_DIR_MODE` =\u003e sets the mode for the socket and log directories (octal, defaults to `0750`)\n- `ZMX_LOG_MODE` =\u003e sets the mode for the log files (octal, defaults to `0640`)\n\nThis is particularly useful when running `zmx` as a system service with a shared group. For example, setting `ZMX_DIR_MODE=0770` and `ZMX_LOG_MODE=0660` allows group members to attach to the session.\n\n## debugging\n\nWe store global logs for cli commands in `{log_dir}/zmx.log`. We store session-specific logs in `{log_dir}/{session_name}.log`. Right now they are enabled by default and cannot be disabled. The idea here is to help with initial development until we reach a stable state.\n\nThe log directory is resolved in this order:\n\n1. `ZMX_DIR/logs` if `ZMX_DIR` is set\n1. `XDG_STATE_HOME/zmx/logs` if `XDG_STATE_HOME` is set\n1. `HOME/.local/state/zmx/logs`\n1. `TMPDIR/zmx-$UID` (or `/tmp/zmx-$UID`) as a last resort\n\n## a smol contract\n\n- Write programs that solve a well defined problem.\n- Write programs that behave the way most users expect them to behave.\n- Write programs that a single person can maintain.\n- Write programs that compose with other smol tools.\n- Write programs that can be finished.\n\n## known issues\n\n- When upgrading versions of `zmx` where we make changes to the underlying IPC communication, it will kill all your sessions because it cannot communicate through the daemon socket properly\n- Terminal state restoration with nested `zmx` sessions through SSH: host A `zmx` -\u003e SSH -\u003e host B `zmx`\n  - Specifically cursor position gets corrupted\n  - Essentially this is unspecified and unsupported behavior\n- When re-attaching and kitty keyboard mode was previously enable, we try to re-send that CSI query to re-enable it\n  - Some programs don't know how to handle that CSI query (e.g. `psql`) so when you type it echos kitty escape sequences erroneously\n\n## impl\n\n- The `daemon` and client processes communicate via a unix socket\n- Both `daemon` and `client` loops leverage `poll(2)`\n- Each session creates its own unix socket file\n- We restore terminal state and output using `libghostty-vt`\n\n### libghostty-vt\n\nWe use `libghostty-vt` to restore the previous state of the terminal when a client re-attaches to a session.\n\nHow it works:\n\n- user creates session `zmx attach term`\n- user interacts with terminal stdin\n- stdin gets sent to pty via daemon\n- daemon sends pty output to client *and* `ghostty-vt`\n- `ghostty-vt` holds terminal state and scrollback\n- user disconnects\n- user re-attaches to session\n- `ghostty-vt` sends terminal snapshot to client stdout\n\nIn this way, `ghostty-vt` doesn't sit in the middle of an active terminal session, it simply receives all the same data the client receives so it can re-hydrate clients that connect to the session. This enables users to pick up where they left off as if they didn't disconnect from the terminal session at all. It also has the added benefit of being very fast, the only thing sitting in-between you and your PTY is a unix socket.\n\n## prior art\n\nBelow is a list of projects that inspired me to build this project. Architecturally, `zmx` uses aspects of both projects. For example, `shpool` inspired the idea of having libghostty restore the terminal state on reattach. Abduco inspired the idea of one daemon (and unix socket) per session.\n\n### shpool\n\nhttps://github.com/shell-pool/shpool\n\n`shpool` is a service that enables session persistence by allowing the creation of named shell sessions owned by `shpool` so that the session is not lost if the connection drops.\n\n### abduco\n\nhttps://github.com/martanne/abduco\n\nabduco provides session management (i.e. it allows programs to be run independently from its controlling terminal). Together with dvtm it provides a simpler alternative to tmux or screen.\n\n## comparison\n\n| Feature                        | zmx | shpool | abduco | dtach | tmux |\n| ------------------------------ | --- | ------ | ------ | ----- | ---- |\n| 1:1 Terminal emulator features | ✓   | ✓      | ✓      | ✓     | ✗    |\n| Terminal state restore         | ✓   | ✓      | ✗      | ✗     | ✓    |\n| Window management              | ✗   | ✗      | ✗      | ✗     | ✓    |\n| Multiple clients per session   | ✓   | ✗      | ✓      | ✓     | ✓    |\n| Native scrollback              | ✓   | ✓      | ✓      | ✓     | ✗    |\n| Configurable detach key        | ✗   | ✓      | ✓      | ✓     | ✓    |\n| Auto-daemonize                 | ✓   | ✓      | ✓      | ✓     | ✓    |\n| Daemon per session             | ✓   | ✗      | ✓      | ✓     | ✗    |\n| Session listing                | ✓   | ✓      | ✓      | ✗     | ✓    |\n\n## community tools\n\n- [emacs-term-sessions](https://github.com/ArthurHeymans/emacs-term-sessions) Persistent terminal sessions in Emacs, both local and remote.\n- [pi-zmx](https://github.com/deevus/pi-zmx) -- [pi](https://pi.dev) extension for zmx.\n- [zsm](https://github.com/mdsakalu/zmx-session-manager) -- TUI session manager for zmx. List, preview, filter, and kill sessions from an interactive terminal UI.\n- [zmosh](https://github.com/mmonad/zmosh) -- A fork of zmx that adds encrypted UDP auto-reconnect for remote sessions (like mosh).\n- [zmx-picker](https://github.com/EarthmanMuons/zmx-picker) -- fzf-based session picker and project launcher. Jump to a running zmx session or start one inside any of your git/jj repos.\n- [rootshell](https://github.com/kitknox/rootshell) -- Metal-accelerated terminal for iPhone, iPad, Vision Pro, and Mac. Lists active zmx sessions with live previews and can auto-attach on connect.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosnap%2Fzmx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneurosnap%2Fzmx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneurosnap%2Fzmx/lists"}