Metadata-Version: 2.4
Name: notebook-terminal
Version: 0.4.0
Summary: A cross-platform PTY/ConPTY terminal for Jupyter with bundled xterm.js
Author: Robert Zawadzki
License: MIT
Project-URL: Homepage, https://github.com/ZawadzkiR/notebook-terminal
Project-URL: Repository, https://github.com/ZawadzkiR/notebook-terminal
Project-URL: Issues, https://github.com/ZawadzkiR/notebook-terminal/issues
Project-URL: Changelog, https://github.com/ZawadzkiR/notebook-terminal/blob/main/CHANGELOG.md
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ipywidgets>=8.1
Requires-Dist: ipython>=8
Requires-Dist: traitlets>=5
Requires-Dist: jupyterlab_widgets<4,>=3.0
Requires-Dist: widgetsnbextension<5,>=4.0
Requires-Dist: requests>=2.28
Requires-Dist: websocket-client>=1.6
Requires-Dist: pywinpty>=3.0.0; platform_system == "Windows"
Provides-Extra: flask
Requires-Dist: Flask>=2.3; extra == "flask"
Requires-Dist: flask-sock>=0.7; extra == "flask"
Provides-Extra: django
Requires-Dist: Django>=4.2; extra == "django"
Requires-Dist: channels>=4; extra == "django"
Provides-Extra: plotly
Requires-Dist: plotly>=5; extra == "plotly"
Provides-Extra: data
Requires-Dist: pandas>=1.5; extra == "data"
Requires-Dist: matplotlib>=3.6; extra == "data"
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# notebook-terminal 0.4.0

Cross-platform PTY/ConPTY terminal for JupyterLab with bundled xterm.js. It works locally on Windows and Linux and can also connect to a terminal on a remote JupyterHub/Jupyter Server using its REST API and terminal WebSocket endpoint.

Repository: `https://github.com/ZawadzkiR/notebook-terminal`

## Local terminal

```python
from notebook_terminal import terminal

term = terminal(height=450, scrollback=10_000)
term.run("python --version")
```

The prompt is produced by the real shell of the current user. It is not hard-coded.

## Remote JupyterHub terminal

```python
import os
from notebook_terminal import remote_terminal

term = remote_terminal(
    hub_url="https://jupyter.example.com",
    username="robert",
    token=os.environ["JUPYTERHUB_TOKEN"],
)

term.run("hostname")
term.run("pwd")
term.run("ls -la")
```

You can also provide the URL of the single-user Jupyter Server directly:

```python
term = remote_terminal(
    server_url="https://jupyter.example.com/user/robert/",
    token=os.environ["JUPYTERHUB_TOKEN"],
)
```

Equivalent unified API:

```python
term = terminal(
    backend="jupyterhub",
    server_url="https://jupyter.example.com/user/robert/",
    token=os.environ["JUPYTERHUB_TOKEN"],
)
```

The remote backend:

1. creates a terminal with `POST /api/terminals`;
2. opens `wss://.../terminals/websocket/<name>`;
3. forwards xterm input as `stdin` messages;
4. forwards remote `stdout` messages to xterm.js;
5. synchronizes rows and columns with `set_size`;
6. removes the created terminal on close by default.

To attach to an existing terminal:

```python
term = remote_terminal(
    server_url="https://jupyter.example.com/user/robert/",
    token=os.environ["JUPYTERHUB_TOKEN"],
    terminal_name="1",
    create_terminal=False,
    delete_on_close=False,
)
```

For a named JupyterHub server:

```python
term = remote_terminal(
    hub_url="https://jupyter.example.com",
    username="robert",
    server_name="gpu",
    token=os.environ["JUPYTERHUB_TOKEN"],
)
```

### TLS

Keep certificate verification enabled. For a private CA, provide its certificate bundle:

```python
term = remote_terminal(
    server_url="https://jupyter.example.com/user/robert/",
    token=os.environ["JUPYTERHUB_TOKEN"],
    verify_ssl="/path/company-ca.pem",
)
```

`verify_ssl=False` exists for controlled testing only.

### Token security

Do not put the token directly in a notebook that will be committed or shared. Use an environment variable, keyring, or another secret store. A token with terminal access can execute commands with your remote account permissions.

By default the token is sent in the `Authorization: token ...` header. `allow_token_in_url=True` is available only for unusual proxies that reject authorization headers on WebSocket upgrades, because query-string tokens are easier to leak.

### Remote limitations

- The remote user server must already be running and reachable over HTTPS/WSS.
- The token must be allowed to access the single-user server.
- The administrator or reverse proxy may disable terminals or WebSockets.
- `run_python_file()` interprets its path as a path on the remote server.
- Rich-output capture is currently local-only. Use normal terminal output remotely.

## Python file arguments

```python
term.run_python_file(
    "train.py",
    args=["dataset.csv"],
    kwargs={"epochs": 20, "verbose": True},
)
```

This runs:

```text
python train.py dataset.csv --epochs 20 --verbose
```

## Installation

```bash
python -m pip install notebook_terminal-0.4.0-py3-none-any.whl
```

Restart the kernel and refresh JupyterLab after replacing an earlier version.

## Optional integrations

```bash
pip install "notebook-terminal[flask]"
pip install "notebook-terminal[django]"
pip install "notebook-terminal[data,plotly]"
```

## License

MIT
