syscheck-cli 0.2.0__py3-none-any.whl

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.
syscheck/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """syscheck — CLI-утилита диагностики системы."""
2
+ __version__ = "0.2.0"
syscheck/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Allow running as: python -m syscheck"""
2
+ from syscheck.cli import main
3
+
4
+ main()
syscheck/cli.py ADDED
@@ -0,0 +1,134 @@
1
+ """Главный entry point для syscheck CLI."""
2
+ import sys
3
+ import typer
4
+ from rich.console import Console
5
+
6
+ # Windows console encoding fix (support emoji/unicode)
7
+ if sys.platform == "win32":
8
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
9
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
10
+
11
+ from syscheck import __version__
12
+ from syscheck.plugin import get_plugins
13
+
14
+ app = typer.Typer(
15
+ name="syscheck",
16
+ help="syscheck — CLI-утилита диагностики системы",
17
+ no_args_is_help=True,
18
+ rich_markup_mode="rich",
19
+ )
20
+ console = Console()
21
+
22
+
23
+ def version_callback(value: bool):
24
+ if value:
25
+ console.print(f"syscheck v{__version__}")
26
+ raise typer.Exit()
27
+
28
+
29
+ @app.callback()
30
+ def main_cb(
31
+ version: bool = typer.Option(
32
+ False, "--version", "-v", callback=version_callback, is_eager=True,
33
+ help="Показать версию",
34
+ ),
35
+ ):
36
+ """syscheck — диагностика системы из терминала.
37
+
38
+ Запуск без аргументов открывает интерактивную оболочку (TUI).
39
+ syscheck <команда> — одиночная проверка. --help по командам.
40
+ """
41
+
42
+
43
+ # Импорт и регистрация команд
44
+ from syscheck.commands.cpu import cpu_cmd
45
+ from syscheck.commands.ram import ram_cmd
46
+ from syscheck.commands.disk import disk_cmd
47
+ from syscheck.commands.net import net_cmd
48
+ from syscheck.commands.proc import proc_cmd
49
+ from syscheck.commands.temp import temp_cmd
50
+ from syscheck.commands.sys import sys_cmd
51
+ from syscheck.commands.watch import watch_cmd
52
+
53
+ app.command("cpu")(cpu_cmd)
54
+ app.command("ram")(ram_cmd)
55
+ app.command("disk")(disk_cmd)
56
+ app.command("net")(net_cmd)
57
+ app.command("proc")(proc_cmd)
58
+ app.command("temp")(temp_cmd)
59
+ app.command("sys")(sys_cmd)
60
+ app.command("watch")(watch_cmd)
61
+
62
+
63
+ @app.command("all")
64
+ def all_cmd(
65
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
66
+ ):
67
+ """Показать все метрики системы."""
68
+ from syscheck.commands.cpu import show_cpu
69
+ from syscheck.commands.ram import show_ram
70
+ from syscheck.commands.disk import show_disk
71
+ from syscheck.commands.net import show_net
72
+ from syscheck.commands.temp import show_temp
73
+ from syscheck.commands.sys import show_sys
74
+
75
+ console.rule("syscheck — Полная диагностика")
76
+ console.print()
77
+ show_cpu()
78
+ console.print()
79
+ show_ram()
80
+ console.print()
81
+ show_disk()
82
+ console.print()
83
+ show_net()
84
+ console.print()
85
+ show_temp()
86
+ console.print()
87
+ show_sys()
88
+
89
+
90
+ @app.command("plugins")
91
+ def plugins_cmd(
92
+ list_plugins: bool = typer.Option(False, "--list", "-l", help="Список плагинов"),
93
+ plugin_name: str = typer.Option(None, "--run", "-r", help="Запустить плагин"),
94
+ ):
95
+ """Управление плагинами."""
96
+ plugins = get_plugins()
97
+
98
+ if not plugins:
99
+ console.print("[dim]Плагины не найдены. Добавьте .py файлы в syscheck/plugins/[/]")
100
+ return
101
+
102
+ if list_plugins or (not plugin_name):
103
+ from rich.table import Table
104
+ table = Table(title="Доступные плагины", show_header=True, header_style="bold cyan")
105
+ table.add_column("Имя", style="green")
106
+ table.add_column("Описание")
107
+ table.add_column("Версия", style="dim")
108
+ for name, p in plugins.items():
109
+ table.add_row(name, p.description, p.version)
110
+ console.print(table)
111
+ return
112
+
113
+ if plugin_name:
114
+ plugin = plugins.get(plugin_name)
115
+ if plugin:
116
+ plugin.execute()
117
+ else:
118
+ console.print(f"[red]Плагин '{plugin_name}' не найден.[/]")
119
+ console.print(f"Доступные: {', '.join(plugins.keys())}")
120
+
121
+
122
+ def main():
123
+ """Точка входа: без аргументов → TUI, с аргументами → CLI."""
124
+ enable_shell = False
125
+ args = list(sys.argv[1:])
126
+ if "--enable-shell" in args:
127
+ enable_shell = True
128
+ args.remove("--enable-shell")
129
+ sys.argv = [sys.argv[0]] + args
130
+ if len(args) <= 0:
131
+ from syscheck.tui import run as run_tui
132
+ run_tui(enable_shell=enable_shell)
133
+ else:
134
+ app()
syscheck/cmdlang.py ADDED
@@ -0,0 +1,109 @@
1
+ """Командный язык syscheck.
2
+
3
+ Грамматика разделена на группы:
4
+
5
+ Info cpu, ram, gpu, disk, battery, temp
6
+ Processes process list/find/kill/stop/resume/restart/details/sort
7
+ Network network (speeds/interfaces/connections/ping)
8
+ Display watch, refresh
9
+ System system, all, help, clear, exit
10
+
11
+ Список COMMANDS — единый источник: он же питает Command Palette (Ctrl+P)
12
+ и справку `help`. Обработчики реализованы в TUI как методы `_cmd_*`;
13
+ этот модуль только описывает команды и генерирует текст справки/палитры.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ CATEGORIES = ["Info", "Processes", "Network", "Battery", "Display", "System"]
18
+
19
+ # (команда, категория, описание, нужен ли фоновый поток)
20
+ COMMANDS = [
21
+ ("cpu", "Info", "CPU information", True),
22
+ ("ram", "Info", "Memory information", True),
23
+ ("gpu", "Info", "GPU information", True),
24
+ ("disk", "Info", "disk list | info X: | io", True),
25
+ ("battery", "Battery", "Battery status", True),
26
+ ("temp", "Info", "Temperatures", True),
27
+ ("network", "Network", "network | connections | interfaces | speeds | ping", True),
28
+ ("net", "Network", "alias: net [host]", True),
29
+ ("process", "Processes", "process list | find | kill | stop | resume | restart | details | sort", True),
30
+ ("proc", "Processes", "top processes by cpu|ram|name", True),
31
+ ("system", "System", "System information", True),
32
+ ("all", "System", "everything at once", True),
33
+ ("watch", "Display", "watch cpu | network | process [name] | off", False),
34
+ ("settings", "Display", "show settings & config path", False),
35
+ ("refresh", "Display", "refresh dashboard now", False),
36
+ ("help", "System", "show this help", False),
37
+ ("clear", "System", "clear terminal output", False),
38
+ ("exit", "System", "quit (alias: quit)", False),
39
+ ]
40
+
41
+ # скрытые алиасы (не показываются в справке/палитре)
42
+ _ALIASES = {
43
+ "quit": ("exit", ""),
44
+ }
45
+
46
+
47
+ def command_list() -> list:
48
+ """Полный список команд палитры (без алиасов)."""
49
+ out = []
50
+ for cmd, cat, desc, thread in COMMANDS:
51
+ out.append({"cmd": cmd, "cat": cat, "desc": desc, "thread": thread})
52
+ return out
53
+
54
+
55
+ def lookup(words: list) -> dict | None:
56
+ """Находит запись команды по первым словам (с учётом алиасов)."""
57
+ first = (words[0] if words else "").lower()
58
+ if first in _ALIASES:
59
+ cmd, _ = _ALIASES[first]
60
+ else:
61
+ cmd = first
62
+ for c, cat, desc, thread in COMMANDS:
63
+ if c == cmd:
64
+ return {"cmd": c, "cat": cat, "desc": desc, "thread": thread}
65
+ return None
66
+
67
+
68
+ def expected_args(cmd: str) -> str:
69
+ """Подсказка по аргументам команды (для справки по одной команде)."""
70
+ guide = {
71
+ "disk": "disk list | disk info C: | disk io",
72
+ "network": "network | network connections | network interfaces | network speeds | network ping <host>",
73
+ "net": "net [host]",
74
+ "process": "process list [name] | process find <name|pid> | process sort cpu|ram|name |"
75
+ " process details <name|pid> | process kill|stop|resume|restart <name|pid>",
76
+ "proc": "proc cpu | proc ram | proc name",
77
+ "watch": "watch cpu | watch network | watch process [name] | watch off",
78
+ "cpu": "cpu",
79
+ "ram": "ram",
80
+ "gpu": "gpu",
81
+ "battery": "battery",
82
+ "temp": "temp",
83
+ "system": "system",
84
+ "all": "all",
85
+ "settings": "settings",
86
+ "refresh": "refresh",
87
+ }
88
+ return guide.get(cmd, "")
89
+
90
+
91
+ def help_text() -> str:
92
+ """Полная справка по группам."""
93
+ lines = ["[bold]syscheck commands[/]"]
94
+ for cat in CATEGORIES:
95
+ items = [(c, d, t) for c, cc, d, t in COMMANDS if cc == cat]
96
+ if not items:
97
+ continue
98
+ lines.append(f" [dim]{cat}[/]")
99
+ for c, d, t in items:
100
+ lines.append(f" [cyan]{c:<10}[/] {d}")
101
+ lines.append("")
102
+ lines.append("[dim]ctrl+p command palette ↑↓ history · in processes table:[/]")
103
+ lines.append("[dim] Enter details, K kill, S stop, R restart, / search[/]")
104
+ return "\n".join(lines)
105
+
106
+
107
+ def help_for(cmd: str) -> str:
108
+ args = expected_args(cmd)
109
+ return f"[cyan]{cmd}[/] {args}" if args else f"[dim]nothing more about '{cmd}'[/]"
@@ -0,0 +1,8 @@
1
+ from syscheck.commands.cpu import cpu_cmd
2
+ from syscheck.commands.ram import ram_cmd
3
+ from syscheck.commands.disk import disk_cmd
4
+ from syscheck.commands.net import net_cmd
5
+ from syscheck.commands.proc import proc_cmd
6
+ from syscheck.commands.temp import temp_cmd
7
+ from syscheck.commands.sys import sys_cmd
8
+ from syscheck.commands.watch import watch_cmd
@@ -0,0 +1,45 @@
1
+ """Команда syscheck cpu — диагностика процессора."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import console, make_bar, print_value, print_header, get_color_for_percent
8
+
9
+
10
+ def show_cpu() -> None:
11
+ data = providers.cpu_info()
12
+ per_cpu = data["per_cpu"] or []
13
+
14
+ print_header("cpu")
15
+
16
+ colour = get_color_for_percent(data["percent"])
17
+ console.print(
18
+ f"load [{colour}]{data['percent']}%[/] {make_bar(data['percent'])}"
19
+ )
20
+ print_value("cores", f"{data['count_physical'] or '?'} physical / {data['count_logical']} logical")
21
+ if data["freq_current"]:
22
+ freq = f"{data['freq_current']:.0f} MHz"
23
+ if data["freq_max"]:
24
+ freq += f" (max {data['freq_max']:.0f} MHz)"
25
+ print_value("freq", freq)
26
+ if per_cpu:
27
+ parts = " ".join(
28
+ f"[{get_color_for_percent(p)}]{i}: {p:.0f}[/]" for i, p in enumerate(per_cpu)
29
+ )
30
+ console.print(f"per-core {parts}")
31
+
32
+ if data["percent"] >= 90:
33
+ console.print(" [red][!] CPU сильно нагружен[/]")
34
+ elif data["percent"] >= 75:
35
+ console.print(" [yellow][!] CPU заметно нагружен[/]")
36
+
37
+
38
+ def cpu_cmd(
39
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
40
+ ):
41
+ """Диагностика процессора (нагрузка, ядра, частота)."""
42
+ if output_json:
43
+ console.print_json(json.dumps(providers.cpu_info(), indent=2))
44
+ else:
45
+ show_cpu()
@@ -0,0 +1,61 @@
1
+ """Команда syscheck disk — диагностика дисков."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import (
8
+ console, make_bar, print_value, print_header, get_color_for_percent, bytes_to_human,
9
+ print_error, print_warning,
10
+ )
11
+
12
+
13
+ def show_disk() -> None:
14
+ data = providers.disk_info()
15
+
16
+ print_header("disks")
17
+
18
+ if not data["disks"]:
19
+ console.print("[dim]диски не найдены[/]")
20
+ return
21
+
22
+ for d in data["disks"]:
23
+ colour = get_color_for_percent(d["percent"])
24
+ console.print(
25
+ f"{d['device'].ljust(8)} [{colour}]{d['percent']:4.1f}%[/] {make_bar(d['percent'])} "
26
+ f"{bytes_to_human(d['used'])} / {bytes_to_human(d['total'])} "
27
+ f"(free {bytes_to_human(d['free'])})"
28
+ )
29
+
30
+ io = data["io"]
31
+ if io:
32
+ console.print()
33
+ print_header("io")
34
+ print_value("read", bytes_to_human(io.read_bytes))
35
+ print_value("write", bytes_to_human(io.write_bytes))
36
+
37
+ for d in data["disks"]:
38
+ if d["percent"] > 95:
39
+ print_error(f"{d['device']}: критически мало места!")
40
+ elif d["percent"] > 85:
41
+ print_warning(f"{d['device']}: мало свободного места")
42
+
43
+
44
+ def disk_cmd(
45
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
46
+ ):
47
+ """Диагностика дисков (место, состояние, I/O)."""
48
+ if output_json:
49
+ out = []
50
+ for d in providers.disk_info()["disks"]:
51
+ out.append({
52
+ "device": d["device"],
53
+ "mountpoint": d["mountpoint"],
54
+ "total": d["total"],
55
+ "used": d["used"],
56
+ "free": d["free"],
57
+ "percent": d["percent"],
58
+ })
59
+ console.print_json(json.dumps(out, indent=2))
60
+ else:
61
+ show_disk()
@@ -0,0 +1,59 @@
1
+ """Команда syscheck net — диагностика сети."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import (
8
+ console, print_value, print_header, bytes_to_human, print_ok, print_warning, print_error,
9
+ )
10
+
11
+
12
+ def show_net(ping_host: str = "8.8.8.8") -> None:
13
+ data = providers.net_info()
14
+
15
+ print_header("network")
16
+
17
+ for i in data["interfaces"]:
18
+ status = "[green]up[/]" if i["up"] else "[dim]down[/]"
19
+ ip = ", ".join(i["ipv4"]) if i["ipv4"] else "[dim]N/A[/]"
20
+ speed = f"{i['speed']} Mbps" if i["speed"] else "-"
21
+ console.print(f" {status} {i['name'].ljust(28)} {ip.ljust(18)} {speed}")
22
+ if i["mac"]:
23
+ console.print(f"{'':2} mac: {', '.join(i['mac'])}")
24
+
25
+ io = data["io"]
26
+ if io:
27
+ console.print()
28
+ print_value("sent", bytes_to_human(io["bytes_sent"]))
29
+ print_value("recv", bytes_to_human(io["bytes_recv"]))
30
+ print_value("packets", f"{io['packets_sent']} / {io['packets_recv']}")
31
+
32
+ console.print()
33
+ print_header(f"ping {ping_host}")
34
+ result = providers.ping(ping_host)
35
+ if result["success"]:
36
+ if result["avg_ms"] is not None:
37
+ print_ok(f"{result['host']}: {result['avg_ms']:.1f} ms "
38
+ f"(min {result['min_ms']:.1f}, max {result['max_ms']:.1f})")
39
+ else:
40
+ print_ok(f"{result['host']}: пинг прошёл (время не определено)")
41
+ if result["lost"] > 0:
42
+ print_warning(f"потеряно {result['lost']}/{result['total']} пакетов")
43
+ if result["avg_ms"] and result["avg_ms"] > 100:
44
+ print_warning("высокий пинг")
45
+ else:
46
+ print_error(f"{ping_host}: недоступен")
47
+
48
+
49
+ def net_cmd(
50
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
51
+ ping_host: str = typer.Option("8.8.8.8", "--ping", "-p", help="Хост для пинга"),
52
+ ):
53
+ """Диагностика сети (интерфейсы, трафик, пинг)."""
54
+ if output_json:
55
+ data = providers.net_info()
56
+ data["ping"] = providers.ping(ping_host)
57
+ console.print_json(json.dumps(data, indent=2))
58
+ else:
59
+ show_net(ping_host)
@@ -0,0 +1,35 @@
1
+ """Команда syscheck proc — топ процессов."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import console, print_header, get_color_for_percent
8
+
9
+
10
+ def show_proc(sort_by: str = "cpu", limit: int = 15) -> None:
11
+ data = providers.processes(sort_by=sort_by, limit=limit)
12
+
13
+ print_header(f"processes by {sort_by}")
14
+
15
+ for p in data:
16
+ cpu = p.get("cpu_percent") or 0
17
+ ram = p.get("memory_percent") or 0
18
+ status = p.get("status", "?")
19
+ console.print(
20
+ f" {p['pid']:<8} {(p['name'] or '?')[:28]:<28} "
21
+ f"cpu[{get_color_for_percent(cpu)}]{cpu:5.1f}[/] "
22
+ f"ram[{get_color_for_percent(ram)}]{ram:5.1f}[/] {status}"
23
+ )
24
+
25
+
26
+ def proc_cmd(
27
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
28
+ sort_by: str = typer.Option("cpu", "--sort", "-s", help="Сортировка: cpu, ram, name"),
29
+ limit: int = typer.Option(15, "--limit", "-n", help="Количество процессов"),
30
+ ):
31
+ """Топ процессов по CPU/RAM."""
32
+ if output_json:
33
+ console.print_json(json.dumps(providers.processes(sort_by=sort_by, limit=limit), indent=2))
34
+ else:
35
+ show_proc(sort_by, limit)
@@ -0,0 +1,49 @@
1
+ """Команда syscheck ram — диагностика оперативной памяти."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import (
8
+ console, make_bar, print_value, print_header, get_color_for_percent, bytes_to_human,
9
+ )
10
+
11
+
12
+ def show_ram() -> None:
13
+ data = providers.ram_info()
14
+
15
+ print_header("ram")
16
+
17
+ colour = get_color_for_percent(data["percent"])
18
+ console.print(
19
+ f"usage [{colour}]{data['percent']}%[/] {make_bar(data['percent'])}"
20
+ )
21
+ print_value("used", f"{bytes_to_human(data['used'])} / {bytes_to_human(data['total'])}")
22
+ print_value("free", bytes_to_human(data["available"]))
23
+ if data["swap_total"] > 0:
24
+ print_value("swap", f"{data['swap_percent']}% ({bytes_to_human(data['swap_total'])})")
25
+
26
+ if data["top_procs"]:
27
+ console.print()
28
+ print_header("top by ram")
29
+ for p in data["top_procs"]:
30
+ pct = p["memory_percent"] or 0
31
+ pc = get_color_for_percent(pct)
32
+ console.print(
33
+ f" {p['pid']:<8} {(p['name'] or '?')[:28]:<28} [{pc}]{pct:5.1f}%[/]"
34
+ )
35
+
36
+ if data["percent"] >= 90:
37
+ console.print(" [red][!] память почти исчерпана[/]")
38
+ elif data["percent"] >= 85:
39
+ console.print(" [yellow][!] много памяти используется[/]")
40
+
41
+
42
+ def ram_cmd(
43
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
44
+ ):
45
+ """Диагностика оперативной памяти."""
46
+ if output_json:
47
+ console.print_json(json.dumps(providers.ram_info(), indent=2))
48
+ else:
49
+ show_ram()
@@ -0,0 +1,43 @@
1
+ """Команда syscheck sys — информация о системе."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import (
8
+ console, print_header, print_value, seconds_to_human, get_color_for_percent, bytes_to_human,
9
+ )
10
+
11
+
12
+ def show_sys() -> None:
13
+ data = providers.system_info()
14
+
15
+ print_header("system")
16
+
17
+ print_value("os", f"{data['os']} ({data['machine']})")
18
+ print_value("hostname", data["node"])
19
+ print_value("processor", data["processor"])
20
+ print_value("uptime", f"{seconds_to_human(data['uptime_seconds'])} (since {data['boot_datetime']})")
21
+ print_value("python", data["python"])
22
+ if data["users"]:
23
+ print_value("users", ", ".join(data["users"]))
24
+
25
+ console.print()
26
+ quick = providers.system_quick()
27
+ print_header("summary")
28
+ console.print(f"cpu [{get_color_for_percent(quick['cpu_percent'])}]{quick['cpu_percent']}%[/]")
29
+ console.print(f"ram [{get_color_for_percent(quick['ram_percent'])}]{quick['ram_percent']}%[/] (free {bytes_to_human(quick['ram_available'])})")
30
+ if quick.get("main_percent") is not None:
31
+ console.print(f"disk [{get_color_for_percent(quick['main_percent'])}]{quick['main_percent']}%[/] (free {bytes_to_human(quick['main_free'])})")
32
+ if quick.get("net_sent") is not None:
33
+ console.print(f"network sent {bytes_to_human(quick['net_sent'])} recv {bytes_to_human(quick['net_recv'])}")
34
+
35
+
36
+ def sys_cmd(
37
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
38
+ ):
39
+ """Информация о системе (ОС, uptime, hostname)."""
40
+ if output_json:
41
+ console.print_json(json.dumps(providers.system_info(), indent=2))
42
+ else:
43
+ show_sys()
@@ -0,0 +1,50 @@
1
+ """Команда syscheck temp — температура сенсоров."""
2
+ import json
3
+
4
+ import typer
5
+
6
+ from syscheck import providers
7
+ from syscheck.utils import console, print_header, print_value, print_warning, print_error
8
+
9
+
10
+ def show_temp() -> None:
11
+ data = providers.temperatures()
12
+
13
+ print_header("temperatures")
14
+
15
+ if not data["available"]:
16
+ console.print("[dim]температуры не поддерживаются на этой системе через psutil[/]")
17
+ console.print("[dim](hwinfo / coretemp на windows)[/]")
18
+ return
19
+
20
+ printed = False
21
+ for name, entries in data["temps"].items():
22
+ for e in entries:
23
+ current = e.current
24
+ high = e.high
25
+ critical = e.critical
26
+ printed = True
27
+ print_value(name, f"{current:.0f}°C")
28
+ if high and current >= high:
29
+ print_warning(f"{name}: выше нормы ({current}°C >= {high}°C)")
30
+ if critical and current >= critical:
31
+ print_error(f"{name}: критическая! ({current}°C >= {critical}°C)")
32
+ if not printed:
33
+ console.print("[dim]датчиков температуры нет[/]")
34
+
35
+
36
+ def temp_cmd(
37
+ output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
38
+ ):
39
+ """Температура сенсоров (CPU, GPU, SSD)."""
40
+ if output_json:
41
+ data = providers.temperatures()
42
+ out = {}
43
+ for name, entries in data["temps"].items():
44
+ out[name] = [
45
+ {"current": e.current, "high": e.high, "critical": e.critical}
46
+ for e in entries
47
+ ]
48
+ console.print_json(json.dumps(out, indent=2))
49
+ else:
50
+ show_temp()