Skip to main content

A cross-platform embeddable PTY/ConPTY terminal for Jupyter and Python web applications

Project description

Notebook Terminal

A cross-platform embeddable terminal for Jupyter Notebook, JupyterLab, Flask, Django, and custom Python frontends.

Notebook Terminal provides a persistent PTY/ConPTY session with real-time output, optional keyboard input, programmatic command execution, rich output tabs, interactive DataFrame viewing, Plotly support, and native Jupyter widgets.

Version 0.1.1 is an alpha release. Test carefully before production use.

Features

  • POSIX PTY support on Linux and macOS
  • ConPTY support on Windows through pywinpty
  • PowerShell, CMD, Bash, Zsh, and other shells
  • Real-time output streaming
  • Interactive or read-only terminal mode
  • Programmatic commands and process input
  • Python code and Python-file execution
  • Matplotlib and Seaborn image tabs
  • Interactive Plotly tabs
  • Interactive DataFrame viewer with filtering, sorting, and pagination
  • Native ipywidgets through the active Jupyter kernel
  • Closable rich-output tabs
  • ANSI colors and status helpers
  • Flask WebSocket and Django Channels adapters
  • Framework-independent TerminalSession

Installation

python -m pip install notebook-terminal

Install optional integrations:

python -m pip install "notebook-terminal[flask]"
python -m pip install "notebook-terminal[django]"
python -m pip install "notebook-terminal[data,plotly]"

For local development:

git clone https://github.com/YOUR-USERNAME/notebook-terminal.git
cd notebook-terminal
python -m pip install -e ".[dev]"

Jupyter quick start

from notebook_terminal import terminal

term = terminal(height=450)

Run a command from another cell:

term.run("python --version")

term.run() uses the existing terminal. It does not display a second copy of the widget.

Interactive and managed modes

Interactive terminal:

term = terminal(interactive=True)

Read-only terminal controlled only through Python:

term = terminal(interactive=False)
term.run("python application.py")

You can still send input programmatically:

term.send_text("answer\r\n")

Running commands

term.run("git status")
term.run("pip list")
term.run("python script.py")

Create a separate terminal explicitly:

second = term.run("python another_script.py", new_window=True)

Running Python code

term.run_python("""
import time

for value in range(5):
    print(value, flush=True)
    time.sleep(1)
""")

Run an existing file:

term.run_python_file(
    "analysis.py",
    args=["input.csv", "--limit", "100"],
)

Rich output

Interactive DataFrame

term.run_python("""
import pandas as pd

frame = pd.DataFrame({
    "city": ["Warsaw", "Krakow", "Gdansk", "Poznan"],
    "value": [12, 18, 9, 15],
})

display(frame)
""", rich_output=True, clear_previous=True)

The DataFrame tab supports filtering, sorting, pagination, page-size selection, and horizontal scrolling.

Matplotlib

term.run_python("""
import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4], [3, 7, 4, 8], marker="o")
plt.title("Example chart")
plt.show()
""", rich_output=True, clear_previous=True)

Seaborn

term.run_python("""
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

frame = pd.DataFrame({
    "category": ["A", "B", "C"],
    "value": [10, 17, 13],
})

sns.barplot(data=frame, x="category", y="value")
plt.show()
""", rich_output=True, clear_previous=True)

Plotly

term.run_python("""
import pandas as pd
import plotly.express as px

frame = pd.DataFrame({
    "category": ["A", "B", "C", "D"],
    "value": [10, 17, 13, 22],
})

figure = px.bar(frame, x="category", y="value", title="Interactive Plotly chart")
display(figure)
""", rich_output=True, clear_previous=True)

Native Jupyter widgets

Jupyter widgets need the current notebook kernel and its Comm connection. Use run_kernel():

term.run_kernel("""
import ipywidgets as widgets
from IPython.display import display

slider = widgets.IntSlider(value=25, min=0, max=100, description="Value:")
label = widgets.Label()

slider.observe(
    lambda change: setattr(label, "value", f"Selected: {change['new']}"),
    names="value",
)

display(widgets.VBox([slider, label]))
""", clear_previous=True)

For interactive Matplotlib, install ipympl and use %matplotlib widget inside run_kernel().

Styled messages

term.success("Code completed successfully")
term.error("Operation failed")
term.warning("Configuration file is missing")
term.info("Starting analysis")
term.debug("Current value: 42")

Custom styling:

from notebook_terminal import Color

term.write_line(
    "Custom message",
    color=Color.GREEN,
    bold=True,
    icon="→",
)

Styled messages are emitted directly to the terminal output channel. They are not written to shell input, so PowerShell does not try to execute ANSI reset sequences such as [0m.

Copy and paste

In interactive mode:

  • select text and press Ctrl+C to copy
  • press Ctrl+C without a selection to interrupt the active process
  • press Ctrl+V to paste
  • use the mouse wheel to scroll

In read-only mode, copying remains available, but keyboard input is not forwarded to the process.

Process control

term.send_text("answer\r\n")
term.interrupt()
term.clear()
term.clear(clear_tabs=True)
term.clear_tabs()
term.close_tab(0)
term.focus()
term.close_terminal()

Framework-independent session

from notebook_terminal import TerminalSession

session = TerminalSession()

session.subscribe(
    lambda chunk: print(chunk.decode("utf-8", errors="replace"), end="")
)

session.run("python --version")
session.success("Completed")

session.write() sends input to the shell. session.emit_output() publishes display-only output to attached frontends.

Flask

from flask import Flask, render_template
from notebook_terminal.web import flask_blueprint, manager

app = Flask(__name__)
app.register_blueprint(flask_blueprint(), url_prefix="/terminal")

@app.route("/")
def index():
    session_id, session = manager.create(cwd=".", interactive=True)
    return render_template("terminal.html", session_id=session_id)

Connect an xterm.js frontend to the WebSocket endpoint exposed by the adapter. Programmatic control remains available through the returned session object.

Django

Install Django Channels and add the provided consumer to your ASGI WebSocket routes:

from django.urls import re_path
from notebook_terminal.web import django_consumer

TerminalConsumer = django_consumer()

websocket_urlpatterns = [
    re_path(
        r"^ws/terminal/(?P<sid>[0-9a-f]+)/$",
        TerminalConsumer.as_asgi(),
    ),
]

Architecture

                         TerminalSession
                               |
             +-----------------+-----------------+
             |                 |                 |
       Jupyter widget     Flask WebSocket   Django Channels
             |                 |                 |
             +-----------------+-----------------+
                               |
                          PTY / ConPTY
                               |
                  PowerShell / CMD / Bash / Zsh

Security

An embedded terminal executes commands with the permissions of the hosting Python process. Do not expose unrestricted terminal access publicly without authentication, authorization, session ownership checks, process limits, automatic cleanup, and operating-system or container isolation.

Development and publishing

See PUBLISHING.md for complete build, GitHub, TestPyPI, and PyPI instructions.

License

MIT License. See LICENSE.

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

notebook_terminal-0.1.1.tar.gz (1.6 MB view details)

Uploaded Source

Built Distribution

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

notebook_terminal-0.1.1-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

Details for the file notebook_terminal-0.1.1.tar.gz.

File metadata

  • Download URL: notebook_terminal-0.1.1.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for notebook_terminal-0.1.1.tar.gz
Algorithm Hash digest
SHA256 11dd913f19700ca7071483fc05c1015cf69fc582d923ea4996378ba701f56112
MD5 f15a84c69aca3a60a907e176ee50bb61
BLAKE2b-256 050ec0ecfa1c84bf48fbcf0f5d00b321175f3f924a6cf841d31283b2e1747fa8

See more details on using hashes here.

File details

Details for the file notebook_terminal-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for notebook_terminal-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 74b92f1a67adc0f4c49783e15b3903262114a5ded89c62eb65de00fe5babbc61
MD5 fc7df6607222a9889c394a44513cb8b4
BLAKE2b-256 29131334bf46762e1a4831252fdd9a4b08da2fcdffb78c2d5030958a9710f18a

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