@ictechgy/lterm
v1.0.4
Published
Lightweight tmux-compatible terminal session daemon with cmux-friendly notifications.
Maintainers
Readme
Light Terminal (lterm)
한국어 | English
TL;DR
- What — A persistent terminal session daemon (like tmux, but smaller) with a tmux-compatible command layer for AI agent tooling. Detach and reattach by name or pane id.
- Who it's for — Terminal-first coding agents such as Claude Code, Codex CLI, OpenCode, GitHub Copilot CLI, Cursor Agent, Antigravity/
agy, Kiro, Jules, Aider, Goose, Amp, Crush, Kimi, Qwen, Gemini CLI,oh-my-codex/oh-my-claude, and users running them insidecmux. - How —
lterm startto create,lterm resumeto (re)connect,lterm agent <profile>/lterm claude/lterm codex/lterm opencode/lterm agy/lterm kiro/lterm geminiand other built-in shortcuts for shimmed agent runs. Inside a tmux-enabled session, thetmuxcommand resolves tolterm tmux-compat. - Status — alpha MVP. A same-user convenience daemon — not a sandbox, an escape-sequence sanitizer, or a full tmux replacement.
lterm is intentionally smaller than tmux. It keeps long-running PTY sessions alive, lets clients detach and reattach at will, forwards terminal escape sequences unchanged, and translates the subset of tmux commands commonly used by terminal-first agent tooling.
Security model:
ltermis a same-user convenience daemon, not a sandbox. It rejects cross-user Unix-socket peers and uses owner-only runtime directories, but any process running as your OS user should be considered capable of controlling your sessions. See SECURITY.md for the full trust-boundary and audit policy details. Non-goals: see docs/non-goals.md.
Why lterm instead of plain tmux?
Use tmux when you want a full terminal multiplexer with rich pane/window/layout
management. Use lterm when you want the smaller surface that AI agents usually
need:
- Agent-first persistence — named PTY sessions keep running across detached clients without requiring every workflow to manage a full tmux server.
- tmux-compatible where agents expect it —
lterm tmux-compatimplements the command subset used by Claude Code, Codex CLI, OpenCode, GitHub Copilot CLI, Cursor Agent, Antigravity/agy, Kiro, Jules, Aider, Goose, Amp, Crush, Kimi, Qwen, Gemini CLI, OMX/OMC, and similar terminal-first tooling. - Raw attach, safe reports — attached PTY streams remain raw for TUIs and
interactive shells, while
logs,capture,list,doctor, and other report surfaces sanitize terminal control sequences before printing. - cmux-friendly by design — notifications and tmux shim calls are shaped for cmux/agent pane orchestration instead of generic desktop multiplexing.
- Built-in observability —
doctor/status, boundedlogs --start/--end,wait/watch, andprocesses --orphansmake daemon, scrollback, completion, and subprocess state easy for humans or agents to inspect.
Why this exists
The project addresses three constraints:
- tmux-like persistence and remote access — sessions run inside a background daemon and can be attached or detached by name or pane id. Remote access is available through
lterm ssh, providedltermis installed on the remote host. - cmux compatibility — when running inside cmux,
ltermpreserves OSC notifications, exposeslterm notify, and the tmux shim opens worker panes as native cmux splits when possible. - AI tooling support —
lterm agent <profile>,lterm claude,lterm codex,lterm opencode,lterm copilot,lterm cursor-agent,lterm agy,lterm jules,lterm kiro,lterm aider,lterm goose,lterm amp,lterm crush,lterm kimi,lterm qwen,lterm gemini,lterm omx,lterm omc, andlterm install-shimprovide a faketmuxcommand and theTMUX/TMUX_PANEenvironment variables that agent tools expect.
cmux compatibility is grounded in cmux's documented behavior: notifications via cmux notify and OSC 777 / OSC 99, a Unix-socket/CLI API for workspaces and splits, and a tmux shim that maps tmux commands into native cmux panes.
Install
With Homebrew:
brew install ictechgy/tap/ltermWith npm on supported macOS/Linux platforms:
npm install -g @ictechgy/ltermHomebrew and npm both install the lterm command on your PATH; verify with lterm --version.
Too lazy to install it manually? Copy the prompt in
docs/agent-install.md into Claude Code, Codex CLI,
OpenCode, GitHub Copilot CLI, Cursor Agent, Antigravity/agy, Kiro, Jules, Aider, Goose, Amp, Crush, Kimi, Qwen, Gemini CLI, or another terminal coding agent. It asks the agent to detect your
platform, install lterm, verify it with a smoke test, and avoid modifying
shell startup files without showing you the change.
For the 1.0 command/output stability boundary, see the public contract and its machine-readable contract manifest.
With Cargo from GitHub (use the latest tag from the Releases page):
cargo install --git https://github.com/ictechgy/light_terminal --tag v1.0.4From this checkout, use Rust 1.85 or newer:
cargo build --release --locked
./target/release/lterm --helpFor local development:
cargo run -- --helpTo expose the tmux shim:
lterm install-shim
# Add the printed directory to PATH ahead of the real tmux, or eval the helper:
eval "$(lterm env)"
# fish:
lterm env --shell fish | sourceQuick start
Create a persistent session and attach immediately:
lterm start -n api -- npm run devCreate detached, attach later:
lterm start -d -n api -- npm run dev
lterm resume api
# Compatibility names remain available:
lterm attach api
lterm a api
# `-a` goes right after `lterm`, separated from the target by a space.
lterm -a apiAgent-terminal command vocabulary:
| Task | General command | Compatibility names |
| --- | --- | --- |
| Start a persistent process | lterm start -n api -- npm run dev | new |
| Run a command with tmux compatibility enabled | lterm run -- codex exec "summarize" | None (--no-tmux opts out) |
| Open or create a session | lterm open main | attach-or-new |
| Resume an existing session | lterm resume api | attach, a, -a |
| List sessions | lterm sessions | list, ls |
| Inspect process trees | lterm processes api --json --orphans | ps |
| Rename a session | lterm rename api api-renamed | None |
| Set a session status theme | lterm status-theme api green | theme |
| Read sanitized scrollback | lterm logs api --start=-80 --end=-1 | capture |
| Open a sanitized scrollback composer for input | lterm compose api | mobile |
| Wait for session output or exit | lterm wait api --contains READY --timeout 30s --json | None |
| Watch a session and notify on completion | lterm watch api --exit --notify | None |
| Write input to a PTY | lterm input api 'echo hello' --enter | send |
| Stop a session | lterm close api | kill |
| Diagnose daemon and shim state | lterm doctor --json | status |
| Preview local setup steps | lterm init --shell zsh | None |
| Run the background daemon explicitly | lterm daemon | None |
| Stop the daemon and all sessions | lterm shutdown | None |
Agent and shim utilities are also product CLI commands, not tmux aliases:
| Task | Product command | Compatibility boundary |
| --- | --- | --- |
| Launch a profiled agent session | lterm agent claude -- --help | Sibling shortcuts: lterm claude, lterm codex, lterm opencode, lterm copilot, lterm cursor-agent, lterm agy, lterm jules, lterm kiro, lterm aider, lterm goose, lterm amp, lterm crush, lterm kimi, lterm qwen, lterm gemini, lterm omx, lterm omc |
| Inspect available agent profiles | lterm agents --json | PATH availability probe at command runtime |
| Install the tmux compatibility shim | lterm install-shim | Creates a shim that forwards to lterm tmux-compat |
| Print shell exports for tmux compatibility | eval "$(lterm env)" (lterm env --shell fish \| source for fish) | Emits trusted shell setup that prepends the shim dir to $PATH |
| Send a cmux-friendly notification | lterm notify --title 'Done' --body 'Tests passed' | OSC 777 fallback strips terminal controls while preserving Unicode text |
| Attach to a remote host | lterm ssh user@host main | Use trusted hosts; SSH handles host-key checks, and remote PTY bytes pass through without sanitization |
| Call the tmux shim namespace directly | lterm tmux-compat list-commands | Compatibility namespace, not a product alias table |
Use eval "$(lterm env)" only when you trust the lterm binary on your PATH.
It emits fixed export lines that prepend the shim directory to $PATH.
For fish, use lterm env --shell fish | source after the same trust check.
lterm ssh forwards remote PTY bytes to the local terminal without sanitizing
terminal control sequences, so a compromised remote can drive terminal features
that your local emulator permits: OSC 52 clipboard writes, OSC 8 hyperlinks,
window/title changes, cursor or screen manipulation, bracketed paste toggles, and
any emulator-specific escape handling. Treat it like direct ssh to a trusted
host and configure terminal features accordingly. "cmux-friendly" notification
means the fallback path emits the OSC 777 notification format that cmux watches.
The OSC 777 fallback sanitizer protects protocol framing; it does not normalize
Unicode bidi, format, or zero-width characters inside trusted title/body text.
Compatibility names are subcommands unless shown as a leading flag: -a is the legacy shortcut form and must be used as lterm -a <target>.
This table is the product CLI surface for humans and agents. lterm tmux-compat ... is a separate shim namespace for scripts that already speak tmux; not every product command has a tmux-compatible spelling. Use lterm tmux-compat list-commands to inspect the supported shim subset at runtime.
lterm sessions hides child panes by default, preserves the original first five tab-separated columns (name, pane, alive, cwd, command), then appends attach state (attached / detached) and parent pane (- or a pane id). The compatibility names lterm list and lterm ls keep the same output shape. Attached clients render a small status bar on the bottom row showing the current session and pane; the PTY is resized to the remaining rows. For the older raw full-terminal resume, use lterm resume --no-status api (or compatibility name lterm attach --no-status api), or set LTERM_NO_STATUS=1 / LTERM_STATUS=0 for clients whose status-line handling conflicts with lterm.
lterm rename <target> <new-name> renames a running session without restarting its process. Renaming a session to its current name is a no-op success, while renaming over a different in-use name fails with a conflict error. <target> accepts a session name, session id, pane id (%0), or bare pane number (0); <new-name> follows the same syntax rules as --name.
lterm status-theme <target> <theme> (alias: lterm theme) stores a per-session status bar theme without restarting the PTY; pane ids resolve to their session. Use default, clear, or none to remove the session override and return to the attaching client's default. Already-attached clients keep their current status color until they detach and reattach. New sessions can set the same metadata at creation time with lterm start --status-theme green -n api -- npm run dev (or alias --status-color).
lterm doctor (compatibility name: lterm status) reports client/daemon versions, protocol compatibility, runtime/data/socket/shim paths, and whether the shim directory is on PATH. It does not start the daemon; daemon_reachable=no / false means no compatible daemon answered on the current socket. Normal client operations warn on stderr when a reachable daemon reports a different lterm or protocol version, which usually means an old daemon survived a binary upgrade.
lterm logs <target> accepts --start / -S and --end / -E line offsets. Non-negative values are absolute scrollback line indexes; negative values count back from the current scrollback line count. --end is inclusive, so lterm logs api -S0 -E0 captures only the first line. Capture output remains sanitized text; attached PTY streams remain raw.
lterm wait <target> --exit|--contains <text> blocks until a session exits or its sanitized scrollback contains a marker. Add --timeout 250ms|2s|5m|1h, --tail N, and --json for automation-friendly health checks. On timeout, wait / watch return exit code 124 and JSON reports timed_out: true. lterm watch uses the same conditions and can add --notify to emit a cmux-friendly completion notification without altering attached PTY bytes; with --json, stdout stays machine-readable even when notification fallback is needed.
Set LTERM_STATUS_STYLE=full or LTERM_STATUS_STYLE=minimal to choose the visual style. full (default for local terminals) draws a colored bar; minimal drops all SGR colors in favor of plain text. SSH sessions (detected via SSH_CONNECTION, SSH_CLIENT, or SSH_TTY) default to minimal to avoid color-mapping issues on mobile SSH clients like Termius, unless a session or environment theme is explicitly set.
Set LTERM_STATUS_THEME=blue|green|magenta|cyan|amber|red|gray|plain to change the default colored status bar for the attaching client. Per-session overrides win over the environment: lterm start --status-theme amber -n api -- npm run dev, lterm run --status-color cyan -- cargo test, or lterm status-theme api plain. If you export this variable from shell startup files, it also opts SSH attaches into colored status bars; leave it unset or set LTERM_STATUS_STYLE=minimal on mobile SSH clients that need plain text. Theme names are parsed from a fixed allowlist; lterm never injects arbitrary user-provided terminal escape sequences into the status row.
Status bar customization
Status bar themes were added in v0.1.3. They are metadata-only: changing a theme never restarts the PTY, never changes the attached PTY byte stream, and never accepts arbitrary terminal escape sequences from user input.
Use the narrowest scope that matches what you want:
| Scope | Example | When to use it |
| --- | --- | --- |
| One new session | lterm start --status-theme green -n api -- npm run dev | Keep a service or agent session recognizable across future attaches. |
| Existing session | lterm status-theme api amber | Recolor a running session without restarting its process. |
| Agent launcher session | lterm codex --status --status-color cyan -- exec "summarize" | Give an agent-owned session a persistent color while preserving long-only launcher controls. |
| Attaching client default | export LTERM_STATUS_THEME=magenta | Change the default for sessions that do not have their own override. |
| Plain/minimal clients | export LTERM_STATUS_STYLE=minimal | Prefer text-only status on mobile SSH clients or terminals with fragile color mapping. |
Allowed themes are intentionally fixed:
| Theme | Good fit |
| --- | --- |
| blue | Default local status bar. |
| green | Long-running services or healthy/background tasks. |
| magenta | Agent or review sessions you want to spot quickly. |
| cyan | Build/test/dev-tool sessions. |
| amber | Watch/diagnostic sessions that need attention. |
| red | Risky, destructive, or production-adjacent sessions. |
| gray | Low-priority background sessions. |
| plain | No colored bar; useful when ANSI colors are distracting but the status row is still helpful. |
Reset a session override with lterm status-theme api default (or clear / none). Already-attached clients keep their current rendered color until detach/reattach, so scripted changes are safe to apply while a human is attached.
When the attached PTY enters the alternate screen buffer (e.g. vim, less, htop via \x1b[?1049h), lterm suspends its status bar to avoid conflicting with the application's UI. The status bar is redrawn immediately when the application exits alt-screen.
If lterm resume / lterm attach panics or aborts mid-session, a process-wide hook emits a minimal recovery sequence (scroll region reset, cursor visible, alt-screen exit, SGR reset) so the user's terminal isn't left in raw mode or with hidden cursor.
Session names containing CJK characters or emoji (including ZWJ families, country flags, and combining marks) are aligned by display width using unicode-width and unicode-segmentation, so the status bar stays correctly padded across mixed-width content.
When a child application enables the Kitty keyboard protocol through CSI u enhancement sequences, lterm tracks that and best-effort restores the terminal keyboard mode when attach exits so a crashed child does not leave later shell input looking like 1;1:3u escape fragments.
Inspect or control a session:
--children includes managed child panes; --all includes sessions that are normally hidden from the default list.
lterm sessions
lterm sessions --children
lterm sessions --all
lterm processes api --orphans
lterm logs api --start=-80 --end=-1
lterm compose api
lterm wait api --contains READY --timeout 30s --json
lterm watch api --exit --notify
lterm input api 'echo hello' --enterThe generic aliases above are meant for day-to-day agent-terminal use: sessions lists persistent work, processes inspects child process trees, logs reads sanitized scrollback, compose shows sanitized scrollback with a fixed bottom prompt for committing text, wait / watch make marker-or-exit conditions observable for scripts and agents, and input writes text to the target PTY. mobile is a visible alias for compose; the compatibility names list / ls, ps, capture, and send remain available for scripts and muscle memory.
For automation and tests, lterm compose api --once --message 'hello' performs one sanitized capture/send cycle. It captures the last --tail sanitized lines (default: 80) from the same session-or-pane target model as logs, then appends Enter (\r) by default, matching lterm input --enter; add --no-enter to send the exact message bytes. compose / mobile is not an attach client and does not change attached-client counts or PTY geometry.
In interactive compose, the view refreshes on --refresh (default: 500ms) and after local input or resize events. Pressing Enter commits the current input buffer (empty buffers are committed too) and appends \r by default, matching the one-shot rule above. Ctrl-C, Ctrl-D, and Esc exit the local composer instead of forwarding to the PTY.
Stop a session:
lterm close apikill is a visible compatibility alias for close; both names use the same session/pane termination path.
Run the daemon explicitly (advanced):
# Client commands start this on demand; run it directly for supervisors/debugging.
lterm daemonStop the daemon and every session it owns:
# This is daemon-wide, not a single-session close.
lterm shutdownAI workflows
Run common agent CLIs inside shimmed sessions:
lterm claude
lterm codex
lterm opencode
lterm copilot
lterm cursor-agent
lterm agy -- -p "summarize this repo"
lterm kiro
lterm jules
lterm aider
lterm goose
lterm amp
lterm crush
lterm kimi
lterm qwen
lterm gemini -- -p "summarize this repo" # legacy-compatible
lterm agentsThese are thin profile aliases for:
lterm agent claude
lterm agent codex
lterm agent opencode
lterm agent cursor-agent
lterm agent agy -- -p "summarize this repo"
lterm agent qwen
lterm agent gemini -- -p "summarize this repo"Agent launchers accept the same session controls across built-in profiles and custom lterm agent <profile> launches:
lterm claude --name repo-review --cwd /path/to/repo
lterm codex --detach --name repo-codex -- exec "summarize this repo"
lterm agy --status -- -p "keep lterm status visible"Known Claude/Codex/OpenCode/Copilot/Cursor Agent/Antigravity/Kiro/Jules/Aider/Goose/Amp/Crush/Kimi/Qwen/Gemini profiles default to a raw full-terminal attach without the lterm status bar, so their own TUI/status/alternate-screen rendering stays in control. OMX/OMC keep the lterm status bar visible by default for continuity with their existing lightweight status/HUD workflow; use --no-status if a specific OMX/OMC run needs a fully raw surface. Use --status to force the lterm status bar on, or --no-status to suppress it for any launch/profile that would otherwise show it. Put -- before agent arguments that could be parsed as lterm launch options. lterm agent <name> also works for any safe bare command name available in PATH (for example lterm agent qwen-code); use lterm run -- <command> only when you want the lower-level tmux-compatible primitive directly.
Launcher controls are long-only (--name, --cwd, --detach, --status, --no-status, --status-theme) so common agent short flags such as -c pass through naturally. They apply uniformly to built-in agent shortcuts such as claude, codex, opencode, copilot, cursor-agent, agy, kiro, jules, aider, goose, amp, crush, kimi, qwen, gemini, omx, omc, and agent <profile>. Use --status-theme / --status-color on agent launches when you want that session to keep a specific lterm status color across future attaches.
--detach prints name<TAB>pane<TAB>command with control characters and Unicode line/paragraph separators in each field replaced by spaces; resume later with lterm resume <name> or compatibility name lterm attach <name>. The detach record does not echo --cwd; query the session if you need to inspect it later.
Explicit --name values use lterm's normal session-name syntax and must be free; they do not auto-suffix on conflict, so an in-use name fails with a conflict error.
Names may contain ASCII letters, digits, ., _, and -, must not start with - or %, must not consist only of digits, must not look like a UUID, and are limited to 128 bytes.
Use lterm agents (or lterm agents --json) to inspect profile defaults and whether their binaries are currently available in PATH. JSON rows use kind values of built-in, custom, or configured. Pass profile names, such as lterm agents codex my-agent --json, to inspect a selected built-in/custom/configured set; availability is a point-in-time PATH probe. Built-ins resolve the binary names shown by lterm agents; most match the profile name, while kiro resolves kiro-cli. If a provider installs a different command name, use lterm agent <command> or a configured profile rather than relying on a guessed alias.
For reusable custom aliases, pass an explicit JSON config file:
cat > agents.json <<'JSON'
{ "profiles": [{ "name": "repo-review", "binary": "codex", "session_base": "repo-review-session", "status_default": false }] }
JSON
lterm agents --agent-config agents.json --json
lterm agent repo-review --agent-config agents.json -- exec "review this repo"Configured names and binaries use the same safe profile syntax as lterm agent <profile>; built-in names cannot be redefined.
binary must be a bare command name resolved from PATH, not a shell fragment or path. binary defaults to name, session_base defaults to <name>-lterm, status_default defaults to true and must be a boolean when present, duplicate names and unknown JSON fields are rejected, and when --agent-config is supplied non-built-in selected names must exist in that file.
Run Oh My Codex inside a shimmed session:
lterm omx team
# Extra omx flags are passed through, e.g.:
lterm omx --madmax --xhighRun Oh My Claude similarly:
lterm omc team
# The OMC builds tested here reject --xhigh — use --madmax alone unless your
# installed `omc --help` explicitly lists --xhigh.
lterm omc --madmaxRun any command with tmux compatibility enabled:
lterm run -- omx hud --tmux
lterm run -- claude
lterm run -- codex exec "summarize the repository"Inside that session, tmux resolves to the lterm tmux-compat shim. This is a compatibility layer, not a second spelling of every lterm product command. The shim implements the command subset most AI orchestration scripts rely on:
- Sessions —
new-session,attach-session,has-session,list-sessions,rename-session,kill-session - Queries —
list-windows,list-clients,list-commands,show-options,show-window-options - Panes —
split-window,list-panes,display-message,capture-pane,send-keys,kill-pane,resize-pane - Buffers / popups —
display-popup,wait-for,load-buffer,save-buffer,paste-buffer - No-op compatibility —
select-pane,select-layout,set-option,set-window-option,set-environment,show-environment
Compatibility notes: lterm models each root session as one pseudo-window
(window_index=0, window_panes=1). client_pid and client_tty expand to
empty strings because lterm does not expose per-client process or TTY metadata.
tmux -f filters are intentionally rejected instead of being silently ignored.
Use lterm tmux-compat list-commands --verbose for tab-separated command, alias, support tier, and usage fields, or --json for machine-readable rows. Support tiers are full, partial, and noop within lterm's compatibility boundary. Set LTERM_DEBUG_TMUX=1 to emit an opt-in stderr diagnostic row when an unsupported tmux command reaches the shim.
cmux behavior
When lterm tmux-compat split-window detects cmux (via CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, or a cmux socket), it:
- Starts a new
ltermPTY session for the worker command. - Asks cmux to create a native split (
cmux new-split right/down). - Sends the compatibility command
lterm attach <pane>into that split, so cmux panes still work ifLTERM_BINpoints at an olderltermbuild that predatesresume.
This gives cmux a real pane to decorate while lterm retains scrollback capture and send-keys compatibility.
Notifications:
lterm notify --title 'Task complete' --body 'All checks passed'lterm notify first tries cmux notify. If that's unavailable, it emits OSC 777 so cmux or another compatible terminal can still surface the notification. Notification fields are stripped of terminal control characters before falling back to OSC; subtitle/body separators such as newlines are normalized to spaces rather than concatenated.
For agent workflows, prefer lterm watch <target> --exit --notify or lterm watch <target> --contains DONE --notify when the notification should be tied to a specific session condition.
Remote access
If lterm is installed on a remote machine:
lterm ssh user@host mainThis uses the same attach-or-create behavior as lterm open main on the remote host; the wire command remains lterm attach-or-new main so newer local clients still work with older remote lterm installs that do not know open. Pass SSH flags after --:
lterm ssh devbox main -- -p 2222 -i ~/.ssh/id_ed25519Architecture
- Daemon — one Unix socket per user under
$XDG_RUNTIME_DIR, with an owner-only fallback under/tmp. - PTY sessions — spawned via
portable-pty, backed by ring-buffer scrollback. - Attach protocol — the CLI sends JSON over the Unix socket, optionally reserves the bottom row for a local status bar, then streams PTY bytes.
- tmux shim — a small shell script named
tmuxforwards commands tolterm tmux-compat. - cmux bridge — optional; uses the cmux CLI when detected.
After upgrading the lterm binary, restart any already-running daemon before
relying on newly added wire-protocol behavior; existing daemon processes keep
the old code until they are stopped.
Security notes
Terminal output is forwarded as-is. lterm resume (compatibility name: lterm attach) passes PTY bytes through so full-screen terminal programs and cmux/OSC notifications keep working. The local status bar is purely a client-side decoration; use --no-status for a fully raw terminal surface. Untrusted child programs can still emit terminal escape sequences to an attached terminal — exactly as under tmux/screen. Do not use lterm as an escape-sequence sanitizer or sandbox.
Capture output is sanitized for human/AI consumption. lterm logs (compatibility name: lterm capture), lterm compose (alias: lterm mobile), and tmux capture-pane strip common terminal control sequences before printing scrollback. compose is a non-attached view that commits text through the existing input/send path; it does not transform raw attached PTY streams.
Process visibility. lterm processes [session] (or compatibility name lterm ps [session]) shows the process tree rooted at each session child, including process-group ids. Add --orphans to also include same-process-group rows that are no longer descendants of the recorded session root, so long-running Codex/OMX/MCP subprocess buildup stays visible before it becomes a memory-leak surprise. The system ps is invoked by absolute path, and malformed process rows are skipped rather than guessed at.
Socket location. Custom LTERM_SOCKET paths must live in an owner-only directory. Prefer LTERM_RUNTIME_DIR when you need an isolated socket location.
Popup commands. tmux-compat display-popup runs the requested command through the user's shell to preserve tmux-like behavior. Do not pass untrusted popup commands.
Build reproducibility. Use the committed lockfile for release builds: cargo build --release --locked. The current lockfile pins serde_json 1.0.149. Its transitive zmij dependency is part of the official serde_json package metadata on docs.rs/crates.io — not a local vendored crate.
Current limitations
- Session persistence lasts only while the daemon and host are alive — reboot/process-state restore is not implemented.
- Outside cmux,
split-windowcreates additional managed PTY sessions but does not draw a tiled in-terminal UI. - This is a compatibility subset, not a full tmux server. Scripts using advanced tmux formats or options may need additional shim commands.
- cmux pane capture is handled through
ltermsessions, not cmux scrollback APIs. - The daemon authenticates local clients via OS peer credentials and owner-only socket paths — there are no per-session ACLs yet.
- Session shutdown uses verified process-group signaling, so child trees like
shell → OMX → Codex → MCPare cleaned up together when possible. Processes that intentionally detach into a different session/process group can outlivelterm close/lterm kill; inspect them withlterm processes/lterm psor OS process tools.
Development
cargo fmt
cargo test
cargo build --lockedUse isolated runtime directories for manual testing:
TMP=$(mktemp -d)
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- start --name test -- sh -lc 'echo hi; sleep 10'
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- logs test -S=-20
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- shutdownRelease/contract preflight helpers:
scripts/release-preflight.sh --contract-only
scripts/release-preflight.sh --allow-occupied-skip --skip-audit
scripts/dependency-minor-dry-run.shUse --run-soak on scripts/release-preflight.sh only for the manual
release-gate soak profile. Use
docs/release-evidence-template.md to
capture release, audit, contract, dependency, and soak evidence before tagging or
publishing.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
