mlx-stack 0.3.2__tar.gz → 0.3.3__tar.gz
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.
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/workflows/ci.yml +4 -4
- mlx_stack-0.3.3/.github/workflows/release-please.yml +100 -0
- mlx_stack-0.3.3/.release-please-manifest.json +3 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CHANGELOG.md +7 -0
- mlx_stack-0.3.3/Makefile +20 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/PKG-INFO +1 -1
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/_version.py +2 -2
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/status.py +12 -7
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_status.py +14 -3
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/watchdog.py +3 -2
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_pull.py +28 -7
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_status.py +34 -11
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_launchd.py +7 -1
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_ops_cross_area.py +27 -27
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_watchdog.py +16 -16
- mlx_stack-0.3.2/.github/workflows/release-please.yml +0 -19
- mlx_stack-0.3.2/.release-please-manifest.json +0 -3
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/init.sh +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/architecture.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/environment.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/user-testing.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/services.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/settings.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/skills/cli-feature/SKILL.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/configuration-management.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/dependency-management.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-catalog-errors-and-families.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-deps-binary-and-ansi.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-scaffolding-data-home.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/hardware-detection.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/model-catalog.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/project-scaffolding.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-config-basic.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-config-deps.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-profile-catalog.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-setup-profile-core.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/down-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-preflight-and-readonly.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-process-robustness.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-typecheck.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/process-management.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/status-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/up-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g1-deps-up-basics.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g2-up-startup.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g3-up-resilience.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g4-down.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g5-status.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g6-cross.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r2-g1-fixes.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r2-g2-cross-blockers.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/reviews/fix-cross-area-test-rigor.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/reviews/misc-cross-area-validation.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/flows/r1-g1-cross-flows.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/flows/r2-g4-cross-port5050.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-lint-errors.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-scrutiny-issues.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-typecheck-errors.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/launchd-integration.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/log-rotation.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/logs-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/ops-cross-area-validation.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/watchdog-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g1-log.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g2-logs-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g3-watch.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g4-launchd.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g5-cross-ops.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/community-docs.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/developing-guide.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/fix-public-ready-scrutiny.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/github-actions-ci.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/readme-rewrite.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-init-and-models-issues.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-recommendation-scoring-issues.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-scoring-lint.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/init-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/models-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/recommend-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/scoring-engine.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g1-recommend-budget-ranking.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g2-recommend-output-integration.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g3-init-core-routing.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g4-init-cloud-overwrite.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g5-init-hardware-summary.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g6-models-local.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g7-models-catalog.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g1-recommend.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g2-models-catalog-filters.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g3-cross-012.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/bench-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/fix-tooling-scrutiny-issues.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/pull-command.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g1-pull-core.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g2-pull-errors.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g3-bench-core.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g4-bench-advanced.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r2-g1-pull.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r2-g2-bench.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r3-g1-pull.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r3-g2-bench.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r4-g1-bench.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round2.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round3.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/release.yml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/workflows/publish.yml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.gitignore +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CODE_OF_CONDUCT.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CONTRIBUTING.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/DEVELOPING.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/LICENSE +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/README.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/SECURITY.md +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/pyproject.toml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/release-please-config.json +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/bench.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/config.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/down.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/init.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/install.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/logs.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/main.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/models.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/profile.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/pull.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/recommend.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/up.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/watch.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/benchmark.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/catalog.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/config.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/deps.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/hardware.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/launchd.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/litellm_gen.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/log_rotation.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/log_viewer.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/models.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/paths.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/process.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/pull.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/scoring.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_down.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_init.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_up.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/deepseek-r1-32b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/deepseek-r1-8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-12b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-27b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-4b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/llama3.3-8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/nemotron-49b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/nemotron-8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3-8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-0.8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-14b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-32b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-3b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-72b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-8b.yaml +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/py.typed +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/utils/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/conftest.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/test_inference_e2e.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/test_launchd_e2e.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/__init__.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_benchmark.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_catalog.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_bench.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_config.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_down.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_init.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_install.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_logs.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_models.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_profile.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_recommend.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_up.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_watch.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_config.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cross_area.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_data_dir.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_deps.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_hardware.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_lifecycle_fixes.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_litellm_gen.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_log_rotation.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_log_viewer.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_models.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_paths.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_process.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_robustness_fixes.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_scoring.py +0 -0
- {mlx_stack-0.3.2 → mlx_stack-0.3.3}/uv.lock +0 -0
|
@@ -32,16 +32,16 @@ jobs:
|
|
|
32
32
|
run: uv python install ${{ matrix.python-version }}
|
|
33
33
|
|
|
34
34
|
- name: Install dependencies
|
|
35
|
-
run:
|
|
35
|
+
run: make install
|
|
36
36
|
|
|
37
37
|
- name: Lint
|
|
38
|
-
run:
|
|
38
|
+
run: make lint
|
|
39
39
|
|
|
40
40
|
- name: Typecheck
|
|
41
|
-
run:
|
|
41
|
+
run: make typecheck
|
|
42
42
|
|
|
43
43
|
- name: Test with coverage
|
|
44
|
-
run:
|
|
44
|
+
run: make test
|
|
45
45
|
|
|
46
46
|
- name: Upload coverage report
|
|
47
47
|
uses: actions/upload-artifact@v4
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
outputs:
|
|
15
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
16
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: googleapis/release-please-action@v4
|
|
19
|
+
id: release
|
|
20
|
+
with:
|
|
21
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
config-file: release-please-config.json
|
|
23
|
+
manifest-file: .release-please-manifest.json
|
|
24
|
+
|
|
25
|
+
- name: Regenerate release notes
|
|
26
|
+
if: ${{ steps.release.outputs.release_created == 'true' }}
|
|
27
|
+
env:
|
|
28
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
run: |
|
|
30
|
+
gh api repos/${{ github.repository }}/releases/generate-notes \
|
|
31
|
+
-f tag_name=${{ steps.release.outputs.tag_name }} \
|
|
32
|
+
--jq '.body' \
|
|
33
|
+
| gh release edit ${{ steps.release.outputs.tag_name }} \
|
|
34
|
+
--repo ${{ github.repository }} \
|
|
35
|
+
--notes-file -
|
|
36
|
+
|
|
37
|
+
ci:
|
|
38
|
+
name: CI
|
|
39
|
+
needs: release-please
|
|
40
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
41
|
+
runs-on: macos-latest
|
|
42
|
+
strategy:
|
|
43
|
+
matrix:
|
|
44
|
+
python-version: ["3.13"]
|
|
45
|
+
steps:
|
|
46
|
+
- name: Checkout code
|
|
47
|
+
uses: actions/checkout@v6
|
|
48
|
+
with:
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
|
|
51
|
+
- name: Install uv
|
|
52
|
+
uses: astral-sh/setup-uv@v7
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
|
|
56
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
57
|
+
run: uv python install ${{ matrix.python-version }}
|
|
58
|
+
|
|
59
|
+
- name: Install dependencies
|
|
60
|
+
run: make install
|
|
61
|
+
|
|
62
|
+
- name: Lint
|
|
63
|
+
run: make lint
|
|
64
|
+
|
|
65
|
+
- name: Typecheck
|
|
66
|
+
run: make typecheck
|
|
67
|
+
|
|
68
|
+
- name: Test
|
|
69
|
+
run: make test
|
|
70
|
+
|
|
71
|
+
publish:
|
|
72
|
+
name: Build & Publish
|
|
73
|
+
needs: [release-please, ci]
|
|
74
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write
|
|
79
|
+
|
|
80
|
+
environment:
|
|
81
|
+
name: pypi
|
|
82
|
+
url: https://pypi.org/p/mlx-stack
|
|
83
|
+
|
|
84
|
+
steps:
|
|
85
|
+
- name: Checkout code
|
|
86
|
+
uses: actions/checkout@v6
|
|
87
|
+
with:
|
|
88
|
+
ref: ${{ needs.release-please.outputs.tag_name }}
|
|
89
|
+
fetch-depth: 0
|
|
90
|
+
|
|
91
|
+
- name: Install uv
|
|
92
|
+
uses: astral-sh/setup-uv@v7
|
|
93
|
+
with:
|
|
94
|
+
enable-cache: true
|
|
95
|
+
|
|
96
|
+
- name: Build package
|
|
97
|
+
run: uv build
|
|
98
|
+
|
|
99
|
+
- name: Publish to PyPI
|
|
100
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.3.3](https://github.com/weklund/mlx-stack/compare/v0.3.2...v0.3.3) (2026-04-02)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* replace raw status strings with ServiceHealth enum and assert behavior in tests ([#13](https://github.com/weklund/mlx-stack/issues/13)) ([ef0161c](https://github.com/weklund/mlx-stack/commit/ef0161c9f0b64096917fd06045786aad49fe8c93))
|
|
13
|
+
|
|
7
14
|
## [0.3.2](https://github.com/weklund/mlx-stack/compare/v0.3.1...v0.3.2) (2026-04-02)
|
|
8
15
|
|
|
9
16
|
|
mlx_stack-0.3.3/Makefile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.PHONY: install lint typecheck test check
|
|
2
|
+
|
|
3
|
+
## Install dev dependencies
|
|
4
|
+
install:
|
|
5
|
+
uv sync --dev
|
|
6
|
+
|
|
7
|
+
## Lint source and tests
|
|
8
|
+
lint:
|
|
9
|
+
uv run ruff check src/ tests/
|
|
10
|
+
|
|
11
|
+
## Run type checker across the full project
|
|
12
|
+
typecheck:
|
|
13
|
+
uv run python -m pyright
|
|
14
|
+
|
|
15
|
+
## Run tests with coverage
|
|
16
|
+
test:
|
|
17
|
+
uv run pytest --cov=src/mlx_stack -x -q --tb=short
|
|
18
|
+
|
|
19
|
+
## Run all checks (same as CI)
|
|
20
|
+
check: lint typecheck test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlx-stack
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: CLI control plane for local LLM infrastructure on Apple Silicon
|
|
5
5
|
Project-URL: Homepage, https://github.com/weklund/mlx-stack
|
|
6
6
|
Project-URL: Repository, https://github.com/weklund/mlx-stack
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -14,17 +14,22 @@ from rich.console import Console
|
|
|
14
14
|
from rich.table import Table
|
|
15
15
|
from rich.text import Text
|
|
16
16
|
|
|
17
|
-
from mlx_stack.core.stack_status import
|
|
17
|
+
from mlx_stack.core.stack_status import (
|
|
18
|
+
ServiceHealth,
|
|
19
|
+
StatusResult,
|
|
20
|
+
run_status,
|
|
21
|
+
status_to_dict,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
console = Console(stderr=True)
|
|
20
25
|
|
|
21
26
|
# Status display styling — maps state to Rich markup
|
|
22
|
-
_STATUS_STYLES: dict[
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
_STATUS_STYLES: dict[ServiceHealth, str] = {
|
|
28
|
+
ServiceHealth.HEALTHY: "[bold green]healthy[/bold green]",
|
|
29
|
+
ServiceHealth.DEGRADED: "[bold yellow]degraded[/bold yellow]",
|
|
30
|
+
ServiceHealth.DOWN: "[bold red]down[/bold red]",
|
|
31
|
+
ServiceHealth.CRASHED: "[bold red]crashed[/bold red]",
|
|
32
|
+
ServiceHealth.STOPPED: "[dim]stopped[/dim]",
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
|
|
@@ -16,6 +16,7 @@ Implements 5-state reporting per service:
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass, field
|
|
19
|
+
from enum import StrEnum
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
21
22
|
from mlx_stack.core.paths import get_stacks_dir
|
|
@@ -33,6 +34,16 @@ from mlx_stack.core.stack_up import LITELLM_HEALTH_PATH, LITELLM_SERVICE_NAME
|
|
|
33
34
|
VLLM_HEALTH_PATH = "/v1/models"
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
class ServiceHealth(StrEnum):
|
|
38
|
+
"""Health state of a managed service."""
|
|
39
|
+
|
|
40
|
+
HEALTHY = "healthy"
|
|
41
|
+
DEGRADED = "degraded"
|
|
42
|
+
DOWN = "down"
|
|
43
|
+
CRASHED = "crashed"
|
|
44
|
+
STOPPED = "stopped"
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
# --------------------------------------------------------------------------- #
|
|
37
48
|
# Data classes
|
|
38
49
|
# --------------------------------------------------------------------------- #
|
|
@@ -45,7 +56,7 @@ class ServiceStatus:
|
|
|
45
56
|
tier: str
|
|
46
57
|
model: str
|
|
47
58
|
port: int
|
|
48
|
-
status:
|
|
59
|
+
status: ServiceHealth
|
|
49
60
|
uptime: float | None # seconds, None for stopped/crashed
|
|
50
61
|
uptime_display: str # human-readable string or "-"
|
|
51
62
|
response_time: float | None # seconds, None if no HTTP response
|
|
@@ -171,7 +182,7 @@ def run_status(stack_name: str = "default") -> StatusResult:
|
|
|
171
182
|
tier=tier_name,
|
|
172
183
|
model=model,
|
|
173
184
|
port=port,
|
|
174
|
-
status=svc_status["status"],
|
|
185
|
+
status=ServiceHealth(svc_status["status"]),
|
|
175
186
|
uptime=svc_status["uptime"],
|
|
176
187
|
uptime_display=format_uptime(svc_status["uptime"]),
|
|
177
188
|
response_time=svc_status["response_time"],
|
|
@@ -190,7 +201,7 @@ def run_status(stack_name: str = "default") -> StatusResult:
|
|
|
190
201
|
tier="litellm",
|
|
191
202
|
model="proxy",
|
|
192
203
|
port=litellm_port,
|
|
193
|
-
status=litellm_status["status"],
|
|
204
|
+
status=ServiceHealth(litellm_status["status"]),
|
|
194
205
|
uptime=litellm_status["uptime"],
|
|
195
206
|
uptime_display=format_uptime(litellm_status["uptime"]),
|
|
196
207
|
response_time=litellm_status["response_time"],
|
|
@@ -34,6 +34,7 @@ from mlx_stack.core.process import (
|
|
|
34
34
|
write_pid_file,
|
|
35
35
|
)
|
|
36
36
|
from mlx_stack.core.stack_status import (
|
|
37
|
+
ServiceHealth,
|
|
37
38
|
ServiceStatus,
|
|
38
39
|
run_status,
|
|
39
40
|
)
|
|
@@ -576,10 +577,10 @@ def poll_cycle(
|
|
|
576
577
|
# Try to reset flap state if service has been stable
|
|
577
578
|
reset_flap_state(tracker)
|
|
578
579
|
|
|
579
|
-
if svc.status !=
|
|
580
|
+
if svc.status != ServiceHealth.CRASHED:
|
|
580
581
|
# Service is not crashed — reset consecutive failure counter
|
|
581
582
|
# if it was previously restarted and is now healthy
|
|
582
|
-
if svc.status ==
|
|
583
|
+
if svc.status == ServiceHealth.HEALTHY and tracker.consecutive_failures > 0:
|
|
583
584
|
tracker.consecutive_failures = 0
|
|
584
585
|
continue
|
|
585
586
|
|
|
@@ -20,6 +20,7 @@ import json
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from unittest.mock import MagicMock, patch
|
|
22
22
|
|
|
23
|
+
import pytest
|
|
23
24
|
from click.testing import CliRunner
|
|
24
25
|
|
|
25
26
|
from mlx_stack.cli.main import cli
|
|
@@ -619,8 +620,9 @@ class TestDownloadModel:
|
|
|
619
620
|
self,
|
|
620
621
|
mock_run: MagicMock,
|
|
621
622
|
tmp_path: Path,
|
|
623
|
+
capsys: pytest.CaptureFixture[str],
|
|
622
624
|
) -> None:
|
|
623
|
-
"""Successful download on first attempt."""
|
|
625
|
+
"""Successful download on first attempt shows completion message."""
|
|
624
626
|
from rich.console import Console
|
|
625
627
|
|
|
626
628
|
from mlx_stack.core.pull import download_model
|
|
@@ -628,7 +630,15 @@ class TestDownloadModel:
|
|
|
628
630
|
local_dir = tmp_path / "model"
|
|
629
631
|
console = Console()
|
|
630
632
|
download_model("mlx-community/test-4bit", local_dir, console)
|
|
631
|
-
|
|
633
|
+
|
|
634
|
+
# Verify correct repo and path were passed to the download function
|
|
635
|
+
args = mock_run.call_args
|
|
636
|
+
assert args[0][0] == "mlx-community/test-4bit"
|
|
637
|
+
assert args[0][1] == local_dir
|
|
638
|
+
|
|
639
|
+
# Verify user-facing success message was printed
|
|
640
|
+
output = capsys.readouterr().out
|
|
641
|
+
assert "Download complete" in output
|
|
632
642
|
|
|
633
643
|
@patch("mlx_stack.core.pull._run_download")
|
|
634
644
|
def test_retry_on_first_failure(
|
|
@@ -1022,7 +1032,7 @@ class TestPullCLI:
|
|
|
1022
1032
|
mock_download: MagicMock,
|
|
1023
1033
|
mlx_stack_home: Path,
|
|
1024
1034
|
) -> None:
|
|
1025
|
-
"""VAL-PULL-006: --force re-downloads."""
|
|
1035
|
+
"""VAL-PULL-006: --force re-downloads even when model exists."""
|
|
1026
1036
|
mock_catalog.return_value = [_make_entry()]
|
|
1027
1037
|
|
|
1028
1038
|
# Create existing model
|
|
@@ -1034,7 +1044,9 @@ class TestPullCLI:
|
|
|
1034
1044
|
runner = CliRunner()
|
|
1035
1045
|
result = runner.invoke(cli, ["pull", "qwen3.5-8b", "--force"])
|
|
1036
1046
|
assert result.exit_code == 0
|
|
1037
|
-
|
|
1047
|
+
# Must show success message (not the "already exists" skip)
|
|
1048
|
+
assert "is ready" in result.output
|
|
1049
|
+
assert "already exists" not in result.output
|
|
1038
1050
|
|
|
1039
1051
|
@patch(
|
|
1040
1052
|
"mlx_stack.core.pull.download_model",
|
|
@@ -1135,14 +1147,23 @@ class TestPullCLI:
|
|
|
1135
1147
|
mock_download: MagicMock,
|
|
1136
1148
|
mlx_stack_home: Path,
|
|
1137
1149
|
) -> None:
|
|
1138
|
-
"""VAL-CROSS-014: --bench flag
|
|
1150
|
+
"""VAL-CROSS-014: --bench flag runs benchmark and shows output."""
|
|
1139
1151
|
mock_catalog.return_value = [_make_entry()]
|
|
1140
1152
|
|
|
1141
|
-
with patch("mlx_stack.
|
|
1153
|
+
with patch("mlx_stack.core.benchmark.run_benchmark") as mock_bench:
|
|
1154
|
+
mock_bench.return_value = MagicMock(
|
|
1155
|
+
prompt_tps_mean=150.0,
|
|
1156
|
+
prompt_tps_std=5.0,
|
|
1157
|
+
gen_tps_mean=80.0,
|
|
1158
|
+
gen_tps_std=2.5,
|
|
1159
|
+
)
|
|
1142
1160
|
runner = CliRunner()
|
|
1143
1161
|
result = runner.invoke(cli, ["pull", "qwen3.5-8b", "--bench"])
|
|
1144
1162
|
assert result.exit_code == 0
|
|
1145
|
-
|
|
1163
|
+
# Must show benchmark output to the user
|
|
1164
|
+
assert "Running post-download benchmark" in result.output
|
|
1165
|
+
assert "Prompt TPS" in result.output
|
|
1166
|
+
assert "Gen TPS" in result.output
|
|
1146
1167
|
|
|
1147
1168
|
@patch("mlx_stack.core.pull.download_model")
|
|
1148
1169
|
@patch("mlx_stack.core.pull.check_disk_space", return_value=(True, 100.0))
|
|
@@ -23,6 +23,7 @@ from click.testing import CliRunner
|
|
|
23
23
|
|
|
24
24
|
from mlx_stack.cli.main import cli
|
|
25
25
|
from mlx_stack.core.stack_status import (
|
|
26
|
+
ServiceHealth,
|
|
26
27
|
ServiceStatus,
|
|
27
28
|
StatusResult,
|
|
28
29
|
_load_stack_for_status,
|
|
@@ -509,7 +510,7 @@ class TestStatusToDict:
|
|
|
509
510
|
tier="standard",
|
|
510
511
|
model="big-model",
|
|
511
512
|
port=8000,
|
|
512
|
-
status=
|
|
513
|
+
status=ServiceHealth.HEALTHY,
|
|
513
514
|
uptime=3600.0,
|
|
514
515
|
uptime_display="1h",
|
|
515
516
|
response_time=0.05,
|
|
@@ -519,7 +520,7 @@ class TestStatusToDict:
|
|
|
519
520
|
tier="fast",
|
|
520
521
|
model="fast-model",
|
|
521
522
|
port=8001,
|
|
522
|
-
status=
|
|
523
|
+
status=ServiceHealth.STOPPED,
|
|
523
524
|
uptime=None,
|
|
524
525
|
uptime_display="-",
|
|
525
526
|
response_time=None,
|
|
@@ -558,7 +559,7 @@ class TestStatusToDict:
|
|
|
558
559
|
tier="t1",
|
|
559
560
|
model="m1",
|
|
560
561
|
port=8000,
|
|
561
|
-
status=
|
|
562
|
+
status=ServiceHealth.HEALTHY,
|
|
562
563
|
uptime=60.0,
|
|
563
564
|
uptime_display="1m",
|
|
564
565
|
response_time=0.1,
|
|
@@ -723,27 +724,49 @@ class TestStatusCli:
|
|
|
723
724
|
mock_status: MagicMock,
|
|
724
725
|
mlx_stack_home: Path,
|
|
725
726
|
) -> None:
|
|
726
|
-
"""VAL-STATUS-003: Table shows tier names, models, ports."""
|
|
727
|
+
"""VAL-STATUS-003: Table shows per-tier names, models, ports, and distinct statuses."""
|
|
728
|
+
|
|
727
729
|
_write_stack_yaml(mlx_stack_home)
|
|
728
730
|
|
|
729
|
-
|
|
730
|
-
"
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
731
|
+
def side_effect(service_name: str, port: int, health_path: str = "") -> dict[str, Any]:
|
|
732
|
+
if service_name == "standard":
|
|
733
|
+
return {
|
|
734
|
+
"status": ServiceHealth.HEALTHY,
|
|
735
|
+
"pid": 12345,
|
|
736
|
+
"uptime": 150.0,
|
|
737
|
+
"response_time": 0.05,
|
|
738
|
+
}
|
|
739
|
+
if service_name == "fast":
|
|
740
|
+
return {
|
|
741
|
+
"status": ServiceHealth.CRASHED,
|
|
742
|
+
"pid": 99999,
|
|
743
|
+
"uptime": None,
|
|
744
|
+
"response_time": None,
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
"status": ServiceHealth.STOPPED,
|
|
748
|
+
"pid": None,
|
|
749
|
+
"uptime": None,
|
|
750
|
+
"response_time": None,
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
mock_status.side_effect = side_effect
|
|
735
754
|
|
|
736
755
|
runner = CliRunner()
|
|
737
756
|
result = runner.invoke(cli, ["status"])
|
|
738
757
|
|
|
739
758
|
assert result.exit_code == 0
|
|
759
|
+
# Tier names and models present
|
|
740
760
|
assert "standard" in result.output
|
|
741
761
|
assert "fast" in result.output
|
|
742
762
|
assert "big-model" in result.output
|
|
743
763
|
assert "fast-model" in result.output
|
|
764
|
+
# Ports present
|
|
744
765
|
assert "8000" in result.output
|
|
745
766
|
assert "8001" in result.output
|
|
746
|
-
|
|
767
|
+
# Each tier's distinct status must appear — verifies per-tier rendering
|
|
768
|
+
assert ServiceHealth.HEALTHY in result.output
|
|
769
|
+
assert ServiceHealth.CRASHED in result.output
|
|
747
770
|
|
|
748
771
|
@patch("mlx_stack.core.stack_status.get_service_status")
|
|
749
772
|
@patch("mlx_stack.core.stack_status._get_litellm_port", return_value=4000)
|
|
@@ -638,7 +638,13 @@ class TestInstallAgent:
|
|
|
638
638
|
path, was_reinstall = install_agent("/usr/bin/mlx-stack")
|
|
639
639
|
|
|
640
640
|
assert was_reinstall is False
|
|
641
|
-
|
|
641
|
+
assert path is mock_plist_path
|
|
642
|
+
|
|
643
|
+
# Verify plist data contains correct binary and label
|
|
644
|
+
plist_data = mock_write.call_args[0][0]
|
|
645
|
+
assert plist_data["Label"] == "com.mlx-stack.watchdog"
|
|
646
|
+
assert plist_data["ProgramArguments"][0] == "/usr/bin/mlx-stack"
|
|
647
|
+
assert "watch" in plist_data["ProgramArguments"]
|
|
642
648
|
|
|
643
649
|
def test_reinstall(
|
|
644
650
|
self, mlx_stack_home: Path, stack_definition: dict[str, Any]
|