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.
Project description
termlit
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.
Features
- 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
Installation
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.
Quick start
-
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
Local mode
If you want to run a Termlit app without starting an SSH server, add --local:
termlit run app.py --local
Notes:
- 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.
CLI flags
--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.
Streaming output
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.
Programmatic usage
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.
Uploading files
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.
Downloading files
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.
Repository layout
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!
Developer Overview
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).
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file termlit-0.3.1.tar.gz.
File metadata
- Download URL: termlit-0.3.1.tar.gz
- Upload date:
- Size: 30.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f1ae7f313fb62b10460e8f163c7d54d674ae339f2f0d7f51be641665b3a0280
|
|
| MD5 |
5814596ddc03d9fafc1e79bc238e6fc0
|
|
| BLAKE2b-256 |
e218e07ba97d5c933bae20750b63da5a7a0b8d89cab010afe675498a40aef620
|
File details
Details for the file termlit-0.3.1-py3-none-any.whl.
File metadata
- Download URL: termlit-0.3.1-py3-none-any.whl
- Upload date:
- Size: 27.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
181346c2b8b2ae5d257c1998d3e5c5d2fc5290897d8be12c3446720bdc9a5996
|
|
| MD5 |
0e2f840efbbaadd6d504d00c87ce464c
|
|
| BLAKE2b-256 |
f7acc63f2a2f8815d2eac81c084fffecf38390e3e13e391f587e437112843514
|