holdpty
Minimal cross-platform detached PTY. Launch commands in a pseudo-terminal, attach/view/record later.
Package details
Install holdpty from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:holdpty- Package
holdpty- Version
0.4.0- Published
- Apr 3, 2026
- Downloads
- 195/mo · 23/wk
- Author
- marcfargas
- License
- MIT
- Types
- skill
- Size
- 144.9 KB
- Dependencies
- 1 dependency · 0 peers
Pi manifest JSON
{
"skills": [
"./skills"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
holdpty
Minimal cross-platform detached PTY — a pseudo-terminal that makes programs think they're running in a real terminal. Launch commands, attach/view/record later.
Status: Beta — fully functional on Windows and Linux. CLI flags may evolve before 1.0.
Why
When you launch a long-running process (an AI agent, a build, a server) programmatically, you lose the terminal. The process runs headless — no TUI, no colors, no way to see or interact with it.
On Linux, screen or tmux solve this — but they do far too much. On Windows, nothing equivalent exists.
holdpty sits between nohup and screen:
- More than
nohup: preserves a real PTY so programs behave interactively (TUI, colors, line editing). - Less than
screen/tmux: no window management, no splits, no keybindings, no config files.
One thing, done well, composable with other tools.
Prior Art
holdpty is a spiritual successor to dtach (Ned T. Crigler) and abduco (Marc André Tanner) — minimal detach/attach tools for Unix. holdpty brings the same concept to the Node.js ecosystem with first-class Windows support via ConPTY.
Also worth noting: alden (mskala, 2025) takes a different approach — it uses named pipes instead of a PTY layer to relay the raw character stream, which preserves the outer terminal's native scrollback. The tradeoff is Linux-only and no output replay buffer.
Install
npm install -g holdpty
Prerequisites
- Node.js 18+ and npm
- holdpty uses node-pty for cross-platform PTY support. On most systems, prebuilt binaries are included. If not:
- Windows: Visual Studio Build Tools (C++ workload)
- Linux:
build-essential,python3 - macOS: Xcode Command Line Tools
Quick Start
# Launch a command in a detached PTY (returns immediately)
holdpty launch --bg --name worker1 -- node server.js
# List running sessions
holdpty ls
# Attach interactively (Ctrl+A then d to detach)
holdpty attach worker1
# Watch from another terminal (read-only, multiple viewers allowed)
holdpty view worker1
# Dump the output buffer and exit (for scripts/agents)
holdpty logs worker1
# Stop a session
holdpty stop worker1
Commands
| Command | Description | Stdout |
|---|---|---|
launch --bg |
Start session detached, return immediately | Session name |
launch --fg |
Start session in foreground (blocks until child exits) | Nothing |
attach <session> |
Interactive connection (single-writer) | Terminal takeover |
view <session> |
Read-only live stream (multiple viewers) | PTY data only |
logs <session> |
Dump output buffer to stdout, exit (supports --tail, --follow, --no-replay) |
PTY data only |
send <session> <text> |
Inject input without attaching (non-exclusive) | Nothing |
ls [--json] |
List active sessions (auto-cleans stale) | Session list |
stop <session> |
Send SIGTERM to child process | Confirmation (stderr) |
info <session> |
Show session metadata | JSON |
Launch
# --fg or --bg is required (no default — be explicit)
holdpty launch --bg --name myapp -- python train.py
holdpty launch --fg --name build -- make all
# Auto-generated name if --name omitted
holdpty launch --bg -- npm start
# Prints: npm-a3f2
# Set initial PTY dimensions (default: 120x40)
holdpty launch --bg --cols 200 --rows 50 -- /bin/zsh
The -- separator before the command is optional. This is important for PowerShell, which strips -- before it reaches the process:
# Both work:
holdpty launch --bg --name worker1 -- node server.js
holdpty launch --bg --name worker1 node server.js
Attach & Detach
holdpty attach worker1
# You're now in the session. Your keystrokes go to the PTY.
# Detach: Ctrl+A then d
# The session keeps running after detach.
Only one attachment at a time. If someone is already attached:
Error: session "worker1" has an active attachment. Use 'holdpty view worker1' for read-only access.
View (for agents, VHS, supervision)
# Read-only live stream — see exactly what the PTY is rendering
holdpty view worker1
# Multiple viewers can connect simultaneously
# view outputs real terminal data (escape sequences, TUI, colors)
# Your terminal renders it — like watching over someone's shoulder
Logs (for scripting and agents)
# Dump the ring buffer contents and exit
holdpty logs worker1
# Last 50 lines only
holdpty logs worker1 --tail 50
# Follow: replay buffer + keep streaming live output (like tail -f)
holdpty logs worker1 --follow
holdpty logs worker1 -f --tail 50
# Live output only, skip buffer replay
holdpty logs worker1 -f --no-replay
# Pipe-friendly
holdpty logs worker1 --tail 20 | grep ERROR
Send (for automation and orchestration)
# Send text to a session without attaching (like tmux send-keys)
holdpty send worker1 "npm test"
# Send from a pipe
echo "exit" | holdpty send worker1 --stdin
# Multiple senders can run concurrently — no exclusive lock
# Works even while someone is attached or viewing
Unlike attach, send does not take an exclusive writer lock. Multiple senders, an attached client, and viewers can all coexist. This makes send ideal for:
- Orchestration tools injecting commands programmatically
- CI/CD scripts that drive interactive sessions
- Multi-agent systems sending notifications
Note: Concurrent senders have no ordering or atomicity guarantees. If multiple senders write simultaneously, their input may interleave. Callers should serialize sends if ordering matters.
Detach Keybinding
Default: Ctrl+A then d (same as GNU screen). Works on all keyboard layouts.
Press Ctrl+A twice to send a literal Ctrl+A to the process.
Configurable via HOLDPTY_DETACH — comma-separated hex bytes (Ctrl+A = 0x01, Ctrl+B = 0x02, d = 0x64):
export HOLDPTY_DETACH="0x02,0x64" # Ctrl+B then d (tmux-style)
export HOLDPTY_DETACH="0x1d,0x64" # Ctrl+] then d (telnet-style)
Session Directory
Sessions are stored as Unix domain sockets + JSON metadata:
| Platform | Default path |
|---|---|
| Windows | %TEMP%\dt\ |
| Linux | $XDG_RUNTIME_DIR/dt/ or /tmp/dt-$UID/ |
Override: HOLDPTY_DIR environment variable.
How It Works
Each session is a holder process that:
- Creates a PTY via
node-pty(ConPTY on Windows, forkpty on Linux/macOS) - Spawns the command inside it
- Buffers output in a 1MB ring buffer
- Listens on a Unix domain socket for client connections
- Relays data between clients and the PTY using a binary protocol
- Exits when the child process exits, cleaning up socket + metadata
Sessions are regular processes — they don't daemonize themselves. Use --bg for detached launch, or manage with pm2/systemd/nohup as needed. This is not a process manager.
Exit Codes
| Command | Exit code |
|---|---|
launch --bg |
0 on successful launch |
launch --fg |
Child's exit code |
attach |
Child's exit code if child exits while attached; 0 on detach |
view |
0 |
logs |
0 |
send |
0 if data sent; 1 if session not found or dead |
stop |
0 if signal sent |
What holdpty is NOT
- Not a process manager. Use pm2, systemd, nohup for lifecycle management.
- Not a terminal emulator. Your terminal (Windows Terminal, iTerm, etc.) does the rendering.
- Not a window manager. No splits, tabs, panes. One PTY per session.
- Not a config-driven tool. Everything via CLI flags and env vars.
Platform Support
| Platform | PTY backend | Status |
|---|---|---|
| Windows 10+ | ConPTY | ✅ Primary |
| Linux | forkpty | ✅ Supported |
| macOS | forkpty | ✅ Supported |
Contributing & Support
- Bugs & feature requests: GitHub Issues
- Source code: github.com/marcfargas/holdpty
License
MIT — see LICENSE.