Skip to main content

A library for building terminal applications with NiceGUI and rich terminal features.

Project description

NiceTerminal: A NiceGUI xterm.js Control

PyPI License Python

This WIP project provides an xterm.js based terminal component for your NiceGUI apps, running on both Linux and Windows. It allows you to embed fully interactive terminals (local shells, serial connections, or anything else) within your NiceGUI UI.

Disclaimer: This is not an official NiceGUI project. Use it at your own risk, especially when enabling shell or device access.


Features

  • NiceGUI Integration: Easily add a terminal component inside your NiceGUI UI.
  • Local Shell Support: Provides a local shell out of the box on Linux and Windows (via pywinpty on Windows).
  • Serial/Custom Support: Extend the base Interface class to communicate with any data source, such as serial devices, remote servers, or custom processes.
  • Concurrent Access: Multiple terminals or multiple users are supported.
  • Persistent State: Caches screen data, so refreshing doesn’t necessarily lose session output.
  • Optional Authentication: The niceterm CLI supports authentication or no-auth modes.
  • Isolation Levels: Control whether terminals are shared globally, per user, or per browser tab.

Installation

Library only:

pip install niceterminal

Library + CLI (installs the niceterm command):

pip install niceterminal[cli]

Note: The CLI offers easy web-based shell access, which can be a security concern. Enable it only if you understand the risks.


Quick Start

A minimal example to embed a shell terminal in a NiceGUI page:

from nicegui import ui
from niceterminal.xterm import ShellXTerm

# Create a full-page terminal that opens your default shell.
ShellXTerm().classes("w-full h-full")

ui.run()

Open your app in the browser. You’ll see a page hosting an interactive shell.


Library Overview

niceterminal is built around two key abstractions:

  1. XTerm – A NiceGUI element based on xterm.js, rendering an interactive terminal in the browser.
  2. Interface – An abstract base class specifying how terminal input/output is processed on the Python side.

XTerm Class

from niceterminal.xterm import XTerm

Purpose:

  • Renders an xterm.js terminal in NiceGUI.
  • Handles sending data to and receiving data from a backend (shell, serial port, custom service, etc.).

Key Parameters / Methods:

  • __init__(self, config: TerminalConfig, interface:Interface=None, on_change: Callable, on_close: Callable, **kwargs)
    • interface: A subclass of Interface that manages I/O.
  • write(self, data: bytes)
    • Called by the attached interface to send output to the terminal.
  • set_cursor_location(self, row: int, col: int)
    • Manually position the cursor on the UI (not interface)

Example:

from niceterminal.interface.base import Interface, INTERFACE_STATE_STARTED, INTERFACE_STATE_INITIALIZED
from loguru import logger

from nicegui import ui
from niceterminal.xterm import XTerm

class EchoInterface(Interface):
    async def write(self, data: bytes):
        if data:
            self.on_read_handle(data)

    @logger.catch
    async def launch_interface(self):
        """Starts the shell process asynchronously."""
        if self.state != INTERFACE_STATE_INITIALIZED:
            return
        self.state = INTERFACE_STATE_STARTED

# Instantiate our custom interface for a specific echo device
echo_interface = EchoInterface()
echo_interface.start()

# Render in your NiceGUI app
ui.label("Echo Terminal")

# Create an XTerm bound to the echo interface
echo_terminal = XTerm(interface=echo_interface).classes("w-full h-full")

ui.run()

In this simplistic example, whatever the user types is immediately echoed back.

ShellXTerm Subclass

from niceterminal.xterm import ShellXTerm

Purpose:

  • A convenience subclass that provides a local shell interface automatically (using pty on Linux or pywinpty on Windows).

Key Usage:

ShellXTerm().classes("w-full h-full")
  • Spawns bash on Linux or cmd.exe on Windows.
  • If you need a different shell (e.g., powershell), you can pass arguments directly to the underlying interface.

Interface Base Class

from niceterminal.interface import Interface

Purpose:

  • Defines the core contract for reading/writing data between the browser-based terminal and a Python-driven data source.

Key Methods:

  • write(self, data: bytes) -> None
    • Invoked when the user types or sends data from the terminal.
  • on_read_handle(self, data: bytes) -> None
    • Sends data to be rendered in the terminal.
  • close(self) -> None
    • Close/cleanup the underlying connection or process.
  • is_closed(self) -> bool
    • Return True if the backend is closed or invalid.

Extend Interface to suit your needs (local shell, remote connections, serial devices, etc.).


Serial Port Example

Below is an example of using pyserial to connect a serial port to the terminal.

  1. Install pyserial:
    pip install pyserial
    
  2. Create a custom interface that:
    • Opens the serial port.
    • Listens for incoming data (in a background thread).
    • Forwards terminal input back to the serial device.
import threading
import serial
from niceterminal.interface.base import Interface, INTERFACE_STATE_STARTED, INTERFACE_STATE_INITIALIZED
from loguru import logger

from nicegui import ui
from niceterminal.xterm import XTerm

class SerialPortInterface(Interface):
    def __init__(self, port="/dev/ttyUSB0", baudrate=115200, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.port = port
        self._closed = False
        self.baudrate = baudrate
        self.ser = None

    @logger.catch
    async def launch_interface(self):
        """Starts the shell process asynchronously."""
        if self.state != INTERFACE_STATE_INITIALIZED:
            return
        self.state = INTERFACE_STATE_STARTED

        # Open the serial device
        self.ser = serial.Serial(self.port, self.baudrate, timeout=0)

        # Start a background thread to read from the serial port
        self._read_thread = threading.Thread(target=self._read_loop, daemon=True)
        self._read_thread.start()

    def _read_loop(self):
        while not self._closed:
            data = self.ser.read(1024)  # Non-blocking read
            if data:
                # Send any received data to the terminal
                self.on_read_handle(data)

    async def write(self, data: bytes):
        if not self.ser:
            return
        # When the user types in the terminal, send it to the serial device
        if not self._closed and self.ser.is_open:
            # Ensure we encode to bytes
            self.ser.write(data)

    def close(self):
        # Cleanup
        if not self.ser:
            return
        self._closed = True
        if self.ser.is_open:
            self.ser.close()

    def is_closed(self):
        return self._closed
  1. Integrate your SerialPortInterface with XTerm in a NiceGUI app:
from nicegui import ui
from niceterminal.xterm import XTerm

# Instantiate our custom interface for a specific serial device
serial_interface = SerialPortInterface(port="COM4", baudrate=115200)
if  __name__ == "__mp_main__":
    serial_interface.start()

# Render in your NiceGUI app
ui.label("Serial Port Terminal")

# Create an XTerm bound to the serial interface
serial_terminal = XTerm(interface=serial_interface).classes("w-full h-full")


ui.run()

Now anything typed in the terminal is sent to the specified serial port, and anything received from the device is displayed in the terminal.


CLI Usage (niceterm)

Installing with [cli] provides the niceterm command, a convenient multi-terminal web interface:

NiceTerminal Web Interface

Usage:
    niceterm [options]
    niceterm -h | --help
    niceterm --version

Options:
  -h --help                    Show this help.
  --version                    Show version.
  --host=<host>                Host to bind web interface [default: 0.0.0.0].
  --port=<port>                Port for web interface [default: 8080].
  --app=<command>              Default command to start in new terminals [default: bash].
  --password=<pass>            Set authentication password.
  --no-auth                    Disable authentication requirement (cannot combine with --password).
  --light-mode                 Use light mode theme.
  --log-level=<level>          Set log level [default: INFO].
  --isolation=<level>          At what level terminals are shared [default: global].
                               (global, user, or tab)

Examples

# Start with default settings on port 8080:
niceterm

# Start on port 9000, with no authentication:
niceterm --port 9000 --no-auth

# Provide a specific password:
niceterm --password secret123

# Run Python in each new terminal and isolate them per tab:
niceterm --app "python3" --isolation tab

On startup, logs display the server address and an auto-generated password (if --password wasn’t specified).


Screenshots

Authentication Screen Terminal Dashboard

Simple auto-index page usage:

from nicegui import ui
from niceterminal.xterm import ShellXTerm

ShellXTerm().classes("w-full h-full")

ui.run()

Which yields:


Security Considerations

  1. Open Shell/Device Access: Running niceterm or embedding a shell/serial interface in a publicly exposed NiceGUI app is risky.
  2. Authentication: Always protect your deployment with strong passwords or other authentication methods if it’s internet-accessible.
  3. TLS/SSL: Consider using HTTPS or a secure reverse proxy for production. (or simply just... maybe not use this in production. This is an experimental library after all)
  4. Isolation Levels: With niceterm, you can decide whether multiple users/tabs share the same terminal or have separate sessions.

License

This project is released under the MIT-0 License. You’re free to copy, modify, and distribute this software with no attribution required.


If you have suggestions, bug reports, or feature requests, please open an issue on GitHub.

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

niceterminal-0.1.3.tar.gz (99.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

niceterminal-0.1.3-py3-none-any.whl (101.7 kB view details)

Uploaded Python 3

File details

Details for the file niceterminal-0.1.3.tar.gz.

File metadata

  • Download URL: niceterminal-0.1.3.tar.gz
  • Upload date:
  • Size: 99.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.22.2 CPython/3.10.12 Linux/5.15.0-97-generic

File hashes

Hashes for niceterminal-0.1.3.tar.gz
Algorithm Hash digest
SHA256 09b545a573831659c0a09dd62519933985ad11acd39f396241f6bede3d7dff37
MD5 bd4d9fd8621417cf832d8f3b3bd32be5
BLAKE2b-256 ad55fdbf7a1f4a98512e96dfe87f387e86e6d90e3725bdc762b9f1738c73beb6

See more details on using hashes here.

File details

Details for the file niceterminal-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: niceterminal-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 101.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.22.2 CPython/3.10.12 Linux/5.15.0-97-generic

File hashes

Hashes for niceterminal-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d70a9b66df2427feddbab18db8e818085e241c7628abd15cbb3223847cee22a8
MD5 70f6ebba33a3f0b17a8812fc78b60880
BLAKE2b-256 9fed94f80e0e1f6e221ecc3c603624a82c3d655bb5301ac12e3f43eeb74b645f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page