Skip to content

Repository files navigation

babysit

crates.io license

Wrap a command in a PTY and control it from another terminal. The command runs in the background under a worker that owns the PTY and records its output. From anywhere you can read the output, screenshot the screen, send input, wait for exit, or attach interactively.

Useful for scripts and coding agents that need to drive a command they didn't start.

babysit demo

$ babysit run -d --json -- make local-ci   # {"id":"ab12"}
$ babysit log -s ab12 --tail 20
$ babysit screenshot -s ab12
$ babysit wait -s ab12

babysit -- make local-ci is the interactive shorthand. For scripting, use run -d --json and capture the id.

How it works

The command runs under a background worker that owns the PTY, logs all output, and serves a Unix control socket. Your terminal is just attached to it, so you can detach, re-attach, and query from other terminals.

State lives in ~/.babysit/sessions/<id>/. Set $BABYSIT_DIR (absolute path) to change the root. status, log, and screenshot work after the worker exits; send, key, restart, and kill need it alive.

-s <id> selects a session; there is no "most recent" fallback. Inside the wrapped command the id is exported as $BABYSIT_SESSION_ID.

Remote hosts over SSH

Pass an OpenSSH destination directly to global --host:

$ babysit --host user@devbox run -- pi
$ babysit --host user@devbox list
$ babysit --host user@devbox attach -s ab12

SSH config aliases work too, so --host dev uses the Host dev entry from ~/.ssh/config (including its user, port, ProxyJump, and keys). The remote host must already have a compatible babysit in its non-interactive PATH.

A foreground remote run starts one detached worker on the remote machine and then attaches to it. SSH server-alive probes detect a silent transport failure (such as Wi-Fi loss) within roughly 10 seconds. Babysit then clears the stale local display and shows the host, session, disconnect reason, offline duration, raw-log offset, retry attempt, and backoff while reconnecting. The same process and PTY continue remotely. Once connected, babysit replays the bounded raw backlog to rebuild the terminal display, then resumes offset-based live output. Input typed while offline is discarded. Detach at any time with Ctrl-\ Ctrl-\, including while reconnecting; pass attach --no-reconnect to make a transport loss fatal. Sessions using run --view-cmd have a transformed stream without stable raw byte offsets, so their remote attach returns an error after transport loss instead of replaying duplicate output. run -d starts the remote worker without attaching.

--host local is the default and runs directly without SSH. Any other value is passed to ssh as its destination. The embedding API remains local-only.

Library use (embedding)

babysit is also a library. Everything is reached through a Babysit context — an explicit handle to a state root. The library never reads the environment to find its root; you pass it in. ($BABYSIT_DIR is consulted only by the babysit binary, via Babysit::from_env.)

use babysit::Babysit;

# async fn demo() -> anyhow::Result<()> {
let bs = Babysit::new("/path/to/state"); // explicit root, no env
let id = /* spawn */ "job".to_string();
for s in bs.list_sessions().await? {
    println!("{} {}", s.id, s.state);
}
bs.kill(Some(id), false).await?;
# Ok(()) }

Embedders (e.g. looop) compute their own root and call Babysit::new, so a babysit-backed tool never has to touch $BABYSIT_DIR or share the global ~/.babysit.

Install

Pick one.

From crates.io:

cargo install babysit

With Nix flakes:

nix profile install github:yusukeshib/babysit

Prebuilt binary via the install script:

curl -fsSL https://raw.githubusercontent.com/yusukeshib/babysit/main/install.sh | sh

The install script drops a prebuilt binary in ~/.local/bin (override with BABYSIT_INSTALL_DIR, pin with BABYSIT_VERSION). babysit upgrade self-updates cargo/binary installs; Nix installs are managed by Nix.

Run without installing: nix run github:yusukeshib/babysit -- -- make local-ci.

Subcommands

Command Description
run Wrap a command in a PTY (babysit -- <cmd> shorthand; -d detached; --json prints the id)
list (ls) List sessions
status Session state and exit code
log Show output; --tail, --grep, --follow, --since
screenshot (shot) Render the current screen; --format plain|ansi|json, --trim
send Send text to stdin (-n no newline; --json)
key Send named keys (Enter, Up, Esc, C-c, F1, …)
expect Block until a regex appears (--screen matches the rendered TUI)
wait-idle Block until output is quiet for --settle
wait Block until exit, return the exit code
resize Resize the terminal (COLSxROWS)
flag / unflag Flag a session for attention / clear it
restart Restart the command
kill Terminate the command's process group (graceful signal, then forced escalation) and return only after exit is confirmed
attach / detach Attach your terminal (detach: Ctrl-\ Ctrl-\) / detach others
prune Delete finished or dead sessions
upgrade Self-update
config Shell completions: eval "$(babysit config zsh|bash)"

Each command documents its own flags and gotchas: babysit help <command>. babysit --help covers the model and the typical agent loop.

On Unix, kill covers the isolated process group created for the wrapped command. A descendant that deliberately daemonizes into a different process group or session is outside that boundary.

Build from source

cargo build --release   # target/release/babysit

About

Wrap a command in a PTY and control it from the outside — read output, screenshot the screen, send input, attach/detach. Handy for scripts and AI coding agents.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages