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.
Files changed (226) hide show
  1. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/workflows/ci.yml +4 -4
  2. mlx_stack-0.3.3/.github/workflows/release-please.yml +100 -0
  3. mlx_stack-0.3.3/.release-please-manifest.json +3 -0
  4. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CHANGELOG.md +7 -0
  5. mlx_stack-0.3.3/Makefile +20 -0
  6. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/PKG-INFO +1 -1
  7. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/_version.py +2 -2
  8. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/status.py +12 -7
  9. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_status.py +14 -3
  10. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/watchdog.py +3 -2
  11. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_pull.py +28 -7
  12. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_status.py +34 -11
  13. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_launchd.py +7 -1
  14. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_ops_cross_area.py +27 -27
  15. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_watchdog.py +16 -16
  16. mlx_stack-0.3.2/.github/workflows/release-please.yml +0 -19
  17. mlx_stack-0.3.2/.release-please-manifest.json +0 -3
  18. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/init.sh +0 -0
  19. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/architecture.md +0 -0
  20. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/environment.md +0 -0
  21. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/library/user-testing.md +0 -0
  22. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/services.yaml +0 -0
  23. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/settings.json +0 -0
  24. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/skills/cli-feature/SKILL.md +0 -0
  25. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/configuration-management.json +0 -0
  26. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/dependency-management.json +0 -0
  27. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-catalog-errors-and-families.json +0 -0
  28. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-deps-binary-and-ansi.json +0 -0
  29. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/fix-scaffolding-data-home.json +0 -0
  30. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/hardware-detection.json +0 -0
  31. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/model-catalog.json +0 -0
  32. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/reviews/project-scaffolding.json +0 -0
  33. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/synthesis.json +0 -0
  34. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/scrutiny/synthesis.round1.json +0 -0
  35. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-config-basic.json +0 -0
  36. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-config-deps.json +0 -0
  37. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-profile-catalog.json +0 -0
  38. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/flows/foundation-setup-profile-core.json +0 -0
  39. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/foundation/user-testing/synthesis.json +0 -0
  40. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/down-command.json +0 -0
  41. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-preflight-and-readonly.json +0 -0
  42. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-process-robustness.json +0 -0
  43. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-typecheck.json +0 -0
  44. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/process-management.json +0 -0
  45. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/status-command.json +0 -0
  46. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/reviews/up-command.json +0 -0
  47. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/synthesis.json +0 -0
  48. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/scrutiny/synthesis.round1.json +0 -0
  49. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g1-deps-up-basics.json +0 -0
  50. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g2-up-startup.json +0 -0
  51. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g3-up-resilience.json +0 -0
  52. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g4-down.json +0 -0
  53. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g5-status.json +0 -0
  54. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r1-g6-cross.json +0 -0
  55. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r2-g1-fixes.json +0 -0
  56. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/flows/r2-g2-cross-blockers.json +0 -0
  57. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/synthesis.json +0 -0
  58. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/lifecycle/user-testing/synthesis.round1.json +0 -0
  59. {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
  60. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/reviews/misc-cross-area-validation.json +0 -0
  61. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/synthesis.json +0 -0
  62. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/scrutiny/synthesis.round1.json +0 -0
  63. {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
  64. {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
  65. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/synthesis.json +0 -0
  66. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/misc-cross-area/user-testing/synthesis.round1.json +0 -0
  67. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-lint-errors.json +0 -0
  68. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-scrutiny-issues.json +0 -0
  69. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/fix-ops-typecheck-errors.json +0 -0
  70. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/launchd-integration.json +0 -0
  71. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/log-rotation.json +0 -0
  72. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/logs-command.json +0 -0
  73. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/ops-cross-area-validation.json +0 -0
  74. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/reviews/watchdog-command.json +0 -0
  75. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/synthesis.json +0 -0
  76. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/scrutiny/synthesis.round1.json +0 -0
  77. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g1-log.json +0 -0
  78. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g2-logs-command.json +0 -0
  79. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g3-watch.json +0 -0
  80. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g4-launchd.json +0 -0
  81. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/flows/g5-cross-ops.json +0 -0
  82. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/ops/user-testing/synthesis.json +0 -0
  83. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/community-docs.json +0 -0
  84. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/developing-guide.json +0 -0
  85. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/fix-public-ready-scrutiny.json +0 -0
  86. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/github-actions-ci.json +0 -0
  87. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/reviews/readme-rewrite.json +0 -0
  88. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/synthesis.json +0 -0
  89. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/public-ready/scrutiny/synthesis.round1.json +0 -0
  90. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-init-and-models-issues.json +0 -0
  91. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-recommendation-scoring-issues.json +0 -0
  92. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/fix-scoring-lint.json +0 -0
  93. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/init-command.json +0 -0
  94. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/models-command.json +0 -0
  95. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/recommend-command.json +0 -0
  96. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/reviews/scoring-engine.json +0 -0
  97. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/synthesis.json +0 -0
  98. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/scrutiny/synthesis.round1.json +0 -0
  99. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g1-recommend-budget-ranking.json +0 -0
  100. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g2-recommend-output-integration.json +0 -0
  101. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g3-init-core-routing.json +0 -0
  102. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g4-init-cloud-overwrite.json +0 -0
  103. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g5-init-hardware-summary.json +0 -0
  104. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g6-models-local.json +0 -0
  105. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/g7-models-catalog.json +0 -0
  106. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g1-recommend.json +0 -0
  107. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g2-models-catalog-filters.json +0 -0
  108. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/flows/r2-g3-cross-012.json +0 -0
  109. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/synthesis.json +0 -0
  110. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/recommendation/user-testing/synthesis.round1.json +0 -0
  111. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/bench-command.json +0 -0
  112. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/fix-tooling-scrutiny-issues.json +0 -0
  113. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/reviews/pull-command.json +0 -0
  114. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/synthesis.json +0 -0
  115. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/scrutiny/synthesis.round1.json +0 -0
  116. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g1-pull-core.json +0 -0
  117. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g2-pull-errors.json +0 -0
  118. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g3-bench-core.json +0 -0
  119. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/g4-bench-advanced.json +0 -0
  120. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r2-g1-pull.json +0 -0
  121. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r2-g2-bench.json +0 -0
  122. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r3-g1-pull.json +0 -0
  123. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r3-g2-bench.json +0 -0
  124. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/flows/r4-g1-bench.json +0 -0
  125. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.json +0 -0
  126. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round1.json +0 -0
  127. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round2.json +0 -0
  128. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.factory/validation/tooling/user-testing/synthesis.round3.json +0 -0
  129. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/release.yml +0 -0
  130. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.github/workflows/publish.yml +0 -0
  131. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/.gitignore +0 -0
  132. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CODE_OF_CONDUCT.md +0 -0
  133. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/CONTRIBUTING.md +0 -0
  134. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/DEVELOPING.md +0 -0
  135. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/LICENSE +0 -0
  136. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/README.md +0 -0
  137. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/SECURITY.md +0 -0
  138. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/pyproject.toml +0 -0
  139. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/release-please-config.json +0 -0
  140. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/__init__.py +0 -0
  141. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/__init__.py +0 -0
  142. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/bench.py +0 -0
  143. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/config.py +0 -0
  144. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/down.py +0 -0
  145. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/init.py +0 -0
  146. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/install.py +0 -0
  147. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/logs.py +0 -0
  148. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/main.py +0 -0
  149. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/models.py +0 -0
  150. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/profile.py +0 -0
  151. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/pull.py +0 -0
  152. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/recommend.py +0 -0
  153. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/up.py +0 -0
  154. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/cli/watch.py +0 -0
  155. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/__init__.py +0 -0
  156. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/benchmark.py +0 -0
  157. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/catalog.py +0 -0
  158. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/config.py +0 -0
  159. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/deps.py +0 -0
  160. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/hardware.py +0 -0
  161. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/launchd.py +0 -0
  162. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/litellm_gen.py +0 -0
  163. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/log_rotation.py +0 -0
  164. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/log_viewer.py +0 -0
  165. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/models.py +0 -0
  166. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/paths.py +0 -0
  167. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/process.py +0 -0
  168. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/pull.py +0 -0
  169. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/scoring.py +0 -0
  170. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_down.py +0 -0
  171. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_init.py +0 -0
  172. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/core/stack_up.py +0 -0
  173. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/__init__.py +0 -0
  174. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/__init__.py +0 -0
  175. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/deepseek-r1-32b.yaml +0 -0
  176. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/deepseek-r1-8b.yaml +0 -0
  177. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-12b.yaml +0 -0
  178. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-27b.yaml +0 -0
  179. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/gemma3-4b.yaml +0 -0
  180. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/llama3.3-8b.yaml +0 -0
  181. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/nemotron-49b.yaml +0 -0
  182. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/nemotron-8b.yaml +0 -0
  183. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3-8b.yaml +0 -0
  184. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-0.8b.yaml +0 -0
  185. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-14b.yaml +0 -0
  186. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-32b.yaml +0 -0
  187. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-3b.yaml +0 -0
  188. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-72b.yaml +0 -0
  189. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/data/catalog/qwen3.5-8b.yaml +0 -0
  190. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/py.typed +0 -0
  191. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/src/mlx_stack/utils/__init__.py +0 -0
  192. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/__init__.py +0 -0
  193. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/conftest.py +0 -0
  194. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/__init__.py +0 -0
  195. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/test_inference_e2e.py +0 -0
  196. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/integration/test_launchd_e2e.py +0 -0
  197. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/__init__.py +0 -0
  198. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_benchmark.py +0 -0
  199. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_catalog.py +0 -0
  200. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli.py +0 -0
  201. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_bench.py +0 -0
  202. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_config.py +0 -0
  203. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_down.py +0 -0
  204. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_init.py +0 -0
  205. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_install.py +0 -0
  206. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_logs.py +0 -0
  207. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_models.py +0 -0
  208. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_profile.py +0 -0
  209. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_recommend.py +0 -0
  210. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_up.py +0 -0
  211. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cli_watch.py +0 -0
  212. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_config.py +0 -0
  213. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_cross_area.py +0 -0
  214. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_data_dir.py +0 -0
  215. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_deps.py +0 -0
  216. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_hardware.py +0 -0
  217. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_lifecycle_fixes.py +0 -0
  218. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_litellm_gen.py +0 -0
  219. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_log_rotation.py +0 -0
  220. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_log_viewer.py +0 -0
  221. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_models.py +0 -0
  222. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_paths.py +0 -0
  223. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_process.py +0 -0
  224. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_robustness_fixes.py +0 -0
  225. {mlx_stack-0.3.2 → mlx_stack-0.3.3}/tests/unit/test_scoring.py +0 -0
  226. {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: uv sync --dev
35
+ run: make install
36
36
 
37
37
  - name: Lint
38
- run: uv run ruff check src/ tests/
38
+ run: make lint
39
39
 
40
40
  - name: Typecheck
41
- run: uv run python -m pyright
41
+ run: make typecheck
42
42
 
43
43
  - name: Test with coverage
44
- run: uv run pytest --cov=src/mlx_stack --cov-report=xml -x -q --tb=short
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
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.3"
3
+ }
@@ -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
 
@@ -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.2
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.2'
22
- __version_tuple__ = version_tuple = (0, 3, 2)
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 StatusResult, run_status, status_to_dict
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[str, str] = {
23
- "healthy": "[bold green]healthy[/bold green]",
24
- "degraded": "[bold yellow]degraded[/bold yellow]",
25
- "down": "[bold red]down[/bold red]",
26
- "crashed": "[bold red]crashed[/bold red]",
27
- "stopped": "[dim]stopped[/dim]",
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: str # "healthy", "degraded", "down", "crashed", "stopped"
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 != "crashed":
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 == "healthy" and tracker.consecutive_failures > 0:
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
- mock_run.assert_called_once()
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
- mock_download.assert_called_once()
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 accepted and runs after download."""
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.cli.pull._run_post_download_bench") as mock_bench:
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
- mock_bench.assert_called_once()
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="healthy",
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="stopped",
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="healthy",
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
- mock_status.return_value = {
730
- "status": "healthy",
731
- "pid": 12345,
732
- "uptime": 150.0,
733
- "response_time": 0.05,
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
- assert "healthy" in result.output
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
- mock_write.assert_called_once()
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]