repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

(345) commits
Eric Bower  ·  2026-04-29
feat(wait): summary of failed tasks and actionable commands to run

This simply prints the failed tasks and shows some commands to run to see what
went wrong.
Eric Bower  ·  2026-04-29
chore: update pico.sh
Eric Bower  ·  2026-04-28
refactor(attach): reset outer terminal to default state

Closes: https://github.com/neurosnap/zmx/issues/106
Eric Bower  ·  2026-04-26
feat: cross-compile from linux to mac

Latest version of libghostty now supports cross-compiling without needing
Apple sdk tools.
Eric Bower  ·  2026-04-26
chore: update libghostty
Radosław Stachowiak  ·  2026-04-26
feat: accept ls as list alias to help our muscle memory
Eric Bower  ·  2026-04-26
docs: copy and changelog
Eric Bower  ·  2026-04-26
chore: cleanup
Ian Tay  ·  2026-04-10
refactor(ipc): freeze Tag wire values; switch via connectSession
Ian Tay  ·  2026-04-10
fix(daemon): self-pipe signal wakeup + version-tolerant IPC liveness

Self-pipe (idle daemon was deaf to SIGTERM/SIGWINCH):
std.posix.poll loops on .INTR internally (PollError has no Interrupted
member), so the prior `catch error.Interrupted` was unreachable since
8640be51/690487b — signals only "worked" when unrelated I/O woke poll().
Replace with the self-pipe trick: posix.pipe2(.{CLOEXEC,NONBLOCK}), one
wakeSignalPipe handler with errno save/restore, pipe read-end at fixed
poll_fds[2] in both loops; resize/shutdown act in the drain branch.
Removes the now-redundant sigwinch/sigterm atomic flags and collapses
setupSig*Handler into installWakeHandler(sig).

IPC versioning (Option E — decouple liveness from Info shape):
connectSession() does connect-only liveness; 5 of 6 callers (kill,
detach, history, run, attach) switch to it so they survive Info struct
changes. probeSession keeps the Info round-trip for `list`, which now
reports InfoSizeMismatch instead of Unexpected on version skew.

Hygiene: clients_len usize->u64 (extern struct); handleInfo zero-inits
Info so asBytes() doesn't ship 7B tail padding + cmd/cwd stack tails.
Tests freeze @sizeOf(Info)=552 / @sizeOf(Header)=8 and assert zeroed
Info ships zero padding; wired via test{_=ipc;}.
Eric Bower  ·  2026-04-25
docs: pi-zmx
Eric Bower  ·  2026-04-25
fix(run): ignore DA queries when no terminal client connected

`run` is not a real terminal client, it just tails the output and cannot
respond to shell QA queries so we have a special flag that ignores read-only
clients
Eric Bower  ·  2026-04-24
refactor(run): check pty foreground process to detect shell

This removes the need to provide the `--fish` flag at all for `zmx run` commands.
Eric Bower  ·  2026-04-23
feat: print cmd

This sends text directly to client's stdout as-is.  Typically you'll want to wrap
the text in '\r\n' but we leave that up to the end-user
Pavel Borzenkov  ·  2026-04-24
fix(daemon): reset leader fd on client disconnect (#141)

Make sure leader fd is reset when the leader client disconnects abruptly
without going through 'Detach' IPC. Otherwise, the daemon may remember
wrong client fd as the leader and run all input from the client via
libghostty parser.

Fixes #135

NOTE: this currently doesn't address the issue when libghostty can't
parse the byte sequence and complains like this:

[warning] (parser): CSI colon or mixed separators only allowed for 'm'
command, got: terminal.Parser.Action{ .csi_dispatch = .{ .intermediates
= {  }, .params = { 57444, 1, 1 }, .params_sep = .{ .mask = 2 }, .final
= 117 } }
Jay Botte  ·  2026-04-21
feat: send raw bytes to pty input

This is a wrapper around the `.Input` event.

Send delivers bytes to the PTY exactly as-is with no automatic
carriage return. The caller is responsible for appending \r when
shell command submission is desired.
Eric Bower  ·  2026-04-21
feat: zig build test-integration
Eric Bower  ·  2026-04-21
chore: remove debug test file
Eric Bower  ·  2026-04-21
chore: add zig to mise toml
Eric Bower  ·  2026-04-21
refactor(test): use `run -d` in tests and detect shell
x1f9  ·  2026-04-02
test: add BATS session lifecycle tests and mise.toml

14 tests covering session creation, listing, killing, history,
wait, ZMX_DIR isolation, and rapid churn. These tests create real
daemon processes — without the inherited-FD close fix, every test
that calls `zmx run` would hang indefinitely.

mise.toml declares bats 1.13.0 as a dev dependency, establishing
the pattern for managing project tooling.
x1f9  ·  2026-04-02
fix(daemon): redirect stdio to /dev/null after fork

The existing FD close loop (3-63) handles bats' internal file
descriptors, but bats' `run` keyword captures output via pipes
on FDs 0-2. The daemon inherits these pipe write ends and holds
them open, so bats never gets EOF and hangs.

Redirect stdin/stdout/stderr to /dev/null right after setsid().
The daemon communicates exclusively via its unix socket — it never
reads stdin or writes stdout/stderr. This is standard daemon
hygiene and completes the inherited-FD fix.
ikma  ·  2026-03-28
fix(daemon): close inherited file descriptors after fork

After the daemon forks and re-initializes its own log file, close all
file descriptors from 3 to 63 (excluding server_sock_fd). These FDs
are inherited from the parent process and are not needed by the daemon.

Without this fix, test harnesses like bats hang indefinitely. Bats uses
FDs 3+ internally and waits for them to close before exiting. Since zmx
forks a long-lived daemon that inherits these FDs, bats never sees them
close and blocks forever.

The close happens after log_system.deinit()/init() to avoid closing the
parent's log FD before it's properly released, and before spawnPty() so
the PTY FD (allocated after this point) is not affected.

Uses std.c.close() (raw libc) instead of std.posix.close() to avoid
panicking on already-closed or invalid FDs.
Eric Bower  ·  2026-04-21
style: index page
Yi Zhou  ·  2026-04-16
Update fish completions for zmx v0.5.0 (#134)
Eric Bower  ·  2026-04-16
style: site change
Eric Bower  ·  2026-04-16
docs(readme): copy
Eric Bower  ·  2026-04-16
chore: prep for v0.5.0
Eric Bower  ·  2026-04-16
fix: default to bash when running command
Eric Bower  ·  2026-04-16
fix(write): remove bracketed paste

Some shell environments don't support it and we are only sending 1 line at a time
so we should be okay to remove it.
Eric Bower  ·  2026-04-16
docs: copy and cleanup
Eric Bower  ·  2026-04-14
fix: add logo back
Eric Bower  ·  2026-04-14
refactor: doc site
Eric Bower  ·  2026-04-14
docs(readme): paste help msg
Eric Bower  ·  2026-04-14
docs: changelog updates
Eric Bower  ·  2026-04-14
refactor: require "*" suffix for wildcard matches

This applies to: wait, kill, and tail

Examples:

- zmx wait "dev*"
- zmx kill "dev*"
- zmx tail "dev*"
- zmx kill "*"
Eric Bower  ·  2026-04-10
feat: tail, write, and run -d

I'm calling this the ai portal integration.  These features allow a local code
agent to be fully operational against a zmx session that is remote.

Everything works by sending key strokes directly into the zmx session.

This means it doesn't matter where the remote session lives it should work
as if you were typing the commands yourself.  SSH'd into a server? Inside
a container?  It's all the same to zmx and the code agent.

BREAKING CHANGE: `zmx run` is now synchronous by default *and* immediately
tails the session output so the agent can get immediate feedback.  To use the
previous run behavior where the run command completes immediately use detached
mode:
  `zmx run -d`
Shravan Sunder  ·  2026-04-14
fix: rewrite OSC 133;A with redraw=0 to prevent prompt loss on resize (#112)

When zmx forwards shell output containing OSC 133;A (prompt start)
to the outer terminal, the terminal sets shell_redraws_prompt=true.
On resize, the terminal clears prompt rows expecting the shell to
redraw them. But the shell's redraw goes through zmx's IPC relay
chain with cursor coordinates relative to the inner PTY state,
causing a cursor desync that makes the prompt invisible.

Fix: rewrite OSC 133;A to include redraw=0 before forwarding. This
tells the outer terminal that the process on its PTY (zmx client)
cannot redraw prompts, so it should leave prompt rows intact on
resize. This is a standard Kitty protocol extension.

Also disables shell_redraws_prompt on the daemon's internal
ghostty_vt terminal during resize to prevent snapshot state
corruption.

Fixes #111. Likely also fixes #99.
Eric Bower  ·  2026-04-12
chore: dont show task_command because it is unreliable

We just don't have a great way to send [][]u8 over ipc because
we are using structs with fixed size fields that are converted to/from
via std.mem.asBytes.  This means we have to convert the command from [][]u8
to []u8 but that makes our daemon struct fields complicated.

Instead, just rely on looking at the zmx history to understand the command
that was run.
Adrian  ·  2026-04-13
feat: allow configuring socket and log permissions via environment variables (#130)
Eric Bower  ·  2026-04-09
docs(readme): demo video
Eric Bower  ·  2026-04-09
refactor: set leader by detecting user input with libghostty parser
Eric Bower  ·  2026-04-08
chore: cleanup util formatting
Michael Sakaluk  ·  2026-03-18
fix: two-phase terminal state serialization for nested session cursor corruption

Fixes #31 (cursor position corruption on re-attach over SSH).
Partially addresses #86 (cursor style error after attach).
Contributes to #96 (testing framework).

Problem:
When running nested zmx sessions (zmx→SSH→zmx), re-attaching to the outer
session causes cursor positions and screen content to appear at wrong rows.
The root cause is that serializeTerminalState() writes scrollback + visible
content sequentially. Scrollback lines scroll the terminal before CUP
sequences arrive, shifting all visible content by the scrollback overflow.

Fix:
Split serialization into two phases:
1. Phase 1: Emit scrollback content only (no modes/cursor/keyboard).
   These lines scroll into the terminal's scrollback buffer.
2. Clear visible screen (ESC[2J ESC[H ESC[0m) to reset for phase 2.
3. Phase 2: Emit visible screen only with full extras (modes, cursor,
   keyboard, scrolling region).

The clear between phases ensures visible content starts from a clean slate
regardless of how much scrollback preceded it. Scrollback is preserved in
the terminal's buffer for native scroll-up. This approach is inspired by
tmux's visible-only redraw strategy but preserves terminal scrollback.

Tests:
Added 7 integration tests using ghostty-vt roundtrip verification:
- Cursor position preservation after roundtrip
- CUP-positioned markers survive roundtrip
- Scrollback does not shift visible content (the core bug)
- Nested double-roundtrip (inner→outer→client)
- Alternate screen content not leaked
- Terminal size mismatch (30→24 rows) + roundtrip
- Scrollback + size mismatch + nested roundtrip (stress test)

All tests run in <1 second via `zig build test`.
Eric Bower  ·  2026-04-08
docs(changelog): switching sessions
Eric Bower  ·  2026-04-08
chore: use env var fn for session name
Eric Bower  ·  2026-04-03
feat(attach): support switching sessions

Previously we did not allow users to switch to a session from within a session
via `zmx attach`.

Now users are able to run `zmx attach` from within a session and it'll properly
detach and then reattach to the new session.

References: https://github.com/neurosnap/zmx/issues/91
Eric Bower  ·  2026-04-08
feat: change pty window size when new leader is set

When we set a new client leader we send a Resize event request from the daemon
to the client.  So now whenever a user types into their stdin we set the new leader
and then resize the window.
Eric Bower  ·  2026-04-05
chore(leader): init cmd only resize if leader
Eric Bower  ·  2026-04-04
fix: kitty up arrow detection
Eric Bower  ·  2026-04-04
chore(leader): up arrow can claim client leader
Eric Bower  ·  2026-04-04
fix(util): tests
Eric Bower  ·  2026-04-04
refactor: kitty key press logic to be more generic

fix: src/util.zig tests were not being run :sad:
Eric Bower  ·  2026-04-03
refactor: generic parsing kitty ctrl key fns
Eric Bower  ·  2026-04-03
docs: leader comment
Eric Bower  ·  2026-04-03
feat: new client leader policy

The last client to send user input bytes (non-ansi escape codes) becomes the leader. The client
leader controls resizing and any other terminal state changes. Non-leader clients are read-only
until they send user input bytes and takeover leadership.

Closes: https://github.com/neurosnap/zmx/issues/73 
Eric Bower  ·  2026-04-02
feat(kill): add `--force` flag to remove the socket file

If we cannot communicate with the daemon then it could mean a number of things.
We don't automatically assume that a communication error means we can never connect to it.

So we add a `--force` command for when there is an error the user cannot recover from and we
will delete the socket file.

Unfortunately, we do not know the pid for the terminal session outside of the daemon so we
cannot automatically kill the pid in some cases which means it could linger.
とーふとふ  ·  2026-03-31
fix(daemon): close pty master before waitpid to prevent zombie on macOS (#114)
Eric Bower  ·  2026-03-31
docs(ai): introduce claude skill
Ian Tay  ·  2026-03-18
feat(daemon): buffer PTY stdin writes and flush via POLLOUT

Replaces the best-effort ptyWrite (drop on EAGAIN) with a buffered
queue flushed by the daemon's poll loop. Mirrors the pattern already
used for client-socket writes.

- pty_write_buf on Daemon, capped at 256KB (drop new payload on
  overflow — same failure mode as before, 64x higher threshold)
- POLLOUT registered on pty_fd when buffer non-empty; flush handler
  loops until EAGAIN
- handleInput/handleRun queue instead of writing directly
- respondToDeviceAttributes routes through the same buffer so DA
  responses can't interleave with a draining .Run payload after
  client disconnect

Follow-up to #82.
Eric Bower  ·  2026-03-23
feat(kill): accepts multiple args and matches session prefixes

Examples:
- zmx kill one
- zmx kill one two three
- zmx kill d.
- ZMX_SESSION_PREFIX="d." zmx kill
Eric Bower  ·  2026-03-23
chore: dockerfile and ci file

I'm experimenting with using zmx as a job engine for CI.
Eric Bower  ·  2026-03-23
chore: wrap lines where applicable to 100
Eric Bower  ·  2026-03-23
docs: index file
Eric Bower  ·  2026-03-23
docs: cleanup and rewording

I'm removing `AGENTS.md` mainly because it is not particularly useful and there is research to
suggest these files don't really improve agent performance.
Eric Bower  ·  2026-03-22
docs: doc strings
Eric Bower  ·  2026-03-22
fix(list): send no session found msg to stderr

Fixes: https://github.com/neurosnap/zmx/issues/101
Eric Bower  ·  2026-03-19
chore: detach key cleanup
Patrik Sundberg  ·  2026-03-17
fix(attach): reject combined intentional modifiers for Ctrl+\ detach

The parser accepted any modifier combination that included ctrl
(ctrl+shift, ctrl+alt, ctrl+super, etc.) because it only checked
the ctrl bit. This was over-permissive: ctrl+shift+\ is a different
key combination and should not trigger detach.

Tighten the check to require ctrl as the ONLY intentional modifier.
Lock modifiers (caps_lock, num_lock) remain tolerated since they are
ambient state, not deliberate key combinations.
Patrik Sundberg  ·  2026-03-15
fix(attach): handle all kitty keyboard protocol encodings for Ctrl+\

The old detection used rigid substring matching for exactly two
sequences (\x1b[92;5u and \x1b[92;5:1u). This failed when apps
enabled progressive enhancement flags that change the encoding:

- Lock modifiers (caps_lock, num_lock) alter the modifier value
- Alternate key sub-fields add :shifted:base after the key code
- Combined modifiers (ctrl+shift, ctrl+alt, etc.) change the value
- Text codepoint sections append an extra ;codepoints field

Replace with a proper CSI u parser that:
1. Matches key code 92 (backslash) with any alternate sub-fields
2. Checks the ctrl bit in the modifier value: (mod - 1) & 0b100
3. Accepts press/repeat events, rejects release
4. Tolerates optional text codepoint sections
Eric Bower  ·  2026-03-19
docs(readme): mark as unofficial packages
Vishal  ·  2026-03-19
fix: avoid replaying synchronized output on reattach (#95)
Eric Bower  ·  2026-03-18
chore: prep for 0.4.2 release
Samuel Hautamäki  ·  2026-03-17
Document the new Gentoo overlay. (#94)
Eric Bower  ·  2026-03-13
refactor: move print fn to socket.zig
Michael Sakaluk  ·  2026-03-10
fix: validate session name length against Unix socket path limit

Session names that cause the socket path to exceed the OS sun_path limit
(104 bytes on macOS, 108 on Linux) now produce a clear error message
instead of failing silently with exit code 1.

- Add max_socket_path_len constant derived from platform sockaddr_un
- Add validation in getSocketPath returning error.NameTooLong
- Add maxSessionNameLen helper for dynamic limit computation
- Add printSessionNameTooLong for user-facing error on stderr
- Handle NameTooLong in all command paths (run, attach, kill, history, detach, list)
- Move validation before sessionExists in kill/history so the error
  message is shown even when the socket file doesn't exist
- Add 6 unit tests covering boundary conditions and platform constants

Fixes: https://github.com/neurosnap/zmx/issues/84
Yi Zhou  ·  2026-03-12
Refactor fish completions for zmx command (#90)
Iván Hernández Cazorla  ·  2026-03-12
chore: add openSUSE package link to website (#89)
Michael Sakaluk  ·  2026-03-12
fix: print error to stderr when session does not exist (#88)
Eric Bower  ·  2026-03-11
fix(attach): type error
Eric Bower  ·  2026-03-11
chore(history): bare `zmx history` should try to use `ZMX_SESSION`
Ian Tay  ·  2026-03-08
fix(ipc): validate wire data before arithmetic and indexing

- expectedLength: header.len is u32 off the wire; adding sizeof(Header)
  at u32 width could wrap (panic in safe mode, UB slice bounds in
  release). Widen to usize first.
- get_session_entries: cmd_len/cwd_len are u16 (max 65535) but index
  into [256]u8 arrays. Clamp before slicing.
Ian Tay  ·  2026-03-08
fix(attach): skip termios setup when stdin is not a TTY

tcgetattr's return value was discarded, so when stdin was piped
(e.g. `zmx attach foo < /dev/null`), orig_termios remained Zig
`undefined` stack bytes. These garbage bytes were then copied,
modified by cfmakeraw, and applied via tcsetattr — UB in safe builds.
Ian Tay  ·  2026-03-08
fix(attach): serialize terminal state before resize, as the comment says

The existing comment correctly explains why serialize must happen before
resize (reflow moves the cursor), but the code did the opposite. Reattaching
with a different terminal size could leave the cursor misplaced.
Ian Tay  ·  2026-03-08
fix(pty): isolate forked child from parent code path and heap-alloc argv

Two issues in spawnPty's child (pid==0) branch:

1. `try` on allocPrint/bufPrintZ could propagate an error past the
   if-block, causing the forked child to fall through to the parent
   code path — running a second daemon on the same socket, or hitting
   errdefers that delete the parent's socket file. The bufPrintZ case
   is triggerable via a long SHELL basename.

2. The fixed-size argv_buf[64] overflowed with >63 CLI arguments.

Both are fixed by extracting child setup into execChild() which returns
!noreturn (exec or error), with the caller exiting on any error. The
argv array is now heap-allocated to the exact size needed.
Ian Tay  ·  2026-03-08
fix: only clean up socket on ConnectionRefused, not Timeout

A 1-second probe timeout doesn't mean the daemon is dead — it may just be
busy (heavy output, terminal resize reflow, serializing state for another
client). Deleting a live daemon's socket orphans it permanently with no
way to reach it via zmx commands.

Applied to all callsites: get_session_entries, ensureSession (worst case:
spawns a replacement daemon leaving the old one orphaned), kill, detachAll,
history. For kill, the user now gets a helpful message if the daemon is
busy vs. actually dead. The `zmx list` status label for error entries now
says `status=unreachable` (not `cleaning up`) on Timeout, so the display
doesn't contradict what we actually did.

`zmx wait` now treats is_error entries as done+failed: on Timeout the
socket persists, so without this the session would sit at task_ended_at==0
forever, defeating both the completion check and the zero-match timeout.
Ian Tay  ·  2026-03-08
fix: signal handling — ignore SIGPIPE, handle EINTR in poll

- Ignore SIGPIPE once in main() (inherited across fork, covers all
  subcommands). Without this, writing to a closed socket delivers
  SIGPIPE (default disposition: terminate) before write() can return
  EPIPE. An abrupt client exit killed the daemon; a daemon that died
  between probe and send killed one-shot clients (run/kill/history).
- Handle EINTR in the daemon's poll loop (clientLoop already does this).
  Without this, SIGTERM/SIGCHLD caused the daemon to exit with an error
  instead of checking the sigterm flag and shutting down gracefully.

Note: SA_RESTART is intentionally NOT set on SIGTERM/SIGWINCH. On BSD/macOS
(unlike Linux), poll() is restartable when SA_RESTART is set — an idle
daemon would never wake from poll() to check the sigterm flag. The EINTR
handling in the poll loop is the correct and sufficient fix.
Ian Tay  ·  2026-03-08
fix: use platform-correct O_NONBLOCK for fcntl; fix PTY write and double-close

The hardcoded value 0o4000 is Linux-specific; on macOS O_NONBLOCK is 0x4.
posix.SOCK.NONBLOCK is for socket()/accept4(), not fcntl(F_SETFL) — it only
worked on Linux by coincidence where both constants share the same value.
On macOS, non-blocking mode was never actually set on the PTY, client
socket, or stdin.

Setting O_NONBLOCK correctly exposes two latent issues, also fixed here:

- PTY writes in handleInput/handleRun did `_ = try posix.write()`,
  discarding short-write counts and propagating WouldBlock as an error
  that crashed the daemon. Added ptyWriteAll() that polls on WouldBlock
  and retries short writes — same blocking semantics macOS had implicitly.

- ensureSession's errdefer + defer both closed server_sock_fd when
  daemonLoop errored, causing EBADF (posix.close treats this as
  unreachable → panic in safe builds). Restructured so spawnPty failure
  is handled by an explicit catch; the defer is the sole owner after.

Additionally, O_NONBLOCK is set on the open file description (shared with
the parent shell), so stdin's original flags must be restored on exit to
avoid leaving the parent shell's stdin in non-blocking mode.
Ian Tay  ·  2026-03-08
fix(wait): time out after 3 polls if no matching sessions are found

Follow-up to the upstream wait fix: the `total > 0` guard prevented
vacuous exit 0, but left a typo'd `zmx wait foo` polling forever —
trading a wrong answer for no answer. After 3 iterations (~3s) with
max_seen still at 0, exit 2 with "no matching sessions found".

3s is generous: `zmx run foo && zmx wait foo` is essentially sequential
(run blocks until the socket exists and the Run IPC is acked), so a
persistent zero is a typo, not slowness.
Iván Hernández Cazorla  ·  2026-03-08
chore: add link to openSUSE Tumbleweed package (#79)
Ian  ·  2026-03-08
Fix verified bugs (#83)

* fix: validate session names to prevent path traversal

Session names become filenames under socket_dir. Without validation,
`zmx attach ../foo` would create a socket outside that directory, and
(worse) stale-socket cleanup via unlinkat(dirfd, "../foo") would delete
files outside it. The ZMX_SESSION_PREFIX env var was similarly unsanitized.

Also fixes a minor inefficiency: seshPrefix() was called twice.

* fix(history): don't panic when only flags are provided

`zmx history --vt` panicked on the .? unwrap when no positional session
name was given. Pass empty string instead so getSeshName handles it
(returns error.SessionNameRequired if no ZMX_SESSION_PREFIX is set either).

* fix(wait): don't report vacuous success when no sessions match

total == done was true when both were 0, so `zmx wait foo` returned
exit 0 immediately if no session named foo existed — either because of a
typo, or because of a race where the preceding `zmx run foo` hadn't yet
created its socket. Now wait keeps polling until at least one matching
session is seen.

Also tracks the high-water mark of matched sessions: if the count drops,
a session disappeared (daemon crashed or was killed), so wait errors out
instead of looping forever.

* fix(run): reset task state when receiving a Run command

The daemon's exit-marker scan is gated on task_exit_code == null, but that
field was never reset after the first task completed. A second `zmx run`
on the same session would have its ZMX_TASK_COMPLETED marker ignored,
causing `zmx wait` to immediately return the *first* task's exit code.

Also explicitly set is_task_mode so `zmx run` works against sessions that
were originally created by `zmx attach` (non-task mode).

* fix(run): use CR not LF to submit commands to an already-prompting shell

When `zmx run` sends a command to an existing session, the shell is at
the readline prompt with the PTY in raw mode. readline binds accept-line
to CR (\r), not LF (\n), so a trailing \n just moves the cursor — the
command sits typed but unexecuted.

The first-ever `zmx run` on a fresh session happened to work because
it's sent during shell startup, before readline takes over: the line
discipline is still canonical and ICRNL translates \n. Any subsequent
run silently did nothing, which was masked by the stale-task-state bug
(wait returned the first run's exit code, looking like success).

\r works in both modes: canonical mode translates it via ICRNL, and
raw-mode readline accepts it directly.
Danil Agafonov  ·  2026-03-06
docs: add session picker recipe using fzf (#75)

Add a shell function that fuzzy-finds and attaches to zmx sessions,
previews scrollback history, or creates new ones. Includes a Ctrl-N
keybinding to force session creation when a fuzzy match is highlighted.
Particularly useful for remote SSH workflows.
Eric Bower  ·  2026-03-04
refactor: break up main.zig

This is just some code cleanup to make it a little easier to navigate the codebase.  I don't want to break everything up into separate files but I want the main business logic to live inside of main.zig.
Eric Bower  ·  2026-03-04
chore: remove comment about old strat
4d398e8
(single-quote-escape)
External.Kai-Fronsdal  ·  2026-03-04
fix(run): always single-quote args to avoid bash history expansion bug

The previous shellQuote preferred double quotes, but \! does not
suppress history expansion inside double quotes in interactive bash.
Switch to always using single quotes (matching Python's shlex.quote),
which are universally safe since nothing is special inside single
quotes except ' itself.

Add unit tests for shellNeedsQuoting and shellQuote.

Fixes #72

Made-with: Cursor
Eric Bower  ·  2026-03-03
chore(list): changed key names and made formatting more stable
Eric Bower  ·  2026-03-03
docs: changelog
Eric Bower  ·  2026-03-03
fix(run): stdin regression with ZMX_TASK_COMPLETED

We were not actually running the command sent into stdin properly.

Closes: https://github.com/neurosnap/zmx/issues/71
Eric Bower  ·  2026-03-03
fix(run): when no client is attached, send DA response query from daemon
Eric Bower  ·  2026-03-03
fix(run): re-quote when using shell meta chars

argv doesn't receive the quotes in the command line so we need to apply an algorithm to re-quote args when it uses special meta-characters.

Closes: https://github.com/neurosnap/zmx/issues/72
rok  ·  2026-03-01
fix: fix nix build failure (#69)

Eric Bower  ·  2026-02-28
fix(wait): use-after-free
Justin Su  ·  2026-02-27
Use `env_var` module in Starship prompt snippet (#66)

ida  ·  2026-02-26
Add starship shell prompt instructions (#62)

Justin Su  ·  2026-02-26
Update fish shell instructions to use user completions directory (#64)

Justin Su  ·  2026-02-26
Simplify `brew tap` and `brew install` into a single command (#65)

Eric Bower  ·  2026-02-23
chore: publish v0.4.1
Eric Bower  ·  2026-02-23
fix(ghostty): zig build test was failing
Noelle Leigh  ·  2026-02-22
docs: Fix typo in CHANGELOG

"stale" → "stall"

Signed-off-by: Noelle Leigh <noelle_leigh@fastmail.com>
Eric Bower  ·  2026-02-22
docs: add alpine pkg
Eric Bower  ·  2026-02-22
docs: site
Eric Bower  ·  2026-02-22
docs: readme

new install.packages section
L.B.R.  ·  2026-02-21
fix(terminal): pop Kitty keyboard protocol on detach (#59)

Eric Bower  ·  2026-02-20
docs: readme
Eric Bower  ·  2026-02-20
chore: publish v0.4.0
Eric Bower  ·  2026-02-20
chore: update libghostty-vt
Eric Bower  ·  2026-02-20
chore: nanoTimestamp -> timestamp
Eric Bower  ·  2026-02-20
docs: changelog
L.B.R.  ·  2026-02-20
docs: add zmosh to community tools (#58)

Co-authored-by: L.B.R. <lbr@mmonad.com>
Eric Bower  ·  2026-02-20
docs: readme
Eric Bower  ·  2026-02-20
docs: readme
L.B.R.  ·  2026-02-20
fix: add missing fields to writeSessionLine test (#56)

Eric Bower  ·  2026-02-19
chore(wait): add `w` alias
Eric Bower  ·  2026-02-19
docs: readme
Eric Bower  ·  2026-02-19
feat: wait command

This command will wait for all tasks to be completed and return an "aggregate" exit code.
Eric Bower  ·  2026-02-17
feat: env var `ZMX_SESSION_PREFIX`

This change will allow users to set an environment variable that prefixes all session names with
the value provided in the env var.  This should help with some automation tasks relying on zmx since
you can now "group" sessions together under a common prefix.
Eric Bower  ·  2026-02-08
feat(run): track task completion with a marker and record exit code
Eric Bower  ·  2026-02-19
docs: readme
Eric Bower  ·  2026-02-19
docs: readme
Michael Sakaluk  ·  2026-02-19
docs: add zsm to community tools section in README (#55)

Eric Bower  ·  2026-02-18
chore: new logo
Eric Bower  ·  2026-02-18
chore: new logo
Eric Bower  ·  2026-02-16
feat(version): print socket and log directory locations
wegel  ·  2026-02-12
chore: update ghostty to HEAD (#53)

David Calhoun  ·  2026-02-02
docs: Fix typo in feature list on index.html (#51)

Eric Bower  ·  2026-02-01
docs: readme
Eric Bower  ·  2026-02-01
chore: publish v0.3.0
Eric Bower  ·  2026-02-01
chore: short should be session name only
Paul Smith  ·  2026-01-24
feat: indicate current session in listing

This change adds a visual indicator (a prefixed arrow) of the current
session, if any, to session listings with `zmx [l]ist`.

Example:

```
$ zmx l
  session_name=o.quux	pid=38719	clients=0	started_in=/home/example
→ session_name=o.foo.bash	pid=60775	clients=2	started_in=/home/example
  session_name=o.foo.quux	pid=50707	clients=1	started_in=/home/example
  session_name=o.zmx	pid=21531	clients=2	started_in=/home/example
  session_name=o.zmx.bash	pid=17772	clients=1	started_in=/home/example
$ zmx l --short
  o.quux
→ o.foo.bash
  o.foo.quux
  o.zmx
  o.zmx.bash
```

If there is no current session, the output is unchanged.

Fixes #42.
Eric Bower  ·  2026-01-23
docs: completions check if cmd exists first
Eric Bower  ·  2026-01-23
docs: copy
Josh Bainbridge  ·  2026-01-01
docs: add documentation for evaluating auto-complete

After adding support for auto-completion, we could add instructions for
users on how to integrate this into their shell configurations.

Update the README with a new sections for shell auto-complete
configuration.
Josh Bainbridge  ·  2026-01-01
feat: add completions command to output shell completion scripts

Adding auto-completion support for a limited number of commonly used
shells (bash, zsh, fish) would allow for greater shell integration. This
can be done by using completions command that is then evaluated in the
shell config.

Add a new completions module that holds completion scripts for zsh, bash
and fish. Then include a new command called completions that takes a
shell name and outputs the requested script.

This can then be added to a shell config such as .zshrc like so:

eval “$(zmx completions zsh)"

For automatic shell integration.
Josh Bainbridge  ·  2026-01-01
feat: add new short list option via flag

Listing just the session names, without extra information, could be
useful for external tools or processes that need to do operations based
on the session names.

Add a new --short option to the list subcommand to list only session
names without extra information.
Eric Bower  ·  2026-01-23
chore(version): show git sha if running dev build
Eric Bower  ·  2026-01-23
feat(list): show `started_in` directory and original `cmd` if provided
Eric Bower  ·  2026-01-21
fix: properly handle killing shells and children

Shell's typically ignore SIGTERM unless there's a trap installed, but they will respond to SIGHUP.
This works fine for shells but when a user runs `zig attach editor nvim` or any other non-shell command
they might not respond to SIGHUP the way we want.  So we have implemented a multi-signal approach:

- SIGHUP
- Wait 500ms
- SIGKILL

Reference: https://github.com/shell-pool/shpool/blob/1b3b27f059f0008c0509b5170d84b86c752b1da3/libshpool/src/daemon/shell.rs#L79-L94
Reference: https://github.com/neurosnap/zmx/pull/47
Eric Bower  ·  2026-01-20
fix: gracefully shutdown daemon process

This change properly handles when the daemon process receives a SIGTERM.

Also during the kill process we properly forward SIGTERM to all children processes of the daemon.
Eric Bower  ·  2026-01-09
docs(readme): copy
Eric Bower  ·  2026-01-09
docs(changelog): add recent changes
Eric Bower  ·  2026-01-09
feat(history): add `--vt` and `--html` flag

The `--vt` flag will send the raw ansi escape sequences to recreate the terminal
session.  The idea is that this could be useful for debugging purposes.

The `--html` flag was added because, why not? It's there, might as well
use it.
Eric Bower  ·  2026-01-08
fix: spawn as login shell

The `-{shell}` convention signals to most shells that they should behave as if invoked as a login
shell, loading appropriate startup files (like .bash_profile or .zprofile).

Reference: https://github.com/neurosnap/zmx/issues/41
Eric Bower  ·  2026-01-08
chore: update ghostty to HEAD
Sebastian Berns  ·  2026-01-07
Add powerlevel10k zsh theme instructions (#40)

Eric Bower  ·  2026-01-04
docs(readme): known issue with kitty protocol
Eric Bower  ·  2026-01-02
fix: use own ghostty vt to test
bdillahu  ·  2025-12-30
Update README.md (#36)

Eric Bower  ·  2025-12-30
docs(readme): known issues
Josh Bainbridge  ·  2025-12-30
docs: add instructions on updating the bash shell prompt (#35)

There are missing instructions on updating the prompt for bash.

Combine both the zsh and the bash instructions in a generic setup.
Eric Bower  ·  2025-12-29
fix: prevent terminal state corruption on detach

When zmx sessions are nested through SSH (host zmx -> ssh -> remote zmx),
the detach restore sequence was corrupting the outer session's terminal
state tracking in ghostty-vt.

The issue: when the remote zmx client detached, it sent clear screen and
home cursor sequences (\x1b[2J\x1b[H) which flowed through SSH to the
host's PTY. This caused the host's ghostty-vt to update its cursor
position, and since nothing followed to correct it, subsequent reattaches
to the host session would restore with incorrect cursor positioning.

The fix: remove clear screen and home cursor from the detach sequence.
We still reset terminal modes (mouse tracking, bracketed paste, alt
screen, etc.) to ensure the terminal is left in a sane state.

Trade-offs:
- On detach, users now see the session content instead of a cleared
  screen. This is arguably useful feedback showing what was happening.
- Nested zmx through SSH may still have cursor positioning issues since
  the restore's CUP sequence also flows through to the outer session.
  This is accepted as an edge case we cannot fully solve without
  breaking normal operation.
- The attach clear is kept because it provides a clean slate and any
  corruption it causes is immediately overwritten by the session restore.

Design principle: be conservative about terminal escape sequences sent
on detach since they persist and affect the outer terminal's state.
The session restore handles reconstruction, so we don't need to
"clean up" on the way out.

References: https://github.com/neurosnap/zmx/issues/31
Eric Bower  ·  2025-12-29
chore: remove trailing slash from TMPDIR
Eric Bower  ·  2025-12-29
chore: publish v0.2.0
Eric Bower  ·  2025-12-29
docs(readme): debugging section copy
Eric Bower  ·  2025-12-28
feat: `zmx [r]un` to send a command to a session without attaching to it
Eric Bower  ·  2025-12-28
docs: readme
Eric Bower  ·  2025-12-27
refactor: remove ctrl+b + d detach shortcut

This feature was annoying to implement because we can't simply check for
a single byte, we first have to detect if ctrl+b was pressed then check
for d.

In the end I don't like the complexity it introduces and it seems pretty low
value since we should recommend users simply close their terminal
window.
Eric Bower  ·  2025-12-27
docs: dont advertise alternative shortcut yet

I might remove this shortcut since i'm noticing issues.
Eric Bower  ·  2025-12-27
feat: `zmx [hi]story` command which prints session scrollback as text
Eric Bower  ·  2025-12-24
refactor: make client socket write non-blocking
Eric Bower  ·  2025-12-22
fix: first attach, resize first
Eric Bower  ·  2025-12-20
fix: timing issue with first attach and sending terminal state

On first attach we don't want to send the terminal state but there
appears to be a timing issue where sometimes we already have pty output
before the Init handler can complete.
Eric Bower  ·  2025-12-19
docs: update changelog
Eric Bower  ·  2025-12-19
fix: correct cursor position when reattaching to session

Serialize terminal state BEFORE resizing the terminal in handleInit.
Previously, the resize was done first, which caused cursor reflow and
moved the cursor position. The shell's SIGWINCH-triggered prompt redraw
would then run after our snapshot was sent, leaving the cursor at the
wrong position (end of line instead of after the prompt).

By capturing the terminal state before resize, we preserve the correct
cursor position from when the user detached.
Eric Bower  ·  2025-12-19
docs: update detach key
Eric Bower  ·  2025-12-19
refactor: input shortcut handling

Another code clarity change that abstracts handling the shortcuts for
detach
Eric Bower  ·  2025-12-19
refactor: daemonLoop and terminal serialization

Code cleanup to make the daemonLoop and ghostty interaction easier to
understand
Eric Bower  ·  2025-12-19
feat: ctrl+b + d to detach

With support to add more commands
Eric Bower  ·  2025-12-19
fix: reset terminal modes on detach

Closes: https://github.com/neurosnap/zmx/issues/26
Eric Bower  ·  2025-12-19
chore: update ghostty-vt
Tristan Partin  ·  2025-12-18
Use XDG_RUNTIME_DIR for the socket directory if defined

XDG_RUNTIME_DIR[0] is a file path for user-specific runtime files, like
sockets.

It will typically be defined on Linux. I can't say for sure on BSDs, and
I know for certain it is not defined on macOS.

Link: https://specifications.freedesktop.org/basedir/latest/ [0]
Signed-off-by: Tristan Partin <tristan@partin.io>
Eric Bower  ·  2025-12-18
revert: don't force pty resize

This seems to be causing issues with multiple clients connected, instead
we will require clients to force resize themselves for now
Eric Bower  ·  2025-12-16
chore: publish v0.1.1
Eric Bower  ·  2025-12-16
docs(changelog): v0.1.1
Eric Bower  ·  2025-12-15
feat(list): sort by session name
Eric Bower  ·  2025-12-15
fix: send SIGWINCH to PTY on re-attach

TIOCSWINSZ alone doesn't notify the shell when terminal size is set.
Now we explicitly signal the foreground process group after setting
the window size, fixing display issues on re-attach.
Eric Bower  ·  2025-12-12
chore: remove dummy integration tests
Eric Bower  ·  2025-12-11
fix(getTerminalSize): return default size if cols and rows are 0

The issue is that `getTerminalSize` can return 0x0 even when ioctl(TIOCGWINSZ) succeeds. The ghostty terminal library panics when initialized with zero dimensions.

The fix in `getTerminalSize` now validates that both rows and cols are positive before using them, otherwise falling back to the default 24x80.

Closes: https://github.com/neurosnap/zmx/issues/25
Ordo  ·  2025-12-10
add zsh shell prompt doc (#24)

Eric Bower  ·  2025-12-10
fix: flake.nix only supports linux atm

`ghostty` does not support packaging nix on mac which I think means we will not be able to support it:

- https://github.com/ghostty-org/ghostty/blob/cf06417b7dfbd0daeb58a9143f9b6ee194cbce26/nix/package.nix#L49
- https://github.com/ghostty-org/ghostty#2824

When our flake tries to build on macOS, it pulls ghostty as a Zig dependency, and ghostty's build.zig calls into apple-sdk/build.zig which needs the Darwin SDK — but ghostty's Nix infrastructure hasn't set that up.
Eric Bower  ·  2025-12-10
feat: homebrew install
Eric Bower  ·  2025-12-09
docs: changelog spec ref
Eric Bower  ·  2025-12-09
docs: update binaries
Eric Bower  ·  2025-12-09
chore: v0.1.0
Eric Bower  ·  2025-12-09
docs: readme
Eric Bower  ·  2025-12-09
docs: readme
Eric Bower  ·  2025-12-09
docs: added tmux to comparison table
Eric Bower  ·  2025-12-09
fix: remove mac flag
Jesse Farebrother  ·  2025-12-05
feat: add version command
Jesse Farebrother  ·  2025-12-05
chore: build.zig version build option
Eric Bower  ·  2025-12-05
refactor!: postfix uid for socket dir

- first we look for TMPDIR env var or else we default to `/tmp`
- then we look for ZMX_DIR env var or else we create a `/tmp/zmx-{uid}` folder

Since this is a breaking change, you can kill all of your previous
sessions by using `ZMX_DIR=/tmp/zmx zmx kill {sesh}`

Closes: https://github.com/neurosnap/zmx/issues/15

BREAKING CHANGE: this moves sockets to a new location which means none
of your previous sessions will be available
Eric Bower  ·  2025-12-08
docs: comparison table
Eric Bower  ·  2025-12-06
docs: index site content
Eric Bower  ·  2025-12-06
feat: docs site
Eric Bower  ·  2025-12-06
fix(attach): cannot attach to session within session

Closes: https://github.com/neurosnap/zmx/issues/20
Eric Bower  ·  2025-12-05
docs(readme): fish prompt file loc
Eric Bower  ·  2025-12-05
chore(release): add shasum files as well
Eric Bower  ·  2025-12-05
fix(release): strip mac metadata
Eric Bower  ·  2025-12-05
fix(release): strip apple xattrs
Eric Bower  ·  2025-12-05
docs: links for binaries
Britton Robitzsch  ·  2025-12-05
add nix flake (#5)

Eric Bower  ·  2025-12-05
fix(macos): release build
Eric Bower  ·  2025-12-05
chore: upload script
Eric Bower  ·  2025-12-05
feat: add release build
Eric Bower  ·  2025-12-05
fix: support kitty keyboard ctrl+\ with event flags

References: https://github.com/neurosnap/zmx/issues/10
Eric Bower  ·  2025-12-05
chore: add kitty graphics protocol example script
Eric Bower  ·  2025-12-03
refactor: when daemon is unresponsive, remove it

This commit does a better job removing a unix socket file when
the daemon is unresponsive, likely indicating that the process has
already been killed.

We accomplish this by creating a probSession fn that will connect to the
unix socket, send the Info event, and timeout after 1 second.  After
that point we remove the unix socket file.
Eric Bower  ·  2025-12-03
fix: send SIGTERM to pty child process before shutting down

The issue was that when `zmx kill` was called, the daemon would call `shutdown()` and exit the loop, but then get stuck at `waitpid(daemon.pid, 0)` waiting for the PTY child shell to exit. Since the shell was never killed, the daemon would hang forever, preventing the defer block from running that deletes the socket file.

The fix sends SIGTERM to the PTY child process before shutting down, allowing the shell to exit gracefully so waitpid returns and the socket cleanup defer runs.

References: https://github.com/neurosnap/zmx/issues/10
Eric Bower  ·  2025-12-03
fix: print help text with unknown commands

We should print the help text when possible and refrain from using the
logger when the user provides invalid input since that gets sent to our
log files.

Closes: https://github.com/neurosnap/zmx/issues/11
Eric Bower  ·  2025-12-03
fix: ghostty should not send color pallet on re-attach

When `zmx` starts a session, `ghostty-vt.Terminal.init()` uses Ghostty's default palette, not foot's palette. On reattach, it dumps all 256 colors from ghostty's defaults, which may differ from foot's defaults.

References: https://github.com/neurosnap/zmx/issues/7
Eric Bower  ·  2025-12-03
feat: increase max scrollback

Closes: https://github.com/neurosnap/zmx/issues/9
Daniel Wennberg  ·  2025-12-03
Bump minimum Zig version (#6)

Eric Bower  ·  2025-12-02
fix: dont send ghostty snapshot on first attach

chore: clear main screen and set cursor to home on first attach
Eric Bower  ·  2025-12-02
fix: dont use alt screen on attach

The problem is that the client switches to the alternate screen buffer on attach, which doesn't have scrollback. When ghostty replays content, it goes into the alt screen where scrollback doesn't accumulate.

The alternate screen buffer (\x1b[?1049h) never has scrollback - that's by design in terminals.

To get scrollback working properly we now stay on main screen and clear on detach.
Eric Bower  ·  2025-12-02
docs: readme
Eric Bower  ·  2025-12-02
docs: readme
Eric Bower  ·  2025-12-01
docs: readme
Eric Bower  ·  2025-12-01
docs: readme
Eric Bower  ·  2025-12-01
docs: readme
Eric Bower  ·  2025-12-01
docs: readme
Eric Bower  ·  2025-12-01
docs: readme
Eric Bower  ·  2025-11-29
chore: license
Eric Bower  ·  2025-11-29
docs: readme
Eric Bower  ·  2025-11-29
chore: logo
Eric Bower  ·  2025-11-29
chore: logo
Eric Bower  ·  2025-11-29
docs: logo
Eric Bower  ·  2025-11-29
chore: update logo
Eric Bower  ·  2025-11-29
docs: logo
Eric Bower  ·  2025-11-29
docs: readme
Eric Bower  ·  2025-11-28
docs: readme
Eric Bower  ·  2025-11-28
feat: shorthand aliases for commands
Eric Bower  ·  2025-11-28
docs: readme
7f18a23
(v0.0.1)
Eric Bower  ·  2025-11-28
docs: readme
Eric Bower  ·  2025-11-27
feat: render terminal snapshot to client on re-attach

This change uses libghostty-vt to restore the previous state of the
terminal when a client re-attaches to a session.

How it works:

- user creates session `zmx attach term`
- user interacts with terminal stdin
- stdin gets sent to pty via daemon
- daemon sends pty output to client *and* ghostty-vt
- ghostty-vt holds terminal state and scrollback
- user disconnects
- user re-attaches to session
- ghostty-vt sends terminal snapshot to client stdout

In 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.
Eric Bower  ·  2025-11-27
chore: cleanup build files
Eric Bower  ·  2025-11-27
docs: clarify detach cmd
Eric Bower  ·  2025-11-26
chore: move code around
Eric Bower  ·  2025-11-26
docs: readme
Eric Bower  ·  2025-11-26
docs: readme
Eric Bower  ·  2025-11-26
fix: discard any unread input when restoring terminal settings on detach
Eric Bower  ·  2025-11-26
feat: handle resize events
Eric Bower  ·  2025-11-26
fix: free socket_path and the clients array backing storage before the daemon process exits
Eric Bower  ·  2025-11-26
fix: close the server socket and delete the socket file if spawnPty fails
Eric Bower  ·  2025-11-26
fix: close the PTY master fd when the daemon exits
Eric Bower  ·  2025-11-26
fix: client socket fd ensure the socket is closed when the function exits
Eric Bower  ·  2025-11-26
fix: ensure the fd is closed if initUnix, bind, or listen fails
Eric Bower  ·  2025-11-26
fix: reap the pty child process and prevent zombies
Eric Bower  ·  2025-11-26
fix: use relative path since we are already inside the socket_dir
Eric Bower  ·  2025-11-26
fix: properly allocate NUL-terminated copies of each argument

the child process will exec (replacing memory) or exit, so no cleanup needed.
Eric Bower  ·  2025-11-26
feat: provide attach with a command
Eric Bower  ·  2025-11-26
docs: readme
Eric Bower  ·  2025-11-26
docs(readme): shell prompt section
Eric Bower  ·  2025-11-26
refactor: cleanup logs
Eric Bower  ·  2025-11-26
feat: on re-attach send SIGWINCH and in-band resize event
Eric Bower  ·  2025-11-26
feat: send logs to a file with rotation
Eric Bower  ·  2025-11-25
fix: cpu spike from unhandled events in our daemon and client loops
Eric Bower  ·  2025-11-24
docs: readme
Eric Bower  ·  2025-11-24
feat: ctrl+\ detach client
Eric Bower  ·  2025-11-23
docs: readme
Eric Bower  ·  2025-11-23
feat: list command
Eric Bower  ·  2025-11-23
chore(docs): readme
Eric Bower  ·  2025-11-23
daemon-per-session (#4)

This is a ground-up rewrite of this tool.  We have dramatically simplified how this tool works.  Instead of having a single daemon that manages all sessions, we have a daemon-per-session.

We employ a simple architecture: `fork() -> setsid() on child -> forkpty() -> attach on parent`

This has a bunch of benefits, one of which is we can safely remove libxev and use simple `poll()` loops for daemon and clients.
Eric Bower  ·  2025-10-16
feat: attach will start in the cwd of client
Eric Bower  ·  2025-10-16
shore: fix cursor error
Eric Bower  ·  2025-10-15
fix: filter our client input queries from echoing to stdout
Eric Bower  ·  2025-10-15
chore: centralize winsize change logic
Eric Bower  ·  2025-10-15
feat: in-band resize window events

https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
Eric Bower  ·  2025-10-15
fix: flush buffer so prompt always shows after cmd
Eric Bower  ·  2025-10-15
fix: properly clear terminal after detach
Eric Bower  ·  2025-10-15
fix: reattach rendering
Eric Bower  ·  2025-10-15
fix: restore errors
Eric Bower  ·  2025-10-15
docs: render
Eric Bower  ·  2025-10-15
chore: tests
Eric Bower  ·  2025-10-15
feat: rendering colors on reattach
Eric Bower  ·  2025-10-15
chore: cursor position and escape sequences
Eric Bower  ·  2025-10-15
chore: Wide/Double-Width Character Handling
Eric Bower  ·  2025-10-15
refactor: move render terminal snapshot to separate module
Eric Bower  ·  2025-10-15
chore(reattach): row iteration
Eric Bower  ·  2025-10-15
chore: checkpoint
Eric Bower  ·  2025-10-14
fix: need clrf
Eric Bower  ·  2025-10-14
perf: increase buffer sizes
Eric Bower  ·  2025-10-14
chore: fmt
Eric Bower  ·  2025-10-14
feat: restore terminal state
Eric Bower  ·  2025-10-14
feat: restore modes
Eric Bower  ·  2025-10-14
feat: restore terminal modes
Eric Bower  ·  2025-10-14
chore: reset
Eric Bower  ·  2025-10-14
refactor: renderTerminalSnapshot
Eric Bower  ·  2025-10-14
refactor: renderTerminalSnapshot
Eric Bower  ·  2025-10-14
docs: fmt
Eric Bower  ·  2025-10-14
refactor: renderTerminalSnapshot
Eric Bower  ·  2025-10-13
refactor: increase pty buffer sizes

This improves performance when writing to ghostty-vt
Eric Bower  ·  2025-10-13
chore: remove pty json handling
Eric Bower  ·  2025-10-13
chore: suppress ghostty-vt warnings
Eric Bower  ·  2025-10-13
refactor: remove sanitized buffer for now
Eric Bower  ·  2025-10-13
refactor(daemon): we dont need to buffer pty read for partial utf8

ghostty already handles this for us.
Eric Bower  ·  2025-10-13
fix: replace to device attribute queries within daemon
Eric Bower  ·  2025-10-13
chore: rm scrollback buffer

ghostty handles this for us
Eric Bower  ·  2025-10-13
refactor: revert env var
Eric Bower  ·  2025-10-13
chore: forward more env vars from daemon to children ptys
Eric Bower  ·  2025-10-13
fix: forward TERM to pty
Eric Bower  ·  2025-10-13
fix: change detach key to ctrl+b d like tmux
Eric Bower  ·  2025-10-13
fix: move DA query handling to proper location
Eric Bower  ·  2025-10-13
fix(attach): when daemon shuts down don't print EOF msg in attach
Eric Bower  ·  2025-10-13
fix(attach): carriage return required in raw mode
Eric Bower  ·  2025-10-13
fix: respond to Device Attribute queries to prevent fish shell warning

Fish shell sends a Primary Device Attribute (DA1) query (ESC [ c) when
it starts to detect terminal capabilities. Previously, zmx forwarded
this query to the PTY but never sent a response back to the client,
causing fish to wait 2 seconds and display a terminal compatibility
warning.

This commit intercepts DA queries in handlePtyInput() before they reach
the PTY and responds directly to the client with VT220 capabilities:
- Primary DA (ESC [ c): VT220 with color (22) and clipboard (52)
- Secondary DA (ESC [ > c): Terminal version info

The response format matches ghostty's deviceAttributes implementation.

Fixes the "fish could not read response to Primary Device Attribute
query" warning on first attach.
Eric Bower  ·  2025-10-13
fix(attach): use-after-free bug
Eric Bower  ·  2025-10-13
docs(protocol): pty binary format
Eric Bower  ·  2025-10-13
fix(attach): pty_out needs to use binary protocol
Eric Bower  ·  2025-10-13
refactor(attach): use xev.Stream for stdout writes
Eric Bower  ·  2025-10-12
refactor: use binary format for pty_in and pty_out
Eric Bower  ·  2025-10-12
fix: vt.resize on attach

fix: filter out characters ghostty cannot handle
Eric Bower  ·  2025-10-12
refactor: resize ghostty term on attach
Tim Culverhouse  ·  2025-10-13
support macOS and FreeBSD PTY functions (#1)

Replace Linux-specific PTY headers with platform-specific imports
to enable cross-platform compatibility. On macOS, use util.h for
openpty(). On FreeBSD, use libutil.h for openpty(). On Linux and
other platforms, continue using pty.h.

Remove unused utmp.h include that was never referenced in the code.

This change allows the daemon to compile and run on macOS and
FreeBSD in addition to Linux without requiring platform-specific
preprocessor directives or build configurations.

Amp-Thread-ID: https://ampcode.com/threads/T-15fd58fa-5673-40c5-87e1-3ea0057599ca

Co-authored-by: Amp <amp@ampcode.com>
Eric Bower  ·  2025-10-11
refactor: move detach key to Config struct
Eric Bower  ·  2025-10-11
fix: segfault on daemon after existing pty
Eric Bower  ·  2025-10-11
fix: memory leak
Eric Bower  ·  2025-10-11
refactor: use new protocol module
Eric Bower  ·  2025-10-11
refactor: move unix ipc protocol to separate module
Eric Bower  ·  2025-10-11
refactor: test
Eric Bower  ·  2025-10-11
fix: use xev for pty writes
Eric Bower  ·  2025-10-11
chore: pass config struct to all subcommands
Eric Bower  ·  2025-10-11
feat: config toml
Eric Bower  ·  2025-10-11
feat: systemd unit file

docs: agents+readme
Eric Bower  ·  2025-10-10
fix: detach shortcut
Eric Bower  ·  2025-10-10
fix: alt-screen
Eric Bower  ·  2025-10-10
fix: more cleanup
Eric Bower  ·  2025-10-10
feat: version
Eric Bower  ·  2025-10-10
fix: allow zig optimization

fix: help text
Eric Bower  ·  2025-10-10
fix: initial attach clear terminal
Eric Bower  ·  2025-10-10
feat: terminal restore works!
Eric Bower  ·  2025-10-10
fix: restore session
Eric Bower  ·  2025-10-10
fix: ghostty impl
Eric Bower  ·  2025-10-10
docs: readme
Eric Bower  ·  2025-10-10
docs: readme
Eric Bower  ·  2025-10-10
docs: readme
Eric Bower  ·  2025-10-10
feat: init