---
name: macstats-system-monitor
description: Создание системного монитора для macOS с реальным временем (CPU/RAM/диск), графиками, кнопкой оптимизации и переключателем "поверх окон". Использует tkinter и built-in macOS команды.
---

# MacStats — системный монитор-стикер для macOS

## Когда использовать

Когда пользователь просит:
- «сделай монитор системы с виджетами»
- «покажи загрузку CPU/RAM в реальном времени»
- «сделай стикер с графиками на рабочий стол»
- «кнопка оптимизации Mac»

## Особенности

- **Zero dependencies** — только Python3 tkinter (идёт с Python) + built-in macOS команды
- **Реальное время** — обновление каждые 2 сек
- **CPU график** — 30-точечный линейный график
- **RAM/Диск** — прогресс-бары
- **Всегда поверх окон / режим окна** — переключается кнопкой
- **Кнопка оптимизации** — чистит кэши + purge
- **Лёгкий** — ~300 строк, 0 фоновых процессов
- **Кнопка оптимизации (⚡)** — убивает ненужные процессы + чистит кэши + purge
- **Список bloat-процессов** — ~70 известных фоновых мусорных процессов (updaters, helpers, daemons) которые всегда безопасно убивать

## Файлы

- `~/.hermes/scripts/macstats.py` — основной скрипт
- `~/.hermes/scripts/macstats.sh` — лаунчер (через Terminal, для отладки)
- `~/Desktop/MacStats.app` — desktop shortcut **без терминала** (двойной клик → запуск окна)

### Как создать .app-пакет (не требует терминала)

```bash
# Структура
mkdir -p ~/Desktop/MacStats.app/Contents/MacOS
mkdir -p ~/Desktop/MacStats.app/Contents/Resources

# Launcher (bash-скрипт) — обязательно ПОЛНЫЙ путь к python3!
# При запуске из .app PATH = /usr/bin:/bin:/usr/sbin:/sbin (без /Library/Frameworks)
# Поэтому 'python3' без полного пути выдаст 'command not found' и LSOpenURLsWithRole error.
cat > ~/Desktop/MacStats.app/Contents/MacOS/macstats << 'EOF'
#!/bin/bash
PYTHON="/Library/Frameworks/Python.framework/Versions/3.12/bin/python3"
RESOURCES="$(dirname "$0")/../Resources"
exec "$PYTHON" "$RESOURCES/macstats.py"
EOF
chmod +x ~/Desktop/MacStats.app/Contents/MacOS/macstats

# Поместить основной скрипт в Resources
cp ~/.hermes/scripts/macstats.py ~/Desktop/MacStats.app/Contents/Resources/

# Info.plist
cat > ~/Desktop/MacStats.app/Contents/Info.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleExecutable</key><string>macstats</string>
  <key>CFBundleIdentifier</key><string>com.hermes.macstats</string>
  <key>CFBundleName</key><string>MacStats</string>
  <key>CFBundleDisplayName</key><string>MacStats</string>
  <key>CFBundleVersion</key><string>1.0</string>
  <key>CFBundlePackageType</key><string>APPL</string>
  <key>LSUIElement</key><true/>
</dict>
</plist>
PLIST

# Теперь ~/Desktop/MacStats.app готов — двойной клик без Terminal
```

**Ключ `LSUIElement = true`** делает приложение без иконки в Dock и без строки меню — идеально для виджета на рабочем столе.

## Использование

```bash
# Запустить вручную
python3 ~/.hermes/scripts/macstats.py

# Автозапуск при входе
launchctl load ~/Library/LaunchAgents/com.hermes.macstats.plist

# Отключить автозапуск
launchctl unload ~/Library/LaunchAgents/com.hermes.macstats.plist
```

## Управление

| Действие | Результат |
|----------|-----------|
| Shift+Esc | Скрыть/показать окно |
| Перетащить за заголовок | Переместить окно |
| ✕ (красный) | Закрыть |
|| ⚡ Оптимизировать | Убить bloat-процессы + очистить кэши + память |
| 📌 Поверх окон | Переключатель always-on-top |

## Получение данных о системе (без psutil)

Ключевые команды macOS для мониторинга:

```python
# CPU — сумма %CPU из ps aux
ps aux | awk 'NR>1 {s+=$3} END {print s}'

# RAM — из vm_stat (страницы 4KB)
vm_stat
# Парсинг: Pages free, active, wired down, inactive
# used = active + wired
# total = used + free + inactive
# percent = (used / total) * 100
# GB = (used * 4096) / 1024^3

# Диск — из df
df -h /

# Общая RAM
sysctl hw.memsize

# Очистка неактивной памяти
sudo purge

# Сброс memory pressure
memory_pressure
```

## LaunchAgent для автозапуска

**Вариант А — .app (рекомендуется, без терминала):**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.hermes.macstats</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/open</string>
        <string>/Users/valen/Desktop/MacStats.app</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
    <key>StandardOutPath</key>
    <string>/tmp/macstats.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/macstats.err</string>
</dict>
</plist>
```

**Вариант Б — скрипт (через Terminal):**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.hermes.macstats</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/valen/.hermes/scripts/macstats.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
</dict>
</plist>
```

### Перезагрузка LaunchAgent
```bash
launchctl unload ~/Library/LaunchAgents/com.hermes.macstats.plist
launchctl load ~/Library/LaunchAgents/com.hermes.macstats.plist
```

## Технические детали

- **Tkinter без рамки**: `overrideredirect(True)`
- **Прозрачность**: `attributes("-alpha", 0.85)`
- **Always on top**: `attributes("-topmost", True/False)`
- **Перетаскивание**: Bind `Button-1` + `B1-Motion` ТОЛЬКО к title_bar, НЕ к root Tk — иначе кнопки не работают
- **График**: Canvas с линиями
- **Оптимизация в фоне**: threading (чтобы не вешать GUI) — без threading GUI зависает на время `rm -rf`
- **Убийство bloat-процессов**: `killall -9` по фиксированному списку BLOAT_PROCESSES (~70 имён: updaters, helpers, daemons). Сначала без sudo, при неудаче — с sudo. Список определён константой в начале скрипта.
- **Обновление**: `after(2000, self.update_stats)`

## Известные грабли (из опыта)

1. **Drag перекрывает кнопки**: если привязать `<Button-1>` к корневому `self`, то все Button-виджеты перестают получать клики. Решение: привязывать drag ТОЛЬКО к title_bar.
2. **F-строки в линтере**: линтер Hermes может ругаться SyntaxError на f-строки (запускается под Python < 3.6). Игнорировать — проверять настоящим python3.
3. **threading для оптимизации**: `rm -rf` кэшей может тормозить. Обёртка в threading + `after(0, callback)` для обновления UI из главного потока.
4. **compact mode**: кнопки лучше делать иконками (⚡ 📌 ✕) без текста — так окно можно ужать до 140px высоты.
5. **purge без sudo**: `sudo purge` на High Sierra без passwordless sudo не сработает. Просто обработать ошибку и показать статус.
6. **Оптимизация запуска**: после обновления скрипта убить старый процесс и запустить новый. LaunchAgent тоже перезагрузить (unload + load).
7. **.command vs .app**: пользователь может не хотеть видеть окно Terminal при запуске. Вместо `MacStats.command` делай `MacStats.app` — полноценный macOS-пакет без терминала. Структура: `Contents/MacOS/launcher` (exec bash), `Contents/Resources/macstats.py`, `Contents/Info.plist` с `LSUIElement = true`.
8. **PID файл для .app**: при запуске через .app не забудь писать PID в `/tmp/macstats.pid` (для pkill при обновлении), а при закрытии — удалять.
9. **Sudoers для purge**: `sudo -n purge` требует `NOPASSWD` в `/etc/sudoers.d/macstats`. Команда: `echo "$(whoami) ALL=(ALL) NOPASSWD: /usr/sbin/purge, /usr/bin/killall" | sudo tee /etc/sudoers.d/macstats > /dev/null && sudo chmod 440 /etc/sudoers.d/macstats`.
