Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,40 @@ line-length = 100
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "F", "I", "W"]
select = [
# Tier 1 — high value, very safe
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"W", # pycodestyle warnings
"UP", # pyupgrade — modern Python syntax
"B", # bugbear — common bug patterns
"SIM", # simplify — reduce complexity
"C4", # flake8-comprehensions
"RUF", # Ruff-specific rules
# Tier 2 — strong value, minor tuning
"PIE", # misc cleanup
"RET", # return simplification
"PERF", # performance anti-patterns
"PT", # pytest style
# "C90", # mccabe complexity — enable after refactoring complex functions
"FURB", # modernization
"FLY", # f-string conversion
]
ignore = [
"E501", # line length (formatter handles this)
"SIM108", # ternary operator (opinionated)
"SIM117", # nested with statements (clearer in test mocking patterns)
"PT018", # composite assertions (splitting weakens error messages)
"PT019", # fixture without value (usefixtures less readable)
"PT017", # assert in except (valid test pattern)
]

[tool.ruff.lint.mccabe]
max-complexity = 10

[tool.ruff.lint.per-file-ignores]
"src/mlx_stack/_version.py" = ["RUF022"] # auto-generated by hatch-vcs

[tool.pyright]
pythonVersion = "3.13"
Expand Down
19 changes: 7 additions & 12 deletions src/mlx_stack/cli/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,16 @@ def _display_results(result: BenchmarkResult_, out: Console, save: bool = False)
out.print(Text("Tool Calling", style="bold cyan"))
tc = result.tool_call_result
if tc.success:
out.print(
f" [green]✓ Valid tool call[/green] — "
f"round-trip: {tc.round_trip_time:.2f}s"
)
out.print(f" [green]✓ Valid tool call[/green] — round-trip: {tc.round_trip_time:.2f}s")
else:
out.print(
f" [red]✗ Tool call failed[/red] — {tc.error}"
)
out.print(f" [red]✗ Tool call failed[/red] — {tc.error}")
out.print()
elif not result.tool_call_result:
# Check if model supports tool calling from entry
if not result.catalog_data_available:
pass # Skip silently if no catalog data
else:
out.print(
"[dim]Tool calling: skipped (model does not support tool calling)[/dim]"
)
out.print("[dim]Tool calling: skipped (model does not support tool calling)[/dim]")
out.print()

# Iteration details
Expand Down Expand Up @@ -212,6 +205,8 @@ def _display_results(result: BenchmarkResult_, out: Console, save: bool = False)

# Save confirmation
if save:
out.print("[green]✓ Results saved.[/green] "
"These will be used by 'recommend' and 'init' for scoring.")
out.print(
"[green]✓ Results saved.[/green] "
"These will be used by 'recommend' and 'init' for scoring."
)
out.print()
4 changes: 1 addition & 3 deletions src/mlx_stack/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ def config_reset(yes: bool, force: bool) -> None:
# Check if stdin is a TTY for interactive confirmation
try:
if click.get_text_stream("stdin").isatty():
confirmed = click.confirm(
"Reset all configuration to defaults?", default=False
)
confirmed = click.confirm("Reset all configuration to defaults?", default=False)
else:
console.print(
"[bold red]Error:[/bold red] Reset requires --yes or --force flag "
Expand Down
10 changes: 3 additions & 7 deletions src/mlx_stack/cli/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,10 @@ def _display_summary(result: dict) -> None:
budget_gb = result["memory_budget_gb"]
total_memory_gb = result.get("total_memory_gb", 0.0)
out.print(
f"[dim]Hardware: {profile.chip} ({profile.memory_gb} GB) · "
f"Budget: {budget_gb:.1f} GB[/dim]"
f"[dim]Hardware: {profile.chip} ({profile.memory_gb} GB) · Budget: {budget_gb:.1f} GB[/dim]"
)
if total_memory_gb > 0:
out.print(
f"[dim]Total estimated memory: {total_memory_gb:.1f} GB[/dim]"
)
out.print(f"[dim]Total estimated memory: {total_memory_gb:.1f} GB[/dim]")

# Warnings (e.g., memory budget exceeded with --add)
init_warnings = result.get("warnings", [])
Expand All @@ -83,8 +80,7 @@ def _display_summary(result: dict) -> None:
if stack.get("cloud_fallback"):
out.print()
out.print(
"[bold green]☁ Cloud Fallback[/bold green] "
"Premium tier via OpenRouter configured"
"[bold green]☁ Cloud Fallback[/bold green] Premium tier via OpenRouter configured"
)

# Missing models warning
Expand Down
4 changes: 1 addition & 3 deletions src/mlx_stack/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ def _display_status(status: AgentStatus) -> None:
if not status.installed:
out.print(Text("Status: not installed", style="dim"))
elif status.running and status.pid is not None:
out.print(
Text(f"Status: installed and running (PID {status.pid})", style="green")
)
out.print(Text(f"Status: installed and running (PID {status.pid})", style="green"))
else:
out.print(Text("Status: installed but not running", style="yellow"))

Expand Down
8 changes: 3 additions & 5 deletions src/mlx_stack/cli/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import contextlib
import sys

import click
Expand Down Expand Up @@ -78,7 +79,7 @@ def _display_rotation_results(results: list) -> None:
out.print(f"[green]✓[/green] {result.service}: rotated")
any_rotated = True
else:
out.print(f"[dim][/dim] {result.service}: no rotation needed")
out.print(f"[dim]-[/dim] {result.service}: no rotation needed")

if not results:
out.print(Text("No log files found to rotate.", style="yellow"))
Expand Down Expand Up @@ -213,11 +214,8 @@ def logs(
# Handle --follow mode
if follow:
num = tail_lines if tail_lines is not None else DEFAULT_TAIL_LINES
try:
with contextlib.suppress(KeyboardInterrupt):
follow_log(log_path, num_lines=num, output_callback=click.echo)
except KeyboardInterrupt:
# Belt-and-suspenders: ensure clean exit
pass
return

# Default: show tail of log
Expand Down
8 changes: 5 additions & 3 deletions src/mlx_stack/cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _display_local_models() -> None:
indicator_style = "bold green" if model.is_active else ""

# Display name: prefer catalog name, fall back to directory name
display_name = model.catalog_name if model.catalog_name else model.name
display_name = model.catalog_name or model.name

# Size
size_str = format_size(model.disk_size_bytes)
Expand Down Expand Up @@ -217,7 +217,7 @@ def _display_catalog(
local_style = "bold green" if cm.is_local else ""

# Parameters
params_str = f"{cm.params_b:.1f}B" if cm.params_b >= 1.0 else f"{cm.params_b:.1f}B"
params_str = f"{cm.params_b:.1f}B"

# Quantizations
quants_str = ", ".join(cm.quants)
Expand Down Expand Up @@ -270,7 +270,9 @@ def _display_catalog(
@click.option("--family", default=None, help="Filter catalog by model family (e.g., 'qwen3.5').")
@click.option("--tag", default=None, help="Filter catalog by tag (e.g., 'agent-ready').")
@click.option(
"--tool-calling", "tool_calling", is_flag=True,
"--tool-calling",
"tool_calling",
is_flag=True,
help="Filter catalog to tool-calling-capable models only.",
)
def models(
Expand Down
8 changes: 2 additions & 6 deletions src/mlx_stack/cli/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ def profile() -> None:

if hw.is_estimate:
out.print()
out.print(
"[yellow]⚠ Bandwidth is estimated for unknown chip.[/yellow]"
)
out.print(
" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements."
)
out.print("[yellow]⚠ Bandwidth is estimated for unknown chip.[/yellow]")
out.print(" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements.")

out.print()
from mlx_stack.core.paths import get_profile_path
Expand Down
15 changes: 4 additions & 11 deletions src/mlx_stack/cli/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,13 @@ def _run_post_download_bench(model_id: str, quant: str, out: Console) -> None:
from mlx_stack.core.benchmark import BenchmarkError, run_benchmark

result = run_benchmark(target=model_id, save=True)
out.print(
f" Prompt TPS: {result.prompt_tps_mean:.1f} ± {result.prompt_tps_std:.1f} tok/s"
)
out.print(
f" Gen TPS: {result.gen_tps_mean:.1f} ± {result.gen_tps_std:.1f} tok/s"
)
out.print(f" Prompt TPS: {result.prompt_tps_mean:.1f} ± {result.prompt_tps_std:.1f} tok/s")
out.print(f" Gen TPS: {result.gen_tps_mean:.1f} ± {result.gen_tps_std:.1f} tok/s")
out.print()
out.print(
"[dim]Results saved for use by 'recommend' and 'init' scoring.[/dim]"
)
out.print("[dim]Results saved for use by 'recommend' and 'init' scoring.[/dim]")
except BenchmarkError as exc:
out.print(
f"[yellow]Benchmark failed: {exc}[/yellow]\n"
f"Run 'mlx-stack bench {model_id}' to retry."
f"[yellow]Benchmark failed: {exc}[/yellow]\nRun 'mlx-stack bench {model_id}' to retry."
)
except Exception as exc:
out.print(
Expand Down
29 changes: 8 additions & 21 deletions src/mlx_stack/cli/recommend.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ def parse_budget(raw: str) -> float:

value = float(match.group(1))
if value <= 0:
msg = (
f"Invalid budget '{raw}'. Budget must be a positive value."
)
msg = f"Invalid budget '{raw}'. Budget must be a positive value."
raise click.BadParameter(msg, param_hint="'--budget'")

return value
Expand All @@ -96,8 +94,7 @@ def _resolve_profile() -> HardwareProfile:
# Auto-detect (in-memory only — recommend is display-only, no file writes)
console.print("[dim]No saved profile found — detecting hardware...[/dim]")
try:
profile = detect_hardware()
return profile
return detect_hardware()
except HardwareError as exc:
console.print(f"[bold red]Error:[/bold red] {exc}")
raise SystemExit(1) from None
Expand Down Expand Up @@ -206,12 +203,8 @@ def _display_tier_table(result: RecommendationResult) -> None:
has_estimates = any(t.model.is_estimated for t in result.tiers)
if has_estimates:
out.print()
out.print(
"[yellow]⚠ Some performance values are estimated from bandwidth ratio.[/yellow]"
)
out.print(
" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements."
)
out.print("[yellow]⚠ Some performance values are estimated from bandwidth ratio.[/yellow]")
out.print(" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements.")

out.print()
out.print("[dim]This is a recommendation only — no files were written.[/dim]")
Expand Down Expand Up @@ -268,20 +261,15 @@ def _display_all_models(result: RecommendationResult) -> None:
if openrouter_key:
out.print()
out.print(
"[bold green]☁ Cloud Fallback[/bold green] "
"Premium tier via OpenRouter also available."
"[bold green]☁ Cloud Fallback[/bold green] Premium tier via OpenRouter also available."
)

# Estimated warning
has_estimates = any(m.is_estimated for m in result.all_scored)
if has_estimates:
out.print()
out.print(
"[yellow]⚠ Some performance values are estimated from bandwidth ratio.[/yellow]"
)
out.print(
" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements."
)
out.print("[yellow]⚠ Some performance values are estimated from bandwidth ratio.[/yellow]")
out.print(" Run [bold]mlx-stack bench --save[/bold] to calibrate with real measurements.")

out.print()
out.print("[dim]This is a recommendation only — no files were written.[/dim]")
Expand Down Expand Up @@ -329,8 +317,7 @@ def recommend(budget: str | None, intent: str | None, show_all: bool) -> None:
elif intent not in VALID_INTENTS:
valid = ", ".join(sorted(VALID_INTENTS))
console.print(
f"[bold red]Error:[/bold red] Invalid intent '{intent}'. "
f"Valid intents: {valid}"
f"[bold red]Error:[/bold red] Invalid intent '{intent}'. Valid intents: {valid}"
)
raise SystemExit(1)

Expand Down
Loading
Loading