Rename to entropymon, restructure as PyPI package

- Renamed systats -> entropymon everywhere (code, logo, config paths)
- New ASCII art logo: ENTROPY MON
- Proper Python package structure: entropymon/{__init__,__main__,monitor}.py
- pyproject.toml with entry_points (pip install -> entropymon command)
- MIT license, README with full docs
- Works: pip install, entropymon CLI, python -m entropymon
- Optional nvidia extra: pip install entropymon[nvidia]
- --version flag added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lukasz@orzechowski.eu
2026-03-24 01:18:23 +01:00
parent 34797a32d1
commit 25da63dc9e
7 changed files with 188 additions and 18 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
__pycache__/
*.pyc
*.pyo
*.egg-info/
dist/
build/
.eggs/

21
LICENSE Normal file
View File

@@ -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.

67
README.md Normal file
View File

@@ -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

3
entropymon/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
"""entropymon - Terminal system monitor by Electric Entropy Lab."""
__version__ = "1.0.0"

5
entropymon/__main__.py Normal file
View File

@@ -0,0 +1,5 @@
"""Allow running as: python -m entropymon"""
from entropymon.monitor import main
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""systats - Terminal system monitor (better than glances).""" """entropymon - Terminal system monitor by Electric Entropy Lab."""
import curses import curses
import os 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})"}, "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"}, "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
"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_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_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"}, "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 d = self.data
hostname = os.uname().nodename hostname = os.uname().nodename
kernel = os.uname().release 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('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')}) " header += f"| {t('procs')}: {d.proc_count} ({d.proc_running} {t('run')}) "
brand = " Electric Entropy Lab " brand = " Electric Entropy Lab "
@@ -1219,23 +1219,29 @@ class Renderer:
VERSION = "1.0.0" VERSION = "1.0.0"
LOGO_ART = [ LOGO_ART = [
" ███████╗██╗ ██╗███████╗████████╗ █████╗ ████████╗███████╗", " ███████╗██╗ ██╗██████████████╗ █████╗ ██████╗ ██╗ ██╗",
" ██╔════╝██╗ ██╔╝██╔════╝╚══██╔══╝██╔══██╗╚══██╔══██╔════", " ██╔════╝████╗ ██╚══██╔══╝██╔══██╗██╔═══██╗██╔══██╗╚██╗ ██╔",
" ███████╗ ╚████╔╝ ███████╗ ██║ ███████║ ██║ ███████╗", " █████ ████╗ ██║ ██║ ██████╔╝██║ ██║██████╔╝ ╚████╔╝ ",
" ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══██║ ██║ ╚════██║", " ██╔══╝ ██║╚████║ ██║ ██╔══██╗██║ ██║██╔═══╝ ╚██╔╝ ",
" ███████║ ██║ ███████║ ██║ ██║ ██║ ██║ ███████║", " ███████╗██║ ╚████║ ██║ ██║ ██║╚██████╔╝██║ ██║ ",
" ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝", " ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ",
" ███╗ ███╗ ██████╗ ███╗ ██╗ ",
" ████╗ ████║██╔═══██╗████╗ ██║ ",
" ██╔████╔██║██║ ██║██╔██╗ ██║ ",
" ██║╚██╔╝██║██║ ██║██║╚██╗██║ ",
" ██║ ╚═╝ ██║╚██████╔╝██║ ╚████║ ",
" ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ",
] ]
BOOT_LINES = [ BOOT_LINES = [
("> SYSTATS v{ver}", 0.0), ("> ENTROPYMON v{ver}", 0.0),
("> initializing system probes... OK", 0.3), ("> initializing system probes... OK", 0.3),
("> scanning CPU cores... {cores} detected", 0.6), ("> scanning CPU cores... {cores} detected", 0.6),
("> GPU subsystem... {gpu}", 0.9), ("> GPU subsystem... {gpu}", 0.9),
("> network interfaces... ONLINE", 1.1), ("> network interfaces... ONLINE", 1.1),
("> thermal sensors... {temps}", 1.3), ("> thermal sensors... {temps}", 1.3),
("> process table... READY", 1.5), ("> process table... READY", 1.5),
("> Electric Entropy Lab // SYSTATS", 1.8), ("> Electric Entropy Lab // ENTROPYMON", 1.8),
] ]
import random as _random import random as _random
@@ -1298,7 +1304,7 @@ def intro_splash(stdscr):
# ── Phase 2: Boot sequence with typing effect ── # ── Phase 2: Boot sequence with typing effect ──
boot_lines = [ 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"> host: {hostname}", C_DIM),
(f"> scanning CPU... {cores_phys}C/{cores}T detected", C_LOW), (f"> scanning CPU... {cores_phys}C/{cores}T detected", C_LOW),
(f"> memory probe... {mem_str} OK", C_LOW), (f"> memory probe... {mem_str} OK", C_LOW),
@@ -1514,7 +1520,7 @@ def intro_splash(stdscr):
show_intro = True show_intro = True
def main(stdscr): def _curses_main(stdscr):
if show_intro: if show_intro:
intro_splash(stdscr) intro_splash(stdscr)
@@ -1673,7 +1679,7 @@ def main(stdscr):
def get_config_path(): 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) os.makedirs(config_dir, exist_ok=True)
return os.path.join(config_dir, "config") return os.path.join(config_dir, "config")
@@ -1700,7 +1706,7 @@ def save_lang(lang):
def ask_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(" \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(" \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 Choose your language: \u2502")
print(" \u2502 \u2502") print(" \u2502 \u2502")
@@ -1723,9 +1729,13 @@ def ask_lang():
print(f" 1-{len(LANGS)} or lang code ({'/'.join(LANGS)})") 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 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()) lang_help = ", ".join(f"{c} ({n})" for c, n in LANG_NAMES.items())
parser.add_argument("--lang", choices=LANGS, default=None, parser.add_argument("--lang", choices=LANGS, default=None,
help=f"UI language: {lang_help}") help=f"UI language: {lang_help}")
@@ -1733,6 +1743,7 @@ if __name__ == "__main__":
help="Reset saved language preference and ask again") help="Reset saved language preference and ask again")
parser.add_argument("--no-intro", action="store_true", parser.add_argument("--no-intro", action="store_true",
help="Skip the boot intro animation") help="Skip the boot intro animation")
parser.add_argument("--version", action="version", version=f"entropymon {VERSION}")
args = parser.parse_args() args = parser.parse_args()
if args.reset_lang: if args.reset_lang:
@@ -1765,6 +1776,10 @@ if __name__ == "__main__":
show_intro = False show_intro = False
try: try:
curses.wrapper(main) curses.wrapper(_curses_main)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
if __name__ == "__main__":
main()

52
pyproject.toml Normal file
View File

@@ -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*"]