Skip to content

Commit ba8d689

Browse files
committed
Initial commit
0 parents  commit ba8d689

17 files changed

Lines changed: 1470 additions & 0 deletions

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Python
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
7+
# Virtual environments
8+
.venv/
9+
venv/
10+
11+
# Logs
12+
logs/
13+
*.log
14+
15+
# Editor / OS
16+
.vscode/
17+
.DS_Store
18+
Thumbs.db
19+
20+
# PyInstaller
21+
build/
22+
dist/
23+
*.spec

README.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Pythonator
2+
3+
**Pythonator** is a desktop application for running, monitoring, and managing multiple Python scripts concurrently through a clean graphical interface.
4+
It is designed for developers who want predictable process control, readable logs, and simple environment management without relying on multiple terminal sessions.
5+
6+
At its core, Pythonator is a **local Python process manager with a GUI**.
7+
8+
<p align="left">
9+
<img alt="Python" src="https://img.shields.io/badge/Python-3.13%2B-blue" />
10+
<img alt="PyQt6" src="https://img.shields.io/badge/UI-PyQt6-41CD52" />
11+
<img alt="License" src="https://img.shields.io/badge/License-MIT-black" />
12+
</p>
13+
14+
---
15+
16+
## Screenshots
17+
18+
<p align="center">
19+
<img src="screenshots/image.png" alt="Pythonator – Main window" width="92%">
20+
</p>
21+
22+
<p align="center">
23+
<img src="screenshots/logs.png" alt="Pythonator – Live logs" width="45%">
24+
<img src="screenshots/editor.png" alt="Pythonator – Built-in editor" width="45%">
25+
</p>
26+
27+
<details>
28+
<summary><b>Venv setup & log history</b></summary>
29+
<br/>
30+
<p align="center">
31+
<img src="screenshots/history-search.png" alt="History and search mode" width="92%">
32+
</p>
33+
<p align="center">
34+
<img src="screenshots/venv.png" alt="Virtual environment management" width="92%">
35+
</p>
36+
</details>
37+
38+
---
39+
40+
## Capabilities
41+
42+
* ✅ Full ANSI color recognition (256-color, true color, bold, inverse)
43+
* ✅ Multi-bot process management with isolation
44+
* ✅ Separate execution paths for UI and processes (`QProcess`, `QThreadPool`)
45+
* ✅ Live / History / Search log modes
46+
* ✅ CPU and RAM usage monitoring (via `psutil`)
47+
* ✅ Virtual environment creation and management
48+
* ✅ Dependency installation from the UI
49+
* ✅ Auto-restart on crash (optional)
50+
* ✅ Integrated Python code editor
51+
* ✅ Syntax highlighting and line numbers
52+
* ✅ Jedi-based code completion (optional)
53+
* ✅ Persistent log storage
54+
* ✅ Dark theme by default
55+
56+
---
57+
58+
## What it does
59+
60+
* Runs **multiple Python scripts simultaneously**
61+
* Assigns **one log tab per script**
62+
* Displays logs exactly as produced by the process (ANSI-accurate)
63+
* Provides **start / stop / restart** controls per script or globally
64+
* Supports **custom commands** and script flags
65+
* Tracks resource usage per process tree
66+
* Keeps logs on drive for later inspection
67+
68+
---
69+
70+
## Why it exists
71+
72+
Pythonator was built to remove friction commonly encountered during local development:
73+
74+
* switching multiple terminals
75+
* manually tracking virtual environments
76+
* restarting crashed scripts
77+
* inspecting large volumes of console output
78+
* running long-lived background processes while coding
79+
80+
It is particularly useful for:
81+
82+
* local bots and agents
83+
* background workers
84+
* small services
85+
* development tooling
86+
* long-running experiments
87+
88+
---
89+
90+
## File structure
91+
92+
| File | Purpose |
93+
| ---------------- | ------------------------------------------------ |
94+
| `app.py` | Application entry point, palette and theme setup |
95+
| `config.py` | Configuration, data models, shared styles |
96+
| `console.py` | ANSI terminal emulator (256-color, true color) |
97+
| `log_buffer.py` | Ring buffer with timestamps and persistence |
98+
| `log_view.py` | Per-bot log viewer (live/history/search) |
99+
| `stats.py` | CPU and RAM monitoring |
100+
| `process_mgr.py` | Process lifecycle and isolation |
101+
| `editor.py` | Python editor with highlighting and completion |
102+
| `main_window.py` | Main application UI |
103+
104+
---
105+
106+
## Requirements
107+
108+
* Python **3.13+**
109+
* `PyQt6` UI framework
110+
* `psutil` CPU/RAM statistics
111+
* `jedi` editor autocompletion (optional)
112+
113+
---
114+
115+
Here’s a clean, **non-intimidating but professional** update to the **Running the application** section that covers **Windows executables** and points to a release `.zip`.
116+
117+
You can drop this straight into your README, replacing the existing section.
118+
119+
---
120+
121+
## Running the application
122+
123+
### Option 1: Run from source (any platform)
124+
125+
Requires Python **3.13+**.
126+
127+
```bash
128+
git clone https://github.com/cfunkz/Pythonator.git
129+
cd Pythonator
130+
python app.py
131+
```
132+
133+
---
134+
135+
### Option 2: Windows executable (no Python required for the app itself)
136+
137+
For Windows users, a prebuilt executable is available.
138+
139+
1. Download the latest release from:
140+
**[https://github.com/cfunkz/Pythonator/releases](https://github.com/cfunkz/Pythonator/releases)**
141+
2. Download the **`.zip`** file for Windows
142+
3. Extract it
143+
4. Run `Pythonator.exe`
144+
145+
No Python installation is required **to run the application itself**.
146+
147+
> ⚠️ Note:
148+
> To run Python scripts *inside* Pythonator, you will still need a Python installation or a configured Python interpreter path.
149+
> The executable only bundles Pythonator, not a full Python runtime for user scripts.
150+
151+
---
152+
153+
### Platform notes
154+
155+
* **Windows**: Supported via executable (`.exe`)
156+
* **macOS / Linux**: Run from source (for now)
157+
* Logs, configs, and virtual environments are stored locally next to the application
158+
159+
No installer is required.
160+
161+
---
162+
163+
## Typical workflow
164+
165+
1. Create a new workspace
166+
2. Select a Python script (or custom command)
167+
3. Optionally edit the script using the built-in editor
168+
4. Optionally configure a custom Python interpreter
169+
5. Set up a virtual environment
170+
6. Install dependencies
171+
7. Start the process
172+
8. Monitor logs and resource usage
173+
174+
---
175+
176+
## License
177+
178+
MIT License.
179+
Use it freely, modify it, and build on it.

app.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Pythonator - Python Bot Runner."""
2+
import sys, os
3+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
4+
5+
from PyQt6.QtGui import QIcon, QColor, QPalette
6+
from PyQt6.QtWidgets import QApplication, QStyleFactory
7+
from config import STYLE
8+
from main_window import MainWindow
9+
10+
def dark_palette() -> QPalette:
11+
pal = QPalette()
12+
dark, darker, light, mid, accent = QColor(24,24,24), QColor(18,18,18), QColor(220,220,220), QColor(35,35,35), QColor(70,130,220)
13+
for role, color in [(QPalette.ColorRole.Window, dark), (QPalette.ColorRole.WindowText, light),
14+
(QPalette.ColorRole.Base, darker), (QPalette.ColorRole.AlternateBase, dark), (QPalette.ColorRole.Text, light),
15+
(QPalette.ColorRole.Button, mid), (QPalette.ColorRole.ButtonText, light),
16+
(QPalette.ColorRole.Highlight, accent), (QPalette.ColorRole.HighlightedText, QColor(255,255,255)),
17+
(QPalette.ColorRole.ToolTipBase, dark), (QPalette.ColorRole.ToolTipText, light),
18+
(QPalette.ColorRole.Link, accent), (QPalette.ColorRole.LinkVisited, QColor(180,130,220)),
19+
(QPalette.ColorRole.PlaceholderText, QColor(128,128,128))]:
20+
pal.setColor(role, color)
21+
return pal
22+
23+
def main() -> int:
24+
app = QApplication(sys.argv)
25+
app.setStyle(QStyleFactory.create("Fusion"))
26+
app.setPalette(dark_palette())
27+
font = app.font(); font.setPointSize(10); app.setFont(font)
28+
app.setStyleSheet(STYLE)
29+
try: app.setWindowIcon(QIcon("icon.ico"))
30+
except: pass
31+
window = MainWindow()
32+
try: window.setWindowIcon(QIcon("icon.ico"))
33+
except: pass
34+
window.show()
35+
return app.exec()
36+
37+
if __name__ == "__main__":
38+
sys.exit(main())

config.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Configuration, data models, and shared styles."""
2+
from __future__ import annotations
3+
import json, re, sys
4+
from dataclasses import dataclass, asdict
5+
from pathlib import Path
6+
7+
__version__ = "1.0.0"
8+
9+
# Paths
10+
def _app_dir() -> Path:
11+
return Path(sys.executable if getattr(sys, "frozen", False) else __file__).resolve().parent
12+
13+
APP_DIR = _app_dir()
14+
CONFIG_FILE = APP_DIR / "bots.json"
15+
LOGS_DIR = APP_DIR / "logs"
16+
17+
# Tuning
18+
MAX_LOG_LINES = 50_000
19+
FLUSH_INTERVAL_MS = 100
20+
STATS_INTERVAL_MS = 1000
21+
HISTORY_CHUNK = 5000
22+
KILL_TIMEOUT_MS = 500
23+
MAX_FLUSH_CHARS = 50_000
24+
25+
# ANSI
26+
ANSI_RE = re.compile(r'\x1b\[[0-9;]*[A-Za-z]')
27+
strip_ansi = lambda t: ANSI_RE.sub('', t)
28+
normalize = lambda t: t.replace("\x00", "").replace("\r\n", "\n").replace("\r", "\n")
29+
30+
@dataclass
31+
class Bot:
32+
name: str
33+
entry: str = ""
34+
reqs: str = ""
35+
flags: str = ""
36+
custom_cmd: bool = False
37+
python_path: str = ""
38+
39+
def load_config() -> dict[str, Bot]:
40+
if not CONFIG_FILE.exists(): return {}
41+
try:
42+
data = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
43+
return {n: Bot(**{**{"custom_cmd": False, "python_path": ""}, **c}) for n, c in data.items()}
44+
except: return {}
45+
46+
def save_config(bots: dict[str, Bot]) -> None:
47+
try: CONFIG_FILE.write_text(json.dumps({n: asdict(b) for n, b in bots.items()}, indent=2), encoding="utf-8")
48+
except: pass
49+
50+
# Shared styles
51+
STYLE = """
52+
QToolTip { background: #252525; color: #ddd; border: 1px solid #444; padding: 4px; border-radius: 2px; }
53+
QScrollBar:vertical { background: #1a1a1a; width: 12px; }
54+
QScrollBar::handle:vertical { background: #404040; min-height: 20px; border-radius: 4px; margin: 2px; }
55+
QScrollBar::handle:vertical:hover { background: #505050; }
56+
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
57+
QScrollBar:horizontal { background: #1a1a1a; height: 12px; }
58+
QScrollBar::handle:horizontal { background: #404040; min-width: 20px; border-radius: 4px; margin: 2px; }
59+
QScrollBar::handle:horizontal:hover { background: #505050; }
60+
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }
61+
"""
62+
BTN = """QPushButton { padding: 4px 10px; border: 1px solid #333; border-radius: 2px; background: #252525; }
63+
QPushButton:hover { background: #303030; border-color: #444; }
64+
QPushButton:pressed { background: #202020; }
65+
QPushButton:disabled { color: #555; background: #1a1a1a; }"""
66+
INPUT = """QLineEdit, QPlainTextEdit { padding: 4px 8px; border: 1px solid #333; border-radius: 2px; background: #1a1a1a; }
67+
QLineEdit:focus, QPlainTextEdit:focus { border-color: #4688d8; }"""

0 commit comments

Comments
 (0)