notebook-terminal 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notebook_terminal-0.1.1/CHANGELOG.md +28 -0
- notebook_terminal-0.1.1/LICENSE +15 -0
- notebook_terminal-0.1.1/MANIFEST.in +7 -0
- notebook_terminal-0.1.1/PKG-INFO +387 -0
- notebook_terminal-0.1.1/README.md +337 -0
- notebook_terminal-0.1.1/pyproject.toml +68 -0
- notebook_terminal-0.1.1/setup.cfg +4 -0
- notebook_terminal-0.1.1/setup.py +3 -0
- notebook_terminal-0.1.1/src/notebook_terminal/__init__.py +26 -0
- notebook_terminal-0.1.1/src/notebook_terminal/notebook.py +403 -0
- notebook_terminal-0.1.1/src/notebook_terminal/rich_runner.py +159 -0
- notebook_terminal-0.1.1/src/notebook_terminal/session.py +383 -0
- notebook_terminal-0.1.1/src/notebook_terminal/static/all.css +319 -0
- notebook_terminal-0.1.1/src/notebook_terminal/static/dataframe.css +1 -0
- notebook_terminal-0.1.1/src/notebook_terminal/static/dataframe.js +23 -0
- notebook_terminal-0.1.1/src/notebook_terminal/static/plotly_widget.js +3892 -0
- notebook_terminal-0.1.1/src/notebook_terminal/static/widget.js +89 -0
- notebook_terminal-0.1.1/src/notebook_terminal/styling.py +48 -0
- notebook_terminal-0.1.1/src/notebook_terminal/web.py +58 -0
- notebook_terminal-0.1.1/src/notebook_terminal.egg-info/PKG-INFO +387 -0
- notebook_terminal-0.1.1/src/notebook_terminal.egg-info/SOURCES.txt +22 -0
- notebook_terminal-0.1.1/src/notebook_terminal.egg-info/dependency_links.txt +1 -0
- notebook_terminal-0.1.1/src/notebook_terminal.egg-info/requires.txt +29 -0
- notebook_terminal-0.1.1/src/notebook_terminal.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Fixed ANSI status messages being interpreted as PowerShell input.
|
|
6
|
+
- Added programmatic command history and rerun helpers.
|
|
7
|
+
- Added run_many, send_key, wait_until_idle, and restart helpers.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## 0.1.1
|
|
11
|
+
|
|
12
|
+
Initial public alpha release.
|
|
13
|
+
|
|
14
|
+
### Included
|
|
15
|
+
|
|
16
|
+
- PTY/ConPTY terminal sessions
|
|
17
|
+
- Jupyter terminal widget
|
|
18
|
+
- interactive and read-only modes
|
|
19
|
+
- real-time output streaming
|
|
20
|
+
- Python command and file execution
|
|
21
|
+
- DataFrame, Matplotlib, Seaborn, Plotly, and Jupyter widget tabs
|
|
22
|
+
- closable rich-output tabs
|
|
23
|
+
- Flask and Django adapters
|
|
24
|
+
- ANSI status helpers
|
|
25
|
+
|
|
26
|
+
### Fixed before release
|
|
27
|
+
|
|
28
|
+
- Styled messages now use the terminal output channel rather than shell stdin. This prevents PowerShell from interpreting ANSI reset fragments such as `[0m` as commands.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: notebook-terminal
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A cross-platform embeddable PTY/ConPTY terminal for Jupyter and Python web applications
|
|
5
|
+
Author: Robert Zawadzki
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ZawadzkiR/notebook-terminal
|
|
8
|
+
Project-URL: Repository, https://github.com/ZawadzkiR/notebook-terminal
|
|
9
|
+
Project-URL: Issues, https://github.com/ZawadzkiR/notebook-terminal/issues
|
|
10
|
+
Keywords: terminal,jupyter,jupyterlab,pty,conpty,xterm,flask,django,notebook,ipywidgets
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: Jupyter
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: System :: Shells
|
|
23
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: anywidget>=0.9.13
|
|
28
|
+
Requires-Dist: ipywidgets>=8.1
|
|
29
|
+
Requires-Dist: ipython>=8
|
|
30
|
+
Requires-Dist: traitlets>=5
|
|
31
|
+
Requires-Dist: jupyterlab_widgets>=3.0
|
|
32
|
+
Requires-Dist: widgetsnbextension>=4.0
|
|
33
|
+
Requires-Dist: pywinpty>=3.0.0; platform_system == "Windows"
|
|
34
|
+
Provides-Extra: flask
|
|
35
|
+
Requires-Dist: Flask>=2.3; extra == "flask"
|
|
36
|
+
Requires-Dist: flask-sock>=0.7; extra == "flask"
|
|
37
|
+
Provides-Extra: django
|
|
38
|
+
Requires-Dist: Django>=4.2; extra == "django"
|
|
39
|
+
Requires-Dist: channels>=4; extra == "django"
|
|
40
|
+
Provides-Extra: plotly
|
|
41
|
+
Requires-Dist: plotly>=5; extra == "plotly"
|
|
42
|
+
Provides-Extra: data
|
|
43
|
+
Requires-Dist: pandas>=1.5; extra == "data"
|
|
44
|
+
Requires-Dist: matplotlib>=3.6; extra == "data"
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
47
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
48
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# Notebook Terminal
|
|
52
|
+
|
|
53
|
+
A cross-platform embeddable terminal for Jupyter Notebook, JupyterLab, Flask, Django, and custom Python frontends.
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
|
|
57
|
+
> Version 0.1.1 is an alpha release. Test carefully before production use.
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- POSIX PTY support on Linux and macOS
|
|
62
|
+
- ConPTY support on Windows through `pywinpty`
|
|
63
|
+
- PowerShell, CMD, Bash, Zsh, and other shells
|
|
64
|
+
- Real-time output streaming
|
|
65
|
+
- Interactive or read-only terminal mode
|
|
66
|
+
- Programmatic commands and process input
|
|
67
|
+
- Python code and Python-file execution
|
|
68
|
+
- Matplotlib and Seaborn image tabs
|
|
69
|
+
- Interactive Plotly tabs
|
|
70
|
+
- Interactive DataFrame viewer with filtering, sorting, and pagination
|
|
71
|
+
- Native `ipywidgets` through the active Jupyter kernel
|
|
72
|
+
- Closable rich-output tabs
|
|
73
|
+
- ANSI colors and status helpers
|
|
74
|
+
- Flask WebSocket and Django Channels adapters
|
|
75
|
+
- Framework-independent `TerminalSession`
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python -m pip install notebook-terminal
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Install optional integrations:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python -m pip install "notebook-terminal[flask]"
|
|
87
|
+
python -m pip install "notebook-terminal[django]"
|
|
88
|
+
python -m pip install "notebook-terminal[data,plotly]"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For local development:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/YOUR-USERNAME/notebook-terminal.git
|
|
95
|
+
cd notebook-terminal
|
|
96
|
+
python -m pip install -e ".[dev]"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Jupyter quick start
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from notebook_terminal import terminal
|
|
103
|
+
|
|
104
|
+
term = terminal(height=450)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Run a command from another cell:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
term.run("python --version")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`term.run()` uses the existing terminal. It does not display a second copy of the widget.
|
|
114
|
+
|
|
115
|
+
## Interactive and managed modes
|
|
116
|
+
|
|
117
|
+
Interactive terminal:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
term = terminal(interactive=True)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Read-only terminal controlled only through Python:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
term = terminal(interactive=False)
|
|
127
|
+
term.run("python application.py")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
You can still send input programmatically:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
term.send_text("answer\r\n")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Running commands
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
term.run("git status")
|
|
140
|
+
term.run("pip list")
|
|
141
|
+
term.run("python script.py")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Create a separate terminal explicitly:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
second = term.run("python another_script.py", new_window=True)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Running Python code
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
term.run_python("""
|
|
154
|
+
import time
|
|
155
|
+
|
|
156
|
+
for value in range(5):
|
|
157
|
+
print(value, flush=True)
|
|
158
|
+
time.sleep(1)
|
|
159
|
+
""")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Run an existing file:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
term.run_python_file(
|
|
166
|
+
"analysis.py",
|
|
167
|
+
args=["input.csv", "--limit", "100"],
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Rich output
|
|
172
|
+
|
|
173
|
+
### Interactive DataFrame
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
term.run_python("""
|
|
177
|
+
import pandas as pd
|
|
178
|
+
|
|
179
|
+
frame = pd.DataFrame({
|
|
180
|
+
"city": ["Warsaw", "Krakow", "Gdansk", "Poznan"],
|
|
181
|
+
"value": [12, 18, 9, 15],
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
display(frame)
|
|
185
|
+
""", rich_output=True, clear_previous=True)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The DataFrame tab supports filtering, sorting, pagination, page-size selection, and horizontal scrolling.
|
|
189
|
+
|
|
190
|
+
### Matplotlib
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
term.run_python("""
|
|
194
|
+
import matplotlib.pyplot as plt
|
|
195
|
+
|
|
196
|
+
plt.plot([1, 2, 3, 4], [3, 7, 4, 8], marker="o")
|
|
197
|
+
plt.title("Example chart")
|
|
198
|
+
plt.show()
|
|
199
|
+
""", rich_output=True, clear_previous=True)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Seaborn
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
term.run_python("""
|
|
206
|
+
import pandas as pd
|
|
207
|
+
import seaborn as sns
|
|
208
|
+
import matplotlib.pyplot as plt
|
|
209
|
+
|
|
210
|
+
frame = pd.DataFrame({
|
|
211
|
+
"category": ["A", "B", "C"],
|
|
212
|
+
"value": [10, 17, 13],
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
sns.barplot(data=frame, x="category", y="value")
|
|
216
|
+
plt.show()
|
|
217
|
+
""", rich_output=True, clear_previous=True)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Plotly
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
term.run_python("""
|
|
224
|
+
import pandas as pd
|
|
225
|
+
import plotly.express as px
|
|
226
|
+
|
|
227
|
+
frame = pd.DataFrame({
|
|
228
|
+
"category": ["A", "B", "C", "D"],
|
|
229
|
+
"value": [10, 17, 13, 22],
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
figure = px.bar(frame, x="category", y="value", title="Interactive Plotly chart")
|
|
233
|
+
display(figure)
|
|
234
|
+
""", rich_output=True, clear_previous=True)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Native Jupyter widgets
|
|
238
|
+
|
|
239
|
+
Jupyter widgets need the current notebook kernel and its Comm connection. Use `run_kernel()`:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
term.run_kernel("""
|
|
243
|
+
import ipywidgets as widgets
|
|
244
|
+
from IPython.display import display
|
|
245
|
+
|
|
246
|
+
slider = widgets.IntSlider(value=25, min=0, max=100, description="Value:")
|
|
247
|
+
label = widgets.Label()
|
|
248
|
+
|
|
249
|
+
slider.observe(
|
|
250
|
+
lambda change: setattr(label, "value", f"Selected: {change['new']}"),
|
|
251
|
+
names="value",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
display(widgets.VBox([slider, label]))
|
|
255
|
+
""", clear_previous=True)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For interactive Matplotlib, install `ipympl` and use `%matplotlib widget` inside `run_kernel()`.
|
|
259
|
+
|
|
260
|
+
## Styled messages
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
term.success("Code completed successfully")
|
|
264
|
+
term.error("Operation failed")
|
|
265
|
+
term.warning("Configuration file is missing")
|
|
266
|
+
term.info("Starting analysis")
|
|
267
|
+
term.debug("Current value: 42")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Custom styling:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
from notebook_terminal import Color
|
|
274
|
+
|
|
275
|
+
term.write_line(
|
|
276
|
+
"Custom message",
|
|
277
|
+
color=Color.GREEN,
|
|
278
|
+
bold=True,
|
|
279
|
+
icon="→",
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
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`.
|
|
284
|
+
|
|
285
|
+
## Copy and paste
|
|
286
|
+
|
|
287
|
+
In interactive mode:
|
|
288
|
+
|
|
289
|
+
- select text and press `Ctrl+C` to copy
|
|
290
|
+
- press `Ctrl+C` without a selection to interrupt the active process
|
|
291
|
+
- press `Ctrl+V` to paste
|
|
292
|
+
- use the mouse wheel to scroll
|
|
293
|
+
|
|
294
|
+
In read-only mode, copying remains available, but keyboard input is not forwarded to the process.
|
|
295
|
+
|
|
296
|
+
## Process control
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
term.send_text("answer\r\n")
|
|
300
|
+
term.interrupt()
|
|
301
|
+
term.clear()
|
|
302
|
+
term.clear(clear_tabs=True)
|
|
303
|
+
term.clear_tabs()
|
|
304
|
+
term.close_tab(0)
|
|
305
|
+
term.focus()
|
|
306
|
+
term.close_terminal()
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Framework-independent session
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
from notebook_terminal import TerminalSession
|
|
313
|
+
|
|
314
|
+
session = TerminalSession()
|
|
315
|
+
|
|
316
|
+
session.subscribe(
|
|
317
|
+
lambda chunk: print(chunk.decode("utf-8", errors="replace"), end="")
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
session.run("python --version")
|
|
321
|
+
session.success("Completed")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`session.write()` sends input to the shell. `session.emit_output()` publishes display-only output to attached frontends.
|
|
325
|
+
|
|
326
|
+
## Flask
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
from flask import Flask, render_template
|
|
330
|
+
from notebook_terminal.web import flask_blueprint, manager
|
|
331
|
+
|
|
332
|
+
app = Flask(__name__)
|
|
333
|
+
app.register_blueprint(flask_blueprint(), url_prefix="/terminal")
|
|
334
|
+
|
|
335
|
+
@app.route("/")
|
|
336
|
+
def index():
|
|
337
|
+
session_id, session = manager.create(cwd=".", interactive=True)
|
|
338
|
+
return render_template("terminal.html", session_id=session_id)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Connect an xterm.js frontend to the WebSocket endpoint exposed by the adapter. Programmatic control remains available through the returned session object.
|
|
342
|
+
|
|
343
|
+
## Django
|
|
344
|
+
|
|
345
|
+
Install Django Channels and add the provided consumer to your ASGI WebSocket routes:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from django.urls import re_path
|
|
349
|
+
from notebook_terminal.web import django_consumer
|
|
350
|
+
|
|
351
|
+
TerminalConsumer = django_consumer()
|
|
352
|
+
|
|
353
|
+
websocket_urlpatterns = [
|
|
354
|
+
re_path(
|
|
355
|
+
r"^ws/terminal/(?P<sid>[0-9a-f]+)/$",
|
|
356
|
+
TerminalConsumer.as_asgi(),
|
|
357
|
+
),
|
|
358
|
+
]
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Architecture
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
TerminalSession
|
|
365
|
+
|
|
|
366
|
+
+-----------------+-----------------+
|
|
367
|
+
| | |
|
|
368
|
+
Jupyter widget Flask WebSocket Django Channels
|
|
369
|
+
| | |
|
|
370
|
+
+-----------------+-----------------+
|
|
371
|
+
|
|
|
372
|
+
PTY / ConPTY
|
|
373
|
+
|
|
|
374
|
+
PowerShell / CMD / Bash / Zsh
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Security
|
|
378
|
+
|
|
379
|
+
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.
|
|
380
|
+
|
|
381
|
+
## Development and publishing
|
|
382
|
+
|
|
383
|
+
See [PUBLISHING.md](PUBLISHING.md) for complete build, GitHub, TestPyPI, and PyPI instructions.
|
|
384
|
+
|
|
385
|
+
## License
|
|
386
|
+
|
|
387
|
+
MIT License. See [LICENSE](LICENSE).
|