{"id":47709489,"url":"https://github.com/clintecker/muxwarp","last_synced_at":"2026-04-02T18:26:53.535Z","repository":{"id":347143327,"uuid":"1193020178","full_name":"clintecker/muxwarp","owner":"clintecker","description":"Warp into tmux sessions on remote machines","archived":false,"fork":false,"pushed_at":"2026-03-26T22:20:32.000Z","size":126,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T08:22:45.880Z","etag":null,"topics":["bubbletea","golang","remote-development","ssh","terminal","tmux","tui"],"latest_commit_sha":null,"homepage":"https://clintecker.github.io/muxwarp","language":"Go","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/clintecker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","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,"dco":null,"cla":null}},"created_at":"2026-03-26T19:45:15.000Z","updated_at":"2026-03-26T22:14:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/clintecker/muxwarp","commit_stats":null,"previous_names":["clintecker/muxwarp"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/clintecker/muxwarp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clintecker%2Fmuxwarp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clintecker%2Fmuxwarp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clintecker%2Fmuxwarp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clintecker%2Fmuxwarp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clintecker","download_url":"https://codeload.github.com/clintecker/muxwarp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clintecker%2Fmuxwarp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312862,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["bubbletea","golang","remote-development","ssh","terminal","tmux","tui"],"created_at":"2026-04-02T18:26:50.934Z","updated_at":"2026-04-02T18:26:53.526Z","avatar_url":"https://github.com/clintecker.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"muxwarp.png\" alt=\"muxwarp logo\" width=\"500\"\u003e\n\u003c/p\u003e\n\n# muxwarp\n\n[![CI](https://github.com/clintecker/muxwarp/actions/workflows/ci.yml/badge.svg)](https://github.com/clintecker/muxwarp/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/clintecker/muxwarp)](https://goreportcard.com/report/github.com/clintecker/muxwarp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nWarp into tmux sessions on remote machines.\n\nmuxwarp scans your configured SSH hosts in parallel, finds every running tmux\nsession, and presents them in a TUI. Pick one, hit enter, and you're in.\n\nDeclare desired sessions in your config and muxwarp shows them as ghost entries\n(◌ NEW) even before they exist. Warp into one and it's created on-the-fly.\n\n## What it looks like\n\n```\n▲ muxwarp ──────────────── 1 host · 4 sessions\n\n▸  api-server    ◇ IDLE  ▪      atlas\n   web-dev       ◇ IDLE  ▪      atlas\n   deploy        ◆ LIVE  ▪▪     atlas\n   new-project   ◌ NEW          atlas      ← desired but doesn't exist yet\n\nenter warp │ / filter │ a add │ e edit │ d delete │ q quit\n```\n\n## Install\n\n### go install\n\n```\ngo install github.com/clintecker/muxwarp/cmd/muxwarp@latest\n```\n\n### From source\n\n```\ngit clone https://github.com/clintecker/muxwarp.git\ncd muxwarp\nmake build\n```\n\nBinary goes to `bin/muxwarp`.\n\n### Releases\n\nPre-built binaries for Linux and macOS (amd64/arm64) are available on the\n[releases page](https://github.com/clintecker/muxwarp/releases).\n\n## Quick start\n\nRun `muxwarp` with no config file and the first-run wizard will walk you through\nadding your first host. Or create `~/.muxwarp.config.yaml` by hand:\n\n```yaml\nhosts:\n  - user@server1\n  - user@server2\n```\n\nRun it:\n\n```\nmuxwarp\n```\n\nThat's it. muxwarp scans both hosts, finds every tmux session, and shows them\nin a navigable list. Pick one and press Enter to warp in.\n\n## Config\n\nConfig lives at `~/.muxwarp.config.yaml`. No XDG, no env vars, no merging.\n\n### Minimal\n\n```yaml\nhosts:\n  - user@server1\n  - user@server2\n```\n\nEach entry is an SSH target string passed directly to your system `ssh` binary.\nYour `~/.ssh/config` aliases, ProxyCommand, agent forwarding -- it all works.\n\n### Full (with defaults shown)\n\n```yaml\ndefaults:\n  timeout: 3s               # per-host SSH connect timeout\n  term: xterm-256color       # TERM to set when attaching\n\nhosts:\n  - user@server1             # plain string (just scan)\n  - target: user@server2     # object with desired sessions\n    sessions:\n      - name: myproject\n        dir: ~/code/myproject\n        cmd: nvim\n      - name: api-dev\n        dir: ~/code/api\n  - workstation              # SSH config aliases work too\n```\n\nPlain string entries scan for existing sessions. Object entries with `sessions`\nalso create ghost entries (◌ NEW) for any desired sessions that don't exist yet.\nWarping into a ghost creates the session on the remote host before attaching.\n\nSee [`examples/muxwarp.config.yaml`](examples/muxwarp.config.yaml) for an\nannotated example.\n\n## Usage\n\n### TUI mode\n\n```\nmuxwarp\n```\n\nScans all hosts, shows every tmux session in a navigable list. Sessions stream\nin as each host responds -- no waiting for the slowest one.\n\nAfter ssh exits (e.g. you detach from tmux), you're returned to the TUI with a\nfresh scan. Pick another session or press `q` to quit.\n\n### Direct warp\n\n```\nmuxwarp \u003cname\u003e\n```\n\nFuzzy-matches `\u003cname\u003e` against session and host names across all hosts:\n\n- **1 match** -- warp immediately, no TUI\n- **Multiple matches** -- open TUI pre-filtered\n- **0 matches** -- print error and exit\n\n### Debug logging\n\n```\nmuxwarp --log /tmp/mux.log\n```\n\nWrites structured JSON logs to the given file. Useful for diagnosing scan\nfailures, ghost session creation issues, or SSH argument problems. When `--log`\nis not passed, no log file is created and there is zero I/O overhead.\n\n### Version\n\n```\nmuxwarp --version\n```\n\n### Help\n\n```\nmuxwarp --help\n```\n\n## Keybindings\n\n| Key         | Action                     |\n|-------------|----------------------------|\n| `Up` / `k`  | Move selection up          |\n| `Down` / `j`| Move selection down        |\n| `Enter`     | Warp into selected session |\n| `/`         | Toggle filter mode         |\n| `Esc`       | Clear filter               |\n| `a`         | Add a new host             |\n| `e`         | Edit selected host         |\n| `d`         | Delete selected host       |\n| `r`         | Rescan all hosts           |\n| `q`         | Quit                       |\n| `Ctrl+C`    | Quit (works in any mode)   |\n\nIn filter mode, type to fuzzy-match against host and session names. Matched\ncharacters are highlighted in the list.\n\nIn the config editor, `Tab`/`Shift+Tab` cycle fields, `Ctrl+S` saves, `Esc`\ncancels. The host field autocompletes from your `~/.ssh/config`.\n\n## How it works\n\n1. Reads `~/.muxwarp.config.yaml` (or runs the first-run wizard if missing)\n2. Spawns one goroutine per host (up to 8 concurrent), each running:\n   ```\n   ssh -o ConnectTimeout=3 -o BatchMode=yes \u003ctarget\u003e \\\n     tmux list-sessions -F '#{session_name}\\t#{session_attached}\\t#{session_windows}'\n   ```\n3. Parses results, validates session names, streams them into the TUI\n4. When you pick a session and press Enter, the TUI exits cleanly\n5. A brief warp animation plays\n6. `ssh -t \u003ctarget\u003e -- env TERM=xterm-256color tmux attach-session -t \u003csession\u003e`\n   runs as a child process\n\nAfter ssh exits, the TUI relaunches with a fresh scan so you can pick another\nsession. Single-match direct warp (`muxwarp \u003cname\u003e` with exactly one hit) uses\n`syscall.Exec` for a clean process replacement instead.\n\nHosts that are down, fail auth (BatchMode=yes), or don't have tmux are silently\nskipped. This is deliberate -- you're scanning known hosts, and missing ones\njust mean fewer sessions in the list.\n\n## Security\n\nmuxwarp is careful about what it executes:\n\n- **No shell interpolation** -- SSH commands are constructed as argument arrays\n  passed directly to `execve(2)`. No shell is involved.\n- **Session name validation** -- names from remote hosts are checked against\n  printable characters excluding `:` (max 256 chars). Invalid names are silently\n  dropped.\n- **`--` separator** -- prevents session names from being interpreted as SSH\n  flags.\n- **BatchMode=yes** -- scanning uses non-interactive SSH to prevent password\n  prompts from appearing inside the TUI.\n\n## Requirements\n\n- **Go 1.23+** to build\n- **ssh** on your local machine (system binary, not a Go library)\n- **tmux** on the remote hosts you're scanning\n\n## Architecture\n\nmuxwarp is a single Go binary with six internal packages:\n\n```\ncmd/muxwarp/main.go        Entry point, arg parsing, orchestration\ninternal/config/            YAML config loading/saving, defaults, validation\ninternal/logging/           Structured debug logging (slog, JSON, --log flag)\ninternal/scanner/           Parallel SSH scanning, result parsing\ninternal/tui/               Bubble Tea v2 TUI (model, view, update, styles)\ninternal/tui/editor/        Config editor and first-run wizard sub-models\ninternal/ssh/               SSH argv construction, validation, exec\ninternal/sshconfig/         ~/.ssh/config parser for host autocomplete\n```\n\nSee [`docs/architecture.md`](docs/architecture.md) for the full technical\ndesign, data flow diagrams, and design decisions.\n\n## Contributing\n\n```\nmake hooks    # install pre-commit hooks\nmake check    # run lint + tests\n```\n\nThe pre-commit hook enforces formatting, static analysis, linting (cyclomatic\ncomplexity max 5), tests with race detector, and 70% minimum coverage.\n\nSee [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) for the full contributor\nguide.\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclintecker%2Fmuxwarp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclintecker%2Fmuxwarp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclintecker%2Fmuxwarp/lists"}