diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9019739 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.eggs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..da8f161 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Electric Entropy Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa9b689 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# entropymon + +**Terminal system monitor by Electric Entropy Lab** + +A powerful, feature-rich terminal system monitor built with Python curses. Better than glances, prettier than htop. + +## Features + +- **CPU** - per-core usage bars, sparkline history, frequency, load average +- **Memory** - RAM/Swap with usage bars +- **Disk** - partitions with usage, IO rates (read/write) +- **Network** - RX/TX rates with sparklines, per-interface info +- **GPU** - NVIDIA (via NVML) and AMD (via sysfs) - utilization, VRAM, temp, power, fan, clocks +- **Temperatures** - CPU, GPU, NVMe, network adapters +- **Processes** - interactive management with sorting, filtering, kill signals, renice, details +- **Multi-language** - EN, PL, DE, ES, FR, UK, ZH with runtime switching +- **Animated intro** - matrix rain boot sequence with decrypt logo animation + +## Install + +```bash +pip install entropymon +``` + +With NVIDIA GPU support: + +```bash +pip install entropymon[nvidia] +``` + +## Usage + +```bash +entropymon # start with auto-detected language +entropymon --lang pl # start in Polish +entropymon --no-intro # skip boot animation +entropymon --reset-lang # reset saved language preference +entropymon --version # show version +``` + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `h` / `F1` / `?` | Help overlay | +| `L` | Switch language | +| `c` / `m` / `p` / `n` | Sort by CPU/MEM/PID/Name | +| `r` | Reverse sort | +| `j` / `k` / arrows | Navigate processes | +| `T` | Send SIGTERM | +| `K` | Send SIGKILL | +| `F9` | Signal selection menu | +| `+` / `-` | Renice process | +| `d` / `F5` / Enter | Process details | +| `/` / `f` | Filter processes | +| `q` / `Esc` | Quit | + +## Requirements + +- Python 3.8+ +- Linux (uses `/proc`, `/sys`) +- psutil +- pynvml (optional, for NVIDIA GPU) + +## License + +MIT - Electric Entropy Lab diff --git a/entropymon/__init__.py b/entropymon/__init__.py new file mode 100644 index 0000000..783308e --- /dev/null +++ b/entropymon/__init__.py @@ -0,0 +1,3 @@ +"""entropymon - Terminal system monitor by Electric Entropy Lab.""" + +__version__ = "1.0.0" diff --git a/entropymon/__main__.py b/entropymon/__main__.py new file mode 100644 index 0000000..78ea302 --- /dev/null +++ b/entropymon/__main__.py @@ -0,0 +1,5 @@ +"""Allow running as: python -m entropymon""" +from entropymon.monitor import main + +if __name__ == "__main__": + main() diff --git a/systats.py b/entropymon/monitor.py similarity index 96% rename from systats.py rename to entropymon/monitor.py index 794ef51..3c2cd82 100755 --- a/systats.py +++ b/entropymon/monitor.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""systats - Terminal system monitor (better than glances).""" +"""entropymon - Terminal system monitor by Electric Entropy Lab.""" import curses import os @@ -91,7 +91,7 @@ TRANSLATIONS = { "set_nice": {"en": "Set nice {val} for {name} (PID {pid})", "pl": "Ustawiono priorytet {val} dla {name} (PID {pid})", "de": "Nice {val} fuer {name} gesetzt (PID {pid})", "es": "Nice {val} para {name} (PID {pid})", "fr": "Nice {val} pour {name} (PID {pid})", "uk": "Vstanovleno priorytet {val} dlya {name} (PID {pid})", "zh": "Yi shezhi nice {val} {name} (PID {pid})"}, "invalid_nice": {"en": "Invalid nice value", "pl": "Nieprawidlowy priorytet", "de": "Ungueltiger Nice-Wert", "es": "Valor nice invalido", "fr": "Valeur nice invalide", "uk": "Nepravyl'nyj priorytet", "zh": "Wuxiao nice zhi"}, # Help - "help_title": {"en": "SYSTATS - Keyboard Shortcuts", "pl": "SYSTATS - Skroty klawiszowe", "de": "SYSTATS - Tastenkuerzel", "es": "SYSTATS - Atajos de teclado", "fr": "SYSTATS - Raccourcis clavier", "uk": "SYSTATS - Klavishni skroty", "zh": "SYSTATS - Jianpan kuaijiejian"}, + "help_title": {"en": "ENTROPYMON - Keyboard Shortcuts", "pl": "ENTROPYMON - Skroty klawiszowe", "de": "ENTROPYMON - Tastenkuerzel", "es": "ENTROPYMON - Atajos de teclado", "fr": "ENTROPYMON - Raccourcis clavier", "uk": "ENTROPYMON - Klavishni skroty", "zh": "ENTROPYMON - Jianpan kuaijiejian"}, "help_close": {"en": "Press h, F1 or ? to close", "pl": "Nacisnij h, F1 lub ? aby zamknac", "de": "h, F1 oder ? zum Schliessen", "es": "Pulsa h, F1 o ? para cerrar", "fr": "Appuyez h, F1 ou ? pour fermer", "uk": "Natysnit' h, F1 abo ? shchob zakryty", "zh": "An h, F1 huo ? guanbi"}, "help_nav": {"en": "NAVIGATION", "pl": "NAWIGACJA", "de": "NAVIGATION", "es": "NAVEGACION", "fr": "NAVIGATION", "uk": "NAVIGACIYA", "zh": "DAOHANG"}, "help_down": {"en": "Move cursor down", "pl": "Kursor w dol", "de": "Cursor nach unten", "es": "Mover cursor abajo", "fr": "Curseur vers le bas", "uk": "Kursor vnyz", "zh": "Guangbiao xia yi"}, @@ -769,7 +769,7 @@ class Renderer: d = self.data hostname = os.uname().nodename kernel = os.uname().release - header = f" SYSTATS | {hostname} | Linux {kernel} | {t('up')}: {d.uptime} " + header = f" ENTROPYMON | {hostname} | Linux {kernel} | {t('up')}: {d.uptime} " header += f"| {t('load')}: {d.load_avg[0]:.2f} {d.load_avg[1]:.2f} {d.load_avg[2]:.2f} " header += f"| {t('procs')}: {d.proc_count} ({d.proc_running} {t('run')}) " brand = " Electric Entropy Lab " @@ -1219,23 +1219,29 @@ class Renderer: VERSION = "1.0.0" LOGO_ART = [ - " ███████╗██╗ ██╗███████╗████████╗ █████╗ ████████╗███████╗", - " ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔══██╗╚══██╔══╝██╔════╝", - " ███████╗ ╚████╔╝ ███████╗ ██║ ███████║ ██║ ███████╗", - " ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══██║ ██║ ╚════██║", - " ███████║ ██║ ███████║ ██║ ██║ ██║ ██║ ███████║", - " ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝", + " ███████╗███╗ ██╗████████╗██████╗ ██████╗ ██████╗ ██╗ ██╗", + " ██╔════╝████╗ ██║╚══██╔══╝██╔══██╗██╔═══██╗██╔══██╗╚██╗ ██╔╝", + " █████╗ ██╔██╗ ██║ ██║ ██████╔╝██║ ██║██████╔╝ ╚████╔╝ ", + " ██╔══╝ ██║╚██╗██║ ██║ ██╔══██╗██║ ██║██╔═══╝ ╚██╔╝ ", + " ███████╗██║ ╚████║ ██║ ██║ ██║╚██████╔╝██║ ██║ ", + " ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ", + " ███╗ ███╗ ██████╗ ███╗ ██╗ ", + " ████╗ ████║██╔═══██╗████╗ ██║ ", + " ██╔████╔██║██║ ██║██╔██╗ ██║ ", + " ██║╚██╔╝██║██║ ██║██║╚██╗██║ ", + " ██║ ╚═╝ ██║╚██████╔╝██║ ╚████║ ", + " ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ", ] BOOT_LINES = [ - ("> SYSTATS v{ver}", 0.0), + ("> ENTROPYMON v{ver}", 0.0), ("> initializing system probes... OK", 0.3), ("> scanning CPU cores... {cores} detected", 0.6), ("> GPU subsystem... {gpu}", 0.9), ("> network interfaces... ONLINE", 1.1), ("> thermal sensors... {temps}", 1.3), ("> process table... READY", 1.5), - ("> Electric Entropy Lab // SYSTATS", 1.8), + ("> Electric Entropy Lab // ENTROPYMON", 1.8), ] import random as _random @@ -1298,7 +1304,7 @@ def intro_splash(stdscr): # ── Phase 2: Boot sequence with typing effect ── boot_lines = [ - (f"> SYSTATS v{VERSION} // Electric Entropy Lab", C_ACCENT), + (f"> ENTROPYMON v{VERSION} // Electric Entropy Lab", C_ACCENT), (f"> host: {hostname}", C_DIM), (f"> scanning CPU... {cores_phys}C/{cores}T detected", C_LOW), (f"> memory probe... {mem_str} OK", C_LOW), @@ -1514,7 +1520,7 @@ def intro_splash(stdscr): show_intro = True -def main(stdscr): +def _curses_main(stdscr): if show_intro: intro_splash(stdscr) @@ -1673,7 +1679,7 @@ def main(stdscr): def get_config_path(): - config_dir = os.path.join(os.path.expanduser("~"), ".config", "systats") + config_dir = os.path.join(os.path.expanduser("~"), ".config", "entropymon") os.makedirs(config_dir, exist_ok=True) return os.path.join(config_dir, "config") @@ -1700,7 +1706,7 @@ def save_lang(lang): def ask_lang(): print(" \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510") - print(" \u2502 SYSTATS - Electric Entropy Lab \u2502") + print(" \u2502 ENTROPYMON - Electric Entropy Lab \u2502") print(" \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524") print(" \u2502 Choose your language: \u2502") print(" \u2502 \u2502") @@ -1723,9 +1729,13 @@ def ask_lang(): print(f" 1-{len(LANGS)} or lang code ({'/'.join(LANGS)})") -if __name__ == "__main__": +def main(): + """CLI entry point for entropymon.""" + global current_lang, show_intro import argparse - parser = argparse.ArgumentParser(description="systats - Terminal system monitor | Electric Entropy Lab") + parser = argparse.ArgumentParser( + prog="entropymon", + description="entropymon - Terminal system monitor | Electric Entropy Lab") lang_help = ", ".join(f"{c} ({n})" for c, n in LANG_NAMES.items()) parser.add_argument("--lang", choices=LANGS, default=None, help=f"UI language: {lang_help}") @@ -1733,6 +1743,7 @@ if __name__ == "__main__": help="Reset saved language preference and ask again") parser.add_argument("--no-intro", action="store_true", help="Skip the boot intro animation") + parser.add_argument("--version", action="version", version=f"entropymon {VERSION}") args = parser.parse_args() if args.reset_lang: @@ -1765,6 +1776,10 @@ if __name__ == "__main__": show_intro = False try: - curses.wrapper(main) + curses.wrapper(_curses_main) except KeyboardInterrupt: pass + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a45f059 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["setuptools>=68.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "entropymon" +dynamic = ["version"] +description = "Terminal system monitor with GPU support, process management, and multi-language UI" +readme = "README.md" +license = "MIT" +requires-python = ">=3.8" +authors = [ + {name = "Electric Entropy Lab", email = "patryk@electricentropy.eu"}, +] +keywords = ["monitor", "system", "terminal", "tui", "gpu", "nvidia", "amd", "htop", "glances", "curses"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console :: Curses", + "Intended Audience :: System Administrators", + "Intended Audience :: Developers", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: System :: Monitoring", + "Topic :: Utilities", +] +dependencies = [ + "psutil>=5.9.0", +] + +[project.optional-dependencies] +nvidia = ["pynvml>=11.0.0"] +all = ["pynvml>=11.0.0"] + +[project.urls] +Homepage = "https://git.electricentropy.eu/ar3dh3l/systats" +Repository = "https://git.electricentropy.eu/ar3dh3l/systats" +Issues = "https://git.electricentropy.eu/ar3dh3l/systats/issues" + +[project.scripts] +entropymon = "entropymon.monitor:main" + +[tool.setuptools.dynamic] +version = {attr = "entropymon.__version__"} + +[tool.setuptools.packages.find] +include = ["entropymon*"]