Termlit is a small Python tool that provides an SSH server interface. It makes it easy to expose interactive applications over SSH — for example, to host AI-powered conversational services that users can connect to with an SSH client.
- SSH server with ready-to-use credentials (or anonymous mode)
- Built-in Rich welcome panels and simple text helpers
- Rich-powered spinners that lock user input while background work is running
- Request/response helpers (
termlit.post) powered byrequests - Password-masked input via
termlit.input(..., hidden=True) - Session-scoped stdout redirection so
print()just works - Upload helpers (
termlit.upload_file(s)) that copy generated files into anupload_files/directory so you can retrieve them easily - Download helpers (
termlit.download_cmd) that generate ready-to-run scp commands or temporary HTTP links for your end users
Two common installation methods are shown below:
- From PyPI (recommended for end users):
python -m pip install termlit- From GitHub (editable / development install):
git clone https://github.com/stoday/termlit.git
cd termlit
python -m pip install -e .The editable install makes it easy to modify code and test changes locally.
-
Install the package (editable mode during development is fine):
pip install -e .or, if you prefer uv:
curl -LsSf https://astral.sh/uv/install.sh | sh # install uv (once) uv pip install -e .
-
Create a script, e.g.
app.py:import termlit termlit.welcome( title="Welcome~", subtitle="version 1.0.0", description="This is a note", ) while True: prompt = termlit.input("User question: ") if prompt.lower() in {"quit", "exit"}: termlit.goodbye("Goodbye! See you next time") break with termlit.spinner("dots", "Processing your question..."): response = termlit.post( url="https://httpbin.org/post", json={"question": prompt}, log=False, # Suppress automatic POST summary ) termlit.write("Answer: " + str(response.json()))
termlit.spinnerblocks input by default, so users cannot queue input while a background task is running. By defaulttermlit.inputignores empty input; allow empty input withallow_empty=True. Usetermlit.input("input password: ", hidden=True)when you need password input — the input will be masked with*. -
Serve it over SSH:
termlit run app.py --host 0.0.0.0 --port 2222 --reload
Add
--auth noneto allow passwordless login (the default is--auth ssh, which requires a password). Or run it locally without SSH:termlit run app.py --local
-
Connect from any SSH client (default credentials
admin/password123):ssh admin@127.0.0.1 -p 2222
If you want to run a Termlit app without starting an SSH server, add --local:
termlit run app.py --localNotes:
- Runs the app on your local terminal using stdin/stdout (no remote access).
--host,--port,--auth,--user, and--allow-anonymousare ignored in local mode.- Useful for quick iteration or single-user usage on the same machine.
--user name=secret: add/override login credentials (repeatable).--auth {ssh,none}: choose between password-protected (ssh) or passwordless (none) sessions.--allow-anonymous: accept any username/password combo.--local: run a script locally without starting an SSH server (use withrun).--reload: watch the target script and restart the SSH server whenever it changes (development helper).--host/--port: where the SSH server listens.--version: display the installed Termlit version and exit.
When you already have a generator/iterable that yields text chunks (for example,
tailing logs or chunked API responses), call termlit.write(..., stream=True)
to forward each chunk immediately without buffering:
def stream_logs():
for line in follow_log_file():
yield line
termlit.write(stream_logs(), stream=True)Strings/bytes are still treated as a single chunk, so you can safely switch the flag on even when a function sometimes returns plain text and sometimes returns an iterator.
You can also embed Termlit inside a Python process:
import termlit
def app():
termlit.welcome("Inline app")
termlit.write("Hello there!")
termlit.goodbye()
if __name__ == "__main__":
termlit.run(app, host="127.0.0.1", port=2222)To allow passwordless sessions, pass
auth_mode="none"totermlit.run.
Use the upload_files/upload_file helpers when your script needs to drop
artifacts (reports, logs, etc.) into a directory that you can fetch later:
import termlit
# Copy a single file to ./upload_files (or $TERMLIT_UPLOAD_DIR) with progress
termlit.upload_files("build/output/report.pdf", show_progress=True)
# Copy multiple files and grab the resulting server-side paths
uploaded = termlit.upload_file(
["app.log", r"C:\temp\screenshot.png"],
show_progress=True,
)
termlit.write("Saved files to:")
for path in uploaded:
termlit.write(f" - {path}")
# Provide ready-to-run scp commands for the client
cmd = termlit.download_cmd(
"report.pdf",
source_dir="upload_files",
)
termlit.write("Run command locally to download:")
termlit.write(cmd)
# Or host a temporary HTTP download link
http_links = termlit.download_cmd(
"report.pdf",
source_dir="upload_files",
type="http",
)
termlit.write("Or open in browser:")
termlit.write(http_links)All files are copied into upload_files/ relative to where termlit run was
executed (override via the TERMLIT_UPLOAD_DIR environment variable or the
destination_dir argument). Pass show_progress=True to stream simple
percentage updates back to the SSH client while a file is being copied. Use
replace=True when you want to overwrite same-named files instead of letting
Termlit append _1, _2, ... suffixes (the default collision-avoidance
behaviour). Use
termlit.download_cmd(...) to generate the scp command your users should run
locally, pass source_dir="upload_files" when you want to specify the hosting
folder, or set type="http" to spin up a temporary http.server over the
target folder. Set TERMLIT_DOWNLOAD_HOST, TERMLIT_DOWNLOAD_PORT,
TERMLIT_DOWNLOAD_USER, or TERMLIT_HTTP_PORT when the defaults are
insufficient.
Once your script calls termlit.upload_file(...), you have two convenient ways
to guide end users through downloading the artifacts:
- scp command – Call
termlit.download_cmd("report.pdf", source_dir="upload_files")to generate a string likescp -P 2222 admin@<host>:/abs/path/report.pdf ./. Give this command to your user to run locally. Adjust host/port/user via env varsTERMLIT_DOWNLOAD_HOST,TERMLIT_DOWNLOAD_PORT,TERMLIT_DOWNLOAD_USER, or override usinghost=,port=,username=,destination=. - HTTP download – Pass
type="http", for exampletermlit.download_cmd("report.pdf", source_dir="upload_files", type="http"). This starts a temporaryhttp.server(default port8765, override withTERMLIT_HTTP_PORT) and returns a URL likehttp://<host>:8765/report.pdf. The user can download via a browser; server access logging is suppressed by default to avoid interfering with the SSH interface.
The HTTP mode requires all target files to be in the same folder—collect them into
upload_files/before generating a link. Remind users to stop the temporary HTTP server (restart the app or run a custom command) after downloading for security.
termlit/session.py– public helper implementations.termlit/runtime.py– SSH server + script runner.termlit/cli.py– command line interface (termlit run).scripts/ssh_server_plain.py,scripts/telnet_server.py– original demo servers (optional utilities; they call an external FastAPI backend that you must run yourself).scripts/start_services.py– helper script that starts the Telnet/SSH demos and forwards the--fastapi-urlyou provide.
Happy terminal building!
- Bumped project version to
0.4.1. - Synced the local lockfile package version to
0.4.1.
termlit/__init__.py: Re-exports Termlit's public API (welcome,input,upload_file, etc.). It also loads version information and binds the thread-local session on import.termlit/session.py: The session implementation that backs all public helpers (UI, HTTP helpers, upload/download) and maintains the internal_current_sessionstate.termlit/runtime.py: Starts and manages the SSH server and the script runner; provides a reload/watchdog flow viaserve_script_with_reloader.termlit/cli.py: The CLI entrypoint fortermlit; parses arguments fortermlit run, manages authentication options, and invokes the runtime.example_app.py: An example app demonstrating how to build a Termlit flow, upload files, and interact with an HTTP API.scripts/ssh_server_plain.py/scripts/telnet_server.py: Legacy demo servers that showcase an interactive shell and optional integration with an external FastAPI backend.scripts/start_services.py: A convenience helper to start the Telnet/SSH demos and forward a common--fastapi-urlto both services.tests/: Unit and integration tests; please add tests covering new helpers or runtime behavior when introducing features.upload_files/: The default location for server-side uploaded artifacts (override withTERMLIT_UPLOAD_DIR).