Skip to content

AnswerDotAI/bgterm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bgterm

bgterm gives you a lightweight way to run an interactive terminal session in the background from Python.

It is for the common control loop:

  1. start a session once
  2. get back a session id
  3. send more input later
  4. wait a bounded amount of time
  5. inspect the output that arrived since your last read

This is useful for REPLs, shells, and interactive CLI tools such as python, ipython, sqlite3, or custom command-line programs that you want to keep alive between calls.

Why Use It

bgterm is a good fit when you want:

  • a persistent interactive session, not a one-shot subprocess
  • a simple functional API from Python
  • output buffered between calls
  • a light in-process tool without bringing in tmux

It is not the right tool when you need:

  • named sessions you can reattach to manually
  • visibility across multiple processes
  • a faithful way to retrieve very large transcripts

For those cases, see bgtmux.

Quick Start

from bgterm import poll, start_session, write_stdin

sid = start_session(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"])

startup = poll(sid, 5000)
print(startup.text)

out = write_stdin(sid, "2+2\n", 500)
print(out.text)

The object wrapper is just a convenience around the same flow:

from bgterm import Session

with Session.start(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"]) as sess:
    print(sess.poll(5000).text)
    print(sess.write_stdin("2+2\n", 500).text)

How To Think About It

Every session has an unread-output cursor.

That means:

  • output can arrive between calls and still be available later
  • each write_stdin() / poll() / read() call returns the next unread chunk by default
  • if there is already unread output waiting, a later poll returns immediately

So the normal pattern is:

  • write_stdin(sid, "command\n", yield_time_ms=...) to send input and wait briefly
  • poll(sid, yield_time_ms=...) to wait again without sending anything
  • read(sid) if you just want the next unread chunk immediately

What yield_time_ms Means

yield_time_ms is a bounded wait, not a semantic completion signal.

When you call write_stdin() or poll() with yield_time_ms:

  • if unread output is already buffered, the call returns immediately
  • otherwise it waits until output arrives, the process exits, or the timeout expires

bgterm does not know whether your REPL command is "done". It only knows whether terminal output changed.

In practice, callers usually decide completion by one of:

  • seeing the next prompt
  • printing a sentinel string and waiting for it
  • waiting for process exit

API

The primary interface is functional and sid-based.

start_session(...) -> sid

Start a background session and return an integer session id.

Important arguments:

  • cmd: command string or argv list
  • cwd, env: forwarded to subprocess.Popen
  • shell: defaults to True for string commands and False for argv lists
  • encoding, errors: control decoding of terminal bytes into PollResult.text
  • max_buffer_bytes: total buffered output kept in memory

write_stdin(sid, chars="", yield_time_ms=0, max_output_bytes=65536)

Write input to the session, wait briefly, and return unread output.

Passing chars="" is valid and often useful.

poll(sid, yield_time_ms=0, max_output_bytes=65536)

Wait for unread output without sending input.

read(sid, max_output_bytes=65536)

Return unread output immediately with no waiting.

wait(sid, timeout_ms=None)

Wait for the session to exit and return its exit code, or None on timeout.

close(sid) / terminate(sid) / kill(sid)

Shut down the session or forward process termination signals.

list_sessions()

Return the currently active session ids in this Python process.

Session

Thin convenience wrapper over the sid-based API.

PollResult

Each write_stdin() / poll() / read() call returns a PollResult.

The most useful fields are:

  • text: decoded unread output
  • running: whether the child is still alive
  • exit_code: final return code, or None while still running
  • remaining_bytes: unread buffered output still waiting after this call
  • dropped_bytes: bytes lost because the buffer overflowed
  • truncated: whether this read was partial or older unread output was lost

The offset fields are there when you need deterministic paging behavior, but most callers only need text plus the status fields above.

Large Output

bgterm is optimized for interactive polling of recent output.

If a session produces output faster than you read it:

  • old unread output may be dropped once the buffer fills
  • one read may only return part of the available unread output

That is usually the right tradeoff for interactive terminals. If you need the full transcript of a huge job, write to a file instead of treating the terminal as the transport.

Example: Multi-Step Interaction

from bgterm import poll, start_session, write_stdin

sid = start_session(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"])

startup = poll(sid, 5000)
print(startup.text)

out = write_stdin(sid, "import time; time.sleep(2); print('done')\n", 200)
print(out.text)    # often just echoed input so far

out = poll(sid, 2500)
print(out.text)    # should now include 'done' and the next prompt

Development

pip install -e .[dev]
pytest

Versioning

Version lives in bgterm/__init__.py as __version__.

ship-bump --part 2   # patch
ship-bump --part 1   # minor
ship-bump --part 0   # major

Release

  1. Ensure GitHub issues are labeled bug, enhancement, or breaking.
  2. Run:
ship-gh
ship-pypi

If you need sessions that are named, visible outside the current Python process, and easy to reattach to manually, see bgtmux. bgtmux uses tmux itself as the session registry, so it is often the better choice when you want inspectable long-lived terminals, cross-process visibility, or tmux-native pane and window browsing. bgterm is the better fit when you want the lightest-weight PTY-backed background session model inside one Python process.

About

A lightweight way to run an interactive terminal session in the background from Python

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages