mlx-stack 0.1.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.
Files changed (61) hide show
  1. mlx_stack/__init__.py +5 -0
  2. mlx_stack/_version.py +24 -0
  3. mlx_stack/cli/__init__.py +5 -0
  4. mlx_stack/cli/bench.py +221 -0
  5. mlx_stack/cli/config.py +166 -0
  6. mlx_stack/cli/down.py +109 -0
  7. mlx_stack/cli/init.py +180 -0
  8. mlx_stack/cli/install.py +165 -0
  9. mlx_stack/cli/logs.py +234 -0
  10. mlx_stack/cli/main.py +187 -0
  11. mlx_stack/cli/models.py +304 -0
  12. mlx_stack/cli/profile.py +65 -0
  13. mlx_stack/cli/pull.py +134 -0
  14. mlx_stack/cli/recommend.py +397 -0
  15. mlx_stack/cli/status.py +111 -0
  16. mlx_stack/cli/up.py +163 -0
  17. mlx_stack/cli/watch.py +252 -0
  18. mlx_stack/core/__init__.py +1 -0
  19. mlx_stack/core/benchmark.py +1182 -0
  20. mlx_stack/core/catalog.py +560 -0
  21. mlx_stack/core/config.py +471 -0
  22. mlx_stack/core/deps.py +323 -0
  23. mlx_stack/core/hardware.py +304 -0
  24. mlx_stack/core/launchd.py +531 -0
  25. mlx_stack/core/litellm_gen.py +188 -0
  26. mlx_stack/core/log_rotation.py +231 -0
  27. mlx_stack/core/log_viewer.py +386 -0
  28. mlx_stack/core/models.py +639 -0
  29. mlx_stack/core/paths.py +79 -0
  30. mlx_stack/core/process.py +887 -0
  31. mlx_stack/core/pull.py +815 -0
  32. mlx_stack/core/scoring.py +611 -0
  33. mlx_stack/core/stack_down.py +317 -0
  34. mlx_stack/core/stack_init.py +524 -0
  35. mlx_stack/core/stack_status.py +229 -0
  36. mlx_stack/core/stack_up.py +856 -0
  37. mlx_stack/core/watchdog.py +744 -0
  38. mlx_stack/data/__init__.py +1 -0
  39. mlx_stack/data/catalog/__init__.py +1 -0
  40. mlx_stack/data/catalog/deepseek-r1-32b.yaml +46 -0
  41. mlx_stack/data/catalog/deepseek-r1-8b.yaml +45 -0
  42. mlx_stack/data/catalog/gemma3-12b.yaml +45 -0
  43. mlx_stack/data/catalog/gemma3-27b.yaml +45 -0
  44. mlx_stack/data/catalog/gemma3-4b.yaml +45 -0
  45. mlx_stack/data/catalog/llama3.3-8b.yaml +44 -0
  46. mlx_stack/data/catalog/nemotron-49b.yaml +41 -0
  47. mlx_stack/data/catalog/nemotron-8b.yaml +44 -0
  48. mlx_stack/data/catalog/qwen3-8b.yaml +45 -0
  49. mlx_stack/data/catalog/qwen3.5-0.8b.yaml +45 -0
  50. mlx_stack/data/catalog/qwen3.5-14b.yaml +46 -0
  51. mlx_stack/data/catalog/qwen3.5-32b.yaml +45 -0
  52. mlx_stack/data/catalog/qwen3.5-3b.yaml +44 -0
  53. mlx_stack/data/catalog/qwen3.5-72b.yaml +42 -0
  54. mlx_stack/data/catalog/qwen3.5-8b.yaml +45 -0
  55. mlx_stack/py.typed +1 -0
  56. mlx_stack/utils/__init__.py +1 -0
  57. mlx_stack-0.1.0.dist-info/METADATA +397 -0
  58. mlx_stack-0.1.0.dist-info/RECORD +61 -0
  59. mlx_stack-0.1.0.dist-info/WHEEL +4 -0
  60. mlx_stack-0.1.0.dist-info/entry_points.txt +2 -0
  61. mlx_stack-0.1.0.dist-info/licenses/LICENSE +21 -0
mlx_stack/cli/watch.py ADDED
@@ -0,0 +1,252 @@
1
+ """CLI command for the watchdog health monitor — `mlx-stack watch`.
2
+
3
+ Long-running health monitor that polls service status, auto-restarts
4
+ crashed services, detects flapping, and triggers log rotation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from datetime import datetime, timezone
11
+
12
+ import click
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+ from rich.text import Text
16
+
17
+ from mlx_stack.core.watchdog import (
18
+ DEFAULT_INTERVAL,
19
+ DEFAULT_MAX_RESTARTS,
20
+ DEFAULT_RESTART_DELAY,
21
+ PollResult,
22
+ RestartRecord,
23
+ WatchdogError,
24
+ WatchdogState,
25
+ run_watchdog,
26
+ )
27
+
28
+ console = Console(stderr=True)
29
+
30
+ # --------------------------------------------------------------------------- #
31
+ # Status color map
32
+ # --------------------------------------------------------------------------- #
33
+
34
+ _STATUS_STYLES: dict[str, str] = {
35
+ "healthy": "green",
36
+ "degraded": "yellow",
37
+ "down": "red",
38
+ "crashed": "bold red",
39
+ "stopped": "dim",
40
+ }
41
+
42
+
43
+ # --------------------------------------------------------------------------- #
44
+ # Display helpers
45
+ # --------------------------------------------------------------------------- #
46
+
47
+
48
+ def _format_status_table(result: PollResult, state: WatchdogState) -> None:
49
+ """Print a Rich-formatted status table for a poll cycle.
50
+
51
+ Args:
52
+ result: The poll cycle result.
53
+ state: Current watchdog state.
54
+ """
55
+ out = Console()
56
+ now = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
57
+
58
+ out.print()
59
+ out.print(Text(f"[Cycle {state.cycle_count}] {now}", style="bold cyan"))
60
+
61
+ table = Table(
62
+ show_header=True,
63
+ header_style="bold",
64
+ )
65
+ table.add_column("Service", style="bold", min_width=12)
66
+ table.add_column("Status", min_width=10)
67
+ table.add_column("Port", justify="right", min_width=6)
68
+
69
+ for svc in result.statuses:
70
+ style = _STATUS_STYLES.get(svc.status, "")
71
+ status_text = Text(svc.status, style=style)
72
+
73
+ # Annotate flapping services
74
+ if svc.tier in result.flapping_services:
75
+ status_text.append(" [FLAPPING]", style="bold magenta")
76
+
77
+ table.add_row(
78
+ svc.tier,
79
+ status_text,
80
+ str(svc.port),
81
+ )
82
+
83
+ out.print(table)
84
+
85
+ # Show rotation info
86
+ if result.rotations_performed > 0:
87
+ out.print(
88
+ Text(
89
+ f" Log rotation: {result.rotations_performed} file(s) rotated",
90
+ style="dim",
91
+ )
92
+ )
93
+
94
+ # Show restart summary
95
+ if result.restarts_attempted > 0:
96
+ out.print(
97
+ Text(
98
+ f" Restarts: {result.restarts_succeeded}/{result.restarts_attempted} succeeded",
99
+ style=(
100
+ "yellow"
101
+ if result.restarts_succeeded < result.restarts_attempted
102
+ else "green"
103
+ ),
104
+ )
105
+ )
106
+
107
+
108
+ def _format_restart_event(record: RestartRecord) -> None:
109
+ """Print a Rich-formatted restart event.
110
+
111
+ Args:
112
+ record: The restart record.
113
+ """
114
+ out = Console()
115
+ ts = datetime.fromtimestamp(record.timestamp, tz=timezone.utc).strftime(
116
+ "%Y-%m-%d %H:%M:%S UTC"
117
+ )
118
+ status = "✓" if record.success else "✗"
119
+ style = "green" if record.success else "red"
120
+
121
+ out.print(
122
+ Text(
123
+ f" [{status}] {ts} Restart {record.service} "
124
+ f"(attempt #{record.attempt}): {record.reason}",
125
+ style=style,
126
+ )
127
+ )
128
+
129
+
130
+ # --------------------------------------------------------------------------- #
131
+ # Input validation
132
+ # --------------------------------------------------------------------------- #
133
+
134
+
135
+ def _validate_positive_int(
136
+ ctx: click.Context,
137
+ param: click.Parameter,
138
+ value: int,
139
+ ) -> int:
140
+ """Validate that a value is a positive integer.
141
+
142
+ Args:
143
+ ctx: Click context.
144
+ param: Click parameter.
145
+ value: The value to validate.
146
+
147
+ Returns:
148
+ The validated value.
149
+
150
+ Raises:
151
+ click.BadParameter: If value is not positive.
152
+ """
153
+ if value < 1:
154
+ raise click.BadParameter(
155
+ f"Must be a positive integer (got {value})."
156
+ )
157
+ return value
158
+
159
+
160
+ # --------------------------------------------------------------------------- #
161
+ # CLI command
162
+ # --------------------------------------------------------------------------- #
163
+
164
+
165
+ @click.command()
166
+ @click.option(
167
+ "--interval",
168
+ type=int,
169
+ default=DEFAULT_INTERVAL,
170
+ show_default=True,
171
+ callback=_validate_positive_int,
172
+ help="Seconds between health polls.",
173
+ )
174
+ @click.option(
175
+ "--max-restarts",
176
+ type=int,
177
+ default=DEFAULT_MAX_RESTARTS,
178
+ show_default=True,
179
+ callback=_validate_positive_int,
180
+ help="Maximum restarts before marking service as flapping.",
181
+ )
182
+ @click.option(
183
+ "--restart-delay",
184
+ type=int,
185
+ default=DEFAULT_RESTART_DELAY,
186
+ show_default=True,
187
+ callback=_validate_positive_int,
188
+ help="Base delay (seconds) before restart with exponential backoff.",
189
+ )
190
+ @click.option(
191
+ "--daemon",
192
+ is_flag=True,
193
+ default=False,
194
+ help="Run in background as a daemon.",
195
+ )
196
+ def watch(
197
+ interval: int,
198
+ max_restarts: int,
199
+ restart_delay: int,
200
+ daemon: bool,
201
+ ) -> None:
202
+ """Monitor stack health and auto-restart crashed services.
203
+
204
+ Polls service health at regular intervals (default 30s) and
205
+ auto-restarts any crashed services (PID file exists, process dead).
206
+ Services that are intentionally stopped (no PID file) are NOT
207
+ restarted.
208
+
209
+ \b
210
+ Features:
211
+ • Rich-formatted status display each poll cycle
212
+ • Auto-restart of crashed services
213
+ • Flap detection (stops restarting after threshold)
214
+ • Exponential backoff on restart delay
215
+ • Log rotation each poll cycle
216
+ • Daemon mode with PID file
217
+
218
+ \b
219
+ Examples:
220
+ mlx-stack watch Start monitoring (foreground)
221
+ mlx-stack watch --daemon Start as background daemon
222
+ mlx-stack watch --interval 60 Poll every 60 seconds
223
+ mlx-stack watch --max-restarts 3 Flap after 3 restarts
224
+ """
225
+ try:
226
+ if daemon:
227
+ console.print(
228
+ Text("Starting watchdog in daemon mode...", style="bold cyan")
229
+ )
230
+
231
+ run_watchdog(
232
+ interval=interval,
233
+ max_restarts=max_restarts,
234
+ restart_delay=restart_delay,
235
+ daemon=daemon,
236
+ status_callback=_format_status_table,
237
+ restart_callback=_format_restart_event,
238
+ )
239
+
240
+ # Clean exit
241
+ if not daemon:
242
+ out = Console()
243
+ out.print()
244
+ out.print(Text("Watchdog stopped.", style="bold cyan"))
245
+
246
+ except WatchdogError as exc:
247
+ console.print(f"[red]Error:[/red] {exc}")
248
+ sys.exit(1)
249
+ except KeyboardInterrupt:
250
+ out = Console()
251
+ out.print()
252
+ out.print(Text("Watchdog stopped.", style="bold cyan"))
@@ -0,0 +1 @@
1
+ """Core business logic modules for mlx-stack."""