mlx-stack 0.3.5__tar.gz → 0.3.7__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 (244) hide show
  1. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/ci.yml +3 -0
  2. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/integration-nightly.yml +3 -0
  3. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/integration-prerelease.yml +3 -0
  4. mlx_stack-0.3.7/.release-please-manifest.json +3 -0
  5. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CHANGELOG.md +24 -0
  6. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/PKG-INFO +44 -17
  7. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/README.md +43 -16
  8. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/_version.py +2 -2
  9. mlx_stack-0.3.7/src/mlx_stack/cli/main.py +300 -0
  10. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli.py +29 -0
  11. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_ops_cross_area.py +12 -4
  12. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/uv.lock +3 -3
  13. mlx_stack-0.3.5/.release-please-manifest.json +0 -3
  14. mlx_stack-0.3.5/src/mlx_stack/cli/main.py +0 -190
  15. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/init.sh +0 -0
  16. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/architecture.md +0 -0
  17. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/environment.md +0 -0
  18. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/user-testing.md +0 -0
  19. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/services.yaml +0 -0
  20. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/settings.json +0 -0
  21. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/skills/cli-feature/SKILL.md +0 -0
  22. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/configuration-management.json +0 -0
  23. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/dependency-management.json +0 -0
  24. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-catalog-errors-and-families.json +0 -0
  25. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-deps-binary-and-ansi.json +0 -0
  26. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-scaffolding-data-home.json +0 -0
  27. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/hardware-detection.json +0 -0
  28. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/model-catalog.json +0 -0
  29. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/project-scaffolding.json +0 -0
  30. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.json +0 -0
  31. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.round1.json +0 -0
  32. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-basic.json +0 -0
  33. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-deps.json +0 -0
  34. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-profile-catalog.json +0 -0
  35. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-setup-profile-core.json +0 -0
  36. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/synthesis.json +0 -0
  37. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/down-command.json +0 -0
  38. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-preflight-and-readonly.json +0 -0
  39. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-process-robustness.json +0 -0
  40. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-typecheck.json +0 -0
  41. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/process-management.json +0 -0
  42. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/status-command.json +0 -0
  43. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/up-command.json +0 -0
  44. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.json +0 -0
  45. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.round1.json +0 -0
  46. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g1-deps-up-basics.json +0 -0
  47. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g2-up-startup.json +0 -0
  48. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g3-up-resilience.json +0 -0
  49. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g4-down.json +0 -0
  50. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g5-status.json +0 -0
  51. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g6-cross.json +0 -0
  52. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g1-fixes.json +0 -0
  53. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g2-cross-blockers.json +0 -0
  54. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.json +0 -0
  55. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.round1.json +0 -0
  56. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/reviews/fix-cross-area-test-rigor.json +0 -0
  57. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/reviews/misc-cross-area-validation.json +0 -0
  58. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.json +0 -0
  59. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.round1.json +0 -0
  60. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/flows/r1-g1-cross-flows.json +0 -0
  61. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/flows/r2-g4-cross-port5050.json +0 -0
  62. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.json +0 -0
  63. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.round1.json +0 -0
  64. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-lint-errors.json +0 -0
  65. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-scrutiny-issues.json +0 -0
  66. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-typecheck-errors.json +0 -0
  67. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/launchd-integration.json +0 -0
  68. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/log-rotation.json +0 -0
  69. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/logs-command.json +0 -0
  70. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/ops-cross-area-validation.json +0 -0
  71. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/watchdog-command.json +0 -0
  72. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.json +0 -0
  73. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.round1.json +0 -0
  74. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g1-log.json +0 -0
  75. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g2-logs-command.json +0 -0
  76. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g3-watch.json +0 -0
  77. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g4-launchd.json +0 -0
  78. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g5-cross-ops.json +0 -0
  79. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/synthesis.json +0 -0
  80. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/community-docs.json +0 -0
  81. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/developing-guide.json +0 -0
  82. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/fix-public-ready-scrutiny.json +0 -0
  83. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/github-actions-ci.json +0 -0
  84. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/readme-rewrite.json +0 -0
  85. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.json +0 -0
  86. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.round1.json +0 -0
  87. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-init-and-models-issues.json +0 -0
  88. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-recommendation-scoring-issues.json +0 -0
  89. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-scoring-lint.json +0 -0
  90. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/init-command.json +0 -0
  91. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/models-command.json +0 -0
  92. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/recommend-command.json +0 -0
  93. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/scoring-engine.json +0 -0
  94. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.json +0 -0
  95. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.round1.json +0 -0
  96. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g1-recommend-budget-ranking.json +0 -0
  97. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g2-recommend-output-integration.json +0 -0
  98. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g3-init-core-routing.json +0 -0
  99. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g4-init-cloud-overwrite.json +0 -0
  100. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g5-init-hardware-summary.json +0 -0
  101. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g6-models-local.json +0 -0
  102. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g7-models-catalog.json +0 -0
  103. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g1-recommend.json +0 -0
  104. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g2-models-catalog-filters.json +0 -0
  105. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g3-cross-012.json +0 -0
  106. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.json +0 -0
  107. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.round1.json +0 -0
  108. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/bench-command.json +0 -0
  109. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/fix-tooling-scrutiny-issues.json +0 -0
  110. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/pull-command.json +0 -0
  111. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.json +0 -0
  112. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.round1.json +0 -0
  113. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g1-pull-core.json +0 -0
  114. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g2-pull-errors.json +0 -0
  115. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g3-bench-core.json +0 -0
  116. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g4-bench-advanced.json +0 -0
  117. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g1-pull.json +0 -0
  118. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g2-bench.json +0 -0
  119. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g1-pull.json +0 -0
  120. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g2-bench.json +0 -0
  121. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r4-g1-bench.json +0 -0
  122. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.json +0 -0
  123. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round1.json +0 -0
  124. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round2.json +0 -0
  125. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round3.json +0 -0
  126. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/release.yml +0 -0
  127. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/publish.yml +0 -0
  128. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/release-please.yml +0 -0
  129. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.gitignore +0 -0
  130. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CODE_OF_CONDUCT.md +0 -0
  131. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CONTRIBUTING.md +0 -0
  132. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/DEVELOPING.md +0 -0
  133. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/LICENSE +0 -0
  134. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/Makefile +0 -0
  135. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/SECURITY.md +0 -0
  136. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/pyproject.toml +0 -0
  137. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/release-please-config.json +0 -0
  138. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/__init__.py +0 -0
  139. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/__init__.py +0 -0
  140. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/bench.py +0 -0
  141. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/config.py +0 -0
  142. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/down.py +0 -0
  143. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/init.py +0 -0
  144. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/install.py +0 -0
  145. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/logs.py +0 -0
  146. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/models.py +0 -0
  147. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/profile.py +0 -0
  148. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/pull.py +0 -0
  149. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/recommend.py +0 -0
  150. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/setup.py +0 -0
  151. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/status.py +0 -0
  152. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/up.py +0 -0
  153. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/watch.py +0 -0
  154. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/__init__.py +0 -0
  155. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/benchmark.py +0 -0
  156. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/catalog.py +0 -0
  157. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/config.py +0 -0
  158. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/deps.py +0 -0
  159. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/discovery.py +0 -0
  160. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/hardware.py +0 -0
  161. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/launchd.py +0 -0
  162. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/litellm_gen.py +0 -0
  163. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/log_rotation.py +0 -0
  164. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/log_viewer.py +0 -0
  165. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/models.py +0 -0
  166. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/onboarding.py +0 -0
  167. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/paths.py +0 -0
  168. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/process.py +0 -0
  169. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/pull.py +0 -0
  170. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/scoring.py +0 -0
  171. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_down.py +0 -0
  172. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_init.py +0 -0
  173. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_status.py +0 -0
  174. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_up.py +0 -0
  175. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/watchdog.py +0 -0
  176. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/__init__.py +0 -0
  177. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/benchmark_data.json +0 -0
  178. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/__init__.py +0 -0
  179. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-32b.yaml +0 -0
  180. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-8b.yaml +0 -0
  181. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-12b.yaml +0 -0
  182. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-27b.yaml +0 -0
  183. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-4b.yaml +0 -0
  184. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/llama3.3-8b.yaml +0 -0
  185. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-49b.yaml +0 -0
  186. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-8b.yaml +0 -0
  187. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3-8b.yaml +0 -0
  188. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-0.8b.yaml +0 -0
  189. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-14b.yaml +0 -0
  190. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-32b.yaml +0 -0
  191. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-3b.yaml +0 -0
  192. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-72b.yaml +0 -0
  193. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-8b.yaml +0 -0
  194. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/py.typed +0 -0
  195. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/utils/__init__.py +0 -0
  196. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/__init__.py +0 -0
  197. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/conftest.py +0 -0
  198. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/factories.py +0 -0
  199. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/fakes.py +0 -0
  200. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/__init__.py +0 -0
  201. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/conftest.py +0 -0
  202. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/report.py +0 -0
  203. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_catalog_validation.py +0 -0
  204. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_harness_compatibility.py +0 -0
  205. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_inference_e2e.py +0 -0
  206. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_launchd_e2e.py +0 -0
  207. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_model_smoke.py +0 -0
  208. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_stack_integration.py +0 -0
  209. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/__init__.py +0 -0
  210. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/conftest.py +0 -0
  211. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_benchmark.py +0 -0
  212. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_catalog.py +0 -0
  213. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_bench.py +0 -0
  214. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_config.py +0 -0
  215. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_down.py +0 -0
  216. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_init.py +0 -0
  217. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_install.py +0 -0
  218. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_logs.py +0 -0
  219. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_models.py +0 -0
  220. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_profile.py +0 -0
  221. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_pull.py +0 -0
  222. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_recommend.py +0 -0
  223. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_setup.py +0 -0
  224. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_status.py +0 -0
  225. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_up.py +0 -0
  226. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_watch.py +0 -0
  227. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_config.py +0 -0
  228. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cross_area.py +0 -0
  229. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_data_dir.py +0 -0
  230. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_deps.py +0 -0
  231. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_discovery.py +0 -0
  232. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_hardware.py +0 -0
  233. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_launchd.py +0 -0
  234. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_lifecycle_fixes.py +0 -0
  235. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_litellm_gen.py +0 -0
  236. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_log_rotation.py +0 -0
  237. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_log_viewer.py +0 -0
  238. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_models.py +0 -0
  239. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_onboarding.py +0 -0
  240. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_paths.py +0 -0
  241. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_process.py +0 -0
  242. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_robustness_fixes.py +0 -0
  243. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_scoring.py +0 -0
  244. {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_watchdog.py +0 -0
@@ -5,6 +5,9 @@ on:
5
5
  branches: [main]
6
6
  pull_request:
7
7
 
8
+ permissions:
9
+ contents: read
10
+
8
11
  concurrency:
9
12
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
10
13
  cancel-in-progress: true
@@ -9,6 +9,9 @@ on:
9
9
  description: "Specific model ID to test (blank = all non-gated models that fit in memory)"
10
10
  required: false
11
11
 
12
+ permissions:
13
+ contents: read
14
+
12
15
  concurrency:
13
16
  group: nightly-integration
14
17
  cancel-in-progress: true
@@ -5,6 +5,9 @@ on:
5
5
  types: [created]
6
6
  workflow_dispatch:
7
7
 
8
+ permissions:
9
+ contents: read
10
+
8
11
  concurrency:
9
12
  group: prerelease-integration
10
13
  cancel-in-progress: true
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.7"
3
+ }
@@ -4,6 +4,30 @@ 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.7](https://github.com/weklund/mlx-stack/compare/v0.3.6...v0.3.7) (2026-04-04)
8
+
9
+
10
+ ### Features
11
+
12
+ * branded welcome screen for bare CLI invocation ([#37](https://github.com/weklund/mlx-stack/issues/37)) ([b4becc9](https://github.com/weklund/mlx-stack/commit/b4becc9a2a4407eb98708c9116b5193286bb23f0))
13
+
14
+
15
+ ### Security
16
+
17
+ * bump pygments from 2.19.2 to 2.20.0 — fixes catastrophic backtracking in archetype, devicetree, and Lua lexers ([#36](https://github.com/weklund/mlx-stack/issues/36)) ([15859f1](https://github.com/weklund/mlx-stack/commit/15859f1))
18
+
19
+ ## [0.3.6](https://github.com/weklund/mlx-stack/compare/v0.3.5...v0.3.6) (2026-04-04)
20
+
21
+
22
+ ### Security
23
+
24
+ * add explicit `permissions: contents: read` to CI, nightly, and pre-release workflows to enforce least-privilege on GITHUB_TOKEN ([#34](https://github.com/weklund/mlx-stack/issues/34)) ([0f8bfb0](https://github.com/weklund/mlx-stack/commit/0f8bfb0a17df82142261284f8d6405918ae6b759))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * replace sleep-based sync with polling in flaky follow test ([#34](https://github.com/weklund/mlx-stack/issues/34))
30
+
7
31
  ## [0.3.5](https://github.com/weklund/mlx-stack/compare/v0.3.4...v0.3.5) (2026-04-04)
8
32
 
9
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-stack
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -43,8 +43,7 @@ Most local LLM tools serve **one model at a time** and leave you to figure out w
43
43
 
44
44
  ```bash
45
45
  uv tool install mlx-stack
46
- mlx-stack init --accept-defaults # detects hardware, picks models, generates configs
47
- mlx-stack up # 3 model servers + API gateway, one command
46
+ mlx-stack setup # detects hardware, picks models, pulls, starts — one command
48
47
  # → OpenAI-compatible API at http://localhost:4000/v1
49
48
  ```
50
49
 
@@ -170,6 +169,33 @@ uvx mlx-stack profile
170
169
 
171
170
  ## Quick Start
172
171
 
172
+ The fastest way to get running is the interactive setup command:
173
+
174
+ ```bash
175
+ mlx-stack setup
176
+ ```
177
+
178
+ This walks you through hardware detection, model selection, downloading, and starting all services in one guided flow. For CI or scripting, pass `--accept-defaults` to skip all prompts:
179
+
180
+ ```bash
181
+ mlx-stack setup --accept-defaults
182
+ ```
183
+
184
+ The OpenAI-compatible API is now available at `http://localhost:4000/v1`.
185
+
186
+ ```bash
187
+ # Check service health
188
+ mlx-stack status
189
+
190
+ # Stop everything when done
191
+ mlx-stack down
192
+ ```
193
+
194
+ <details>
195
+ <summary>Manual step-by-step setup</summary>
196
+
197
+ If you prefer full control over each step:
198
+
173
199
  ```bash
174
200
  # 1. Detect your hardware
175
201
  mlx-stack profile
@@ -187,17 +213,20 @@ mlx-stack up
187
213
  mlx-stack status
188
214
  ```
189
215
 
190
- The OpenAI-compatible API is now available at `http://localhost:4000/v1`.
191
-
192
- ```bash
193
- # Stop everything when done
194
- mlx-stack down
195
- ```
216
+ </details>
196
217
 
197
218
  ## CLI Reference
198
219
 
199
220
  ### Setup & Configuration
200
221
 
222
+ **`mlx-stack setup`** — Interactive guided setup: detects hardware, selects models, pulls weights, and starts the stack in one command.
223
+
224
+ | Option | Description |
225
+ |--------|-------------|
226
+ | `--accept-defaults` | Skip all prompts and use recommended defaults |
227
+ | `--intent <balanced\|agent-fleet>` | Use case intent (prompted if not provided) |
228
+ | `--budget-pct <10-90>` | Memory budget as percentage of unified memory (default: 40) |
229
+
201
230
  | Command | Description |
202
231
  |---------|-------------|
203
232
  | `mlx-stack profile` | Detect Apple Silicon hardware and save profile to `~/.mlx-stack/profile.json` |
@@ -323,7 +352,7 @@ mlx-stack is designed to run unattended on always-on hardware like a Mac Mini.
323
352
  ### Quick setup
324
353
 
325
354
  ```bash
326
- mlx-stack init --accept-defaults
355
+ mlx-stack setup --accept-defaults
327
356
  mlx-stack install
328
357
  ```
329
358
 
@@ -436,14 +465,12 @@ See [DEVELOPING.md](DEVELOPING.md) for the full developer guide, including proje
436
465
  # Install dev dependencies
437
466
  uv sync
438
467
 
439
- # Run tests
440
- uv run pytest
441
-
442
- # Type checking
443
- uv run python -m pyright
468
+ # Run all checks (lint + typecheck + tests) — same as CI
469
+ make check
444
470
 
445
- # Linting
446
- uv run ruff check src/ tests/
471
+ # Or individually
472
+ make lint # ruff + pyright
473
+ make test # pytest with coverage
447
474
  ```
448
475
 
449
476
  ## Contributing
@@ -14,8 +14,7 @@ Most local LLM tools serve **one model at a time** and leave you to figure out w
14
14
 
15
15
  ```bash
16
16
  uv tool install mlx-stack
17
- mlx-stack init --accept-defaults # detects hardware, picks models, generates configs
18
- mlx-stack up # 3 model servers + API gateway, one command
17
+ mlx-stack setup # detects hardware, picks models, pulls, starts — one command
19
18
  # → OpenAI-compatible API at http://localhost:4000/v1
20
19
  ```
21
20
 
@@ -141,6 +140,33 @@ uvx mlx-stack profile
141
140
 
142
141
  ## Quick Start
143
142
 
143
+ The fastest way to get running is the interactive setup command:
144
+
145
+ ```bash
146
+ mlx-stack setup
147
+ ```
148
+
149
+ This walks you through hardware detection, model selection, downloading, and starting all services in one guided flow. For CI or scripting, pass `--accept-defaults` to skip all prompts:
150
+
151
+ ```bash
152
+ mlx-stack setup --accept-defaults
153
+ ```
154
+
155
+ The OpenAI-compatible API is now available at `http://localhost:4000/v1`.
156
+
157
+ ```bash
158
+ # Check service health
159
+ mlx-stack status
160
+
161
+ # Stop everything when done
162
+ mlx-stack down
163
+ ```
164
+
165
+ <details>
166
+ <summary>Manual step-by-step setup</summary>
167
+
168
+ If you prefer full control over each step:
169
+
144
170
  ```bash
145
171
  # 1. Detect your hardware
146
172
  mlx-stack profile
@@ -158,17 +184,20 @@ mlx-stack up
158
184
  mlx-stack status
159
185
  ```
160
186
 
161
- The OpenAI-compatible API is now available at `http://localhost:4000/v1`.
162
-
163
- ```bash
164
- # Stop everything when done
165
- mlx-stack down
166
- ```
187
+ </details>
167
188
 
168
189
  ## CLI Reference
169
190
 
170
191
  ### Setup & Configuration
171
192
 
193
+ **`mlx-stack setup`** — Interactive guided setup: detects hardware, selects models, pulls weights, and starts the stack in one command.
194
+
195
+ | Option | Description |
196
+ |--------|-------------|
197
+ | `--accept-defaults` | Skip all prompts and use recommended defaults |
198
+ | `--intent <balanced\|agent-fleet>` | Use case intent (prompted if not provided) |
199
+ | `--budget-pct <10-90>` | Memory budget as percentage of unified memory (default: 40) |
200
+
172
201
  | Command | Description |
173
202
  |---------|-------------|
174
203
  | `mlx-stack profile` | Detect Apple Silicon hardware and save profile to `~/.mlx-stack/profile.json` |
@@ -294,7 +323,7 @@ mlx-stack is designed to run unattended on always-on hardware like a Mac Mini.
294
323
  ### Quick setup
295
324
 
296
325
  ```bash
297
- mlx-stack init --accept-defaults
326
+ mlx-stack setup --accept-defaults
298
327
  mlx-stack install
299
328
  ```
300
329
 
@@ -407,14 +436,12 @@ See [DEVELOPING.md](DEVELOPING.md) for the full developer guide, including proje
407
436
  # Install dev dependencies
408
437
  uv sync
409
438
 
410
- # Run tests
411
- uv run pytest
412
-
413
- # Type checking
414
- uv run python -m pyright
439
+ # Run all checks (lint + typecheck + tests) — same as CI
440
+ make check
415
441
 
416
- # Linting
417
- uv run ruff check src/ tests/
442
+ # Or individually
443
+ make lint # ruff + pyright
444
+ make test # pytest with coverage
418
445
  ```
419
446
 
420
447
  ## Contributing
@@ -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.5'
22
- __version_tuple__ = version_tuple = (0, 3, 5)
21
+ __version__ = version = '0.3.7'
22
+ __version_tuple__ = version_tuple = (0, 3, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,300 @@
1
+ """Main CLI entry point for mlx-stack.
2
+
3
+ Provides the top-level Click command group with --help, --version,
4
+ Rich-formatted output, and typo suggestions for unknown subcommands.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import difflib
10
+
11
+ import click
12
+ from rich.console import Console
13
+ from rich.table import Table
14
+ from rich.text import Text
15
+
16
+ from mlx_stack import __version__
17
+ from mlx_stack.cli.bench import bench as bench_command
18
+ from mlx_stack.cli.config import config as config_group
19
+ from mlx_stack.cli.down import down as down_command
20
+ from mlx_stack.cli.init import init as init_command
21
+ from mlx_stack.cli.install import install as install_command
22
+ from mlx_stack.cli.install import uninstall as uninstall_command
23
+ from mlx_stack.cli.logs import logs as logs_command
24
+ from mlx_stack.cli.models import models as models_command
25
+ from mlx_stack.cli.profile import profile as profile_command
26
+ from mlx_stack.cli.pull import pull as pull_command
27
+ from mlx_stack.cli.recommend import recommend as recommend_command
28
+ from mlx_stack.cli.setup import setup as setup_command
29
+ from mlx_stack.cli.status import status as status_command
30
+ from mlx_stack.cli.up import up as up_command
31
+ from mlx_stack.cli.watch import watch as watch_command
32
+
33
+ console = Console(stderr=True)
34
+
35
+ # ASCII banner — block-character logo, each row is (color, text) pairs
36
+ # Visual width: ~78 columns, fits 80-col terminals
37
+ _BANNER_LINES = [
38
+ ("cyan", " ███╗ ███╗ ██╗ ██╗ ██╗"),
39
+ ("white", " ███████╗████████╗ █████╗ ██████╗██╗ ██╗"),
40
+ ("cyan", " ████╗ ████║ ██║ ╚██╗██╔╝"),
41
+ ("white", " ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝"),
42
+ ("cyan", " ██╔████╔██║ ██║ ╚███╔╝ "),
43
+ ("white", " ███████╗ ██║ ███████║██║ █████╔╝ "),
44
+ ("cyan", " ██║╚██╔╝██║ ██║ ██╔██╗ "),
45
+ ("white", " ╚════██║ ██║ ██╔══██║██║ ██╔═██╗ "),
46
+ ("cyan", " ██║ ╚═╝ ██║ ███████╗██╔╝ ██╗"),
47
+ ("white", " ███████║ ██║ ██║ ██║╚██████╗██║ ██╗"),
48
+ ("cyan", " ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝"),
49
+ ("white", " ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝"),
50
+ ]
51
+
52
+ # Command categories and their members
53
+ _COMMAND_CATEGORIES: dict[str, list[str]] = {
54
+ "Setup & Configuration": ["setup", "profile", "config", "init"],
55
+ "Model Management": ["recommend", "models", "pull"],
56
+ "Stack Lifecycle": ["up", "down", "status", "watch", "install", "uninstall"],
57
+ "Diagnostics": ["bench", "logs"],
58
+ }
59
+
60
+
61
+ def _get_quick_status() -> list[tuple[str, str]]:
62
+ """Gather lightweight, read-only status info.
63
+
64
+ Only reads files that already exist — never creates directories.
65
+ Returns a list of (label, value) pairs for display.
66
+ """
67
+ items: list[tuple[str, str]] = []
68
+
69
+ # Chip — read profile.json if it exists
70
+ try:
71
+ from mlx_stack.core.paths import get_profile_path
72
+
73
+ profile_path = get_profile_path()
74
+ if profile_path.exists():
75
+ from mlx_stack.core.hardware import load_profile
76
+
77
+ profile = load_profile()
78
+ if profile:
79
+ mem = f"{profile.memory_gb}GB"
80
+ items.append(("Chip", f"{profile.chip} {mem} unified"))
81
+ except Exception:
82
+ pass
83
+
84
+ # Stack — check if a stack definition exists
85
+ try:
86
+ from mlx_stack.core.paths import get_stacks_dir
87
+
88
+ stack_dir = get_stacks_dir()
89
+ if stack_dir.exists() and (stack_dir / "default.yaml").exists():
90
+ items.append(("Stack", "configured"))
91
+ else:
92
+ items.append(("Stack", "not configured"))
93
+ except Exception:
94
+ pass
95
+
96
+ return items
97
+
98
+
99
+ def _render_welcome(ctx: click.Context, group: RichGroup) -> None:
100
+ """Render the branded welcome screen for bare `mlx-stack` invocation."""
101
+ out = Console()
102
+
103
+ # Banner
104
+ out.print()
105
+ for i in range(0, len(_BANNER_LINES), 2):
106
+ left_color, left_text = _BANNER_LINES[i]
107
+ right_color, right_text = _BANNER_LINES[i + 1]
108
+ line = Text(left_text, style=f"bold {left_color}")
109
+ line.append(right_text, style=f"bold {right_color}")
110
+ out.print(line, highlight=False)
111
+ out.print()
112
+
113
+ # Version line
114
+ ver_line = Text(" v" + __version__, style="dim")
115
+ ver_line.append(" ")
116
+ ver_line.append("Local LLM infrastructure on Apple Silicon", style="italic")
117
+ out.print(ver_line)
118
+ out.print()
119
+
120
+ # Quick status (read-only, best-effort)
121
+ status_items = _get_quick_status()
122
+ if status_items:
123
+ for label, value in status_items:
124
+ line = Text(f" {label}: ", style="bold")
125
+ style = "green" if value != "not configured" else "yellow"
126
+ line.append(value, style=style)
127
+ out.print(line)
128
+ out.print()
129
+
130
+ # Grouped commands
131
+ commands = group.list_commands(ctx)
132
+ if commands:
133
+ for category_name, cmd_names in _COMMAND_CATEGORIES.items():
134
+ cmds_in_cat = []
135
+ for cmd_name in cmd_names:
136
+ if cmd_name in commands:
137
+ cmd = group.get_command(ctx, cmd_name)
138
+ if cmd:
139
+ cmds_in_cat.append(
140
+ (cmd_name, cmd.get_short_help_str(limit=60))
141
+ )
142
+ if not cmds_in_cat:
143
+ continue
144
+
145
+ out.print(Text(f" {category_name}", style="bold yellow"))
146
+ cmd_table = Table(
147
+ show_header=False,
148
+ box=None,
149
+ padding=(0, 2),
150
+ pad_edge=False,
151
+ )
152
+ cmd_table.add_column(style="green", min_width=14)
153
+ cmd_table.add_column(style="dim")
154
+ for name, help_text in cmds_in_cat:
155
+ cmd_table.add_row(f" {name}", help_text)
156
+ out.print(cmd_table)
157
+ out.print()
158
+
159
+ # Get started nudge
160
+ if not status_items or all(v == "not configured" for _, v in status_items):
161
+ out.print(
162
+ Text(" Get started: ", style="bold")
163
+ + Text("mlx-stack setup", style="bold cyan")
164
+ )
165
+ else:
166
+ out.print(
167
+ Text(" Run ", style="dim")
168
+ + Text("mlx-stack <command> --help", style="dim bold")
169
+ + Text(" for details on any command.", style="dim")
170
+ )
171
+ out.print()
172
+
173
+
174
+ class RichGroup(click.Group):
175
+ """Custom Click Group with Rich-formatted help and typo suggestions."""
176
+
177
+ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
178
+ """Format help text using Rich tables."""
179
+ console_out = Console()
180
+
181
+ # Title
182
+ console_out.print()
183
+ title = Text("mlx-stack", style="bold cyan")
184
+ title.append(" — CLI control plane for local LLM infrastructure on Apple Silicon")
185
+ console_out.print(title)
186
+ console_out.print()
187
+
188
+ # Usage
189
+ usage = Text("Usage: ", style="bold") + Text("mlx-stack [OPTIONS] COMMAND [ARGS]...")
190
+ console_out.print(usage)
191
+ console_out.print()
192
+
193
+ # Options
194
+ console_out.print(Text("Options:", style="bold yellow"))
195
+ options_table = Table(show_header=False, box=None, padding=(0, 2))
196
+ options_table.add_column(style="green", min_width=20)
197
+ options_table.add_column()
198
+ options_table.add_row("--version", "Show version and exit.")
199
+ options_table.add_row("--help", "Show this message and exit.")
200
+ console_out.print(options_table)
201
+ console_out.print()
202
+
203
+ # Commands grouped by category
204
+ commands = self.list_commands(ctx)
205
+ if commands:
206
+ for category_name, cmd_names in _COMMAND_CATEGORIES.items():
207
+ cmds_in_cat = []
208
+ for cmd_name in cmd_names:
209
+ if cmd_name in commands:
210
+ cmd = self.get_command(ctx, cmd_name)
211
+ if cmd:
212
+ cmds_in_cat.append(
213
+ (cmd_name, cmd.get_short_help_str(limit=80))
214
+ )
215
+ if not cmds_in_cat:
216
+ continue
217
+
218
+ console_out.print(Text(f"{category_name}:", style="bold yellow"))
219
+ cmd_table = Table(show_header=False, box=None, padding=(0, 2))
220
+ cmd_table.add_column(style="green", min_width=20)
221
+ cmd_table.add_column()
222
+ for name, help_text in cmds_in_cat:
223
+ cmd_table.add_row(name, help_text)
224
+ console_out.print(cmd_table)
225
+ console_out.print()
226
+
227
+ def resolve_command(
228
+ self, ctx: click.Context, args: list[str]
229
+ ) -> tuple[str | None, click.Command | None, list[str]]:
230
+ """Override resolve_command to provide typo suggestions."""
231
+ try:
232
+ return super().resolve_command(ctx, args)
233
+ except click.UsageError:
234
+ # Get the attempted command name
235
+ if args:
236
+ cmd_name = args[0]
237
+ available = self.list_commands(ctx)
238
+ matches = difflib.get_close_matches(cmd_name, available, n=3, cutoff=0.5)
239
+
240
+ error_msg = f"Error: No such command '{cmd_name}'."
241
+ if matches:
242
+ suggestions = ", ".join(f"'{m}'" for m in matches)
243
+ error_msg += f"\n\nDid you mean one of these?\n {suggestions}"
244
+ error_msg += "\n\nRun 'mlx-stack --help' for a list of available commands."
245
+
246
+ console.print(f"[red]{error_msg}[/red]")
247
+ ctx.exit(2)
248
+ raise SystemExit(2) # noqa: B904 — we want to exit, not chain
249
+ raise
250
+
251
+
252
+ def version_callback(ctx: click.Context, _param: click.Parameter, value: bool) -> None:
253
+ """Print version and exit."""
254
+ if not value or ctx.resilient_parsing:
255
+ return
256
+ click.echo(f"mlx-stack, version {__version__}")
257
+ ctx.exit(0)
258
+
259
+
260
+ @click.group(cls=RichGroup, invoke_without_command=True)
261
+ @click.option(
262
+ "--version",
263
+ is_flag=True,
264
+ callback=version_callback,
265
+ expose_value=False,
266
+ is_eager=True,
267
+ help="Show version and exit.",
268
+ )
269
+ @click.pass_context
270
+ def cli(ctx: click.Context) -> None:
271
+ """CLI control plane for local LLM infrastructure on Apple Silicon."""
272
+ if ctx.invoked_subcommand is None:
273
+ _render_welcome(ctx, ctx.command) # type: ignore[arg-type]
274
+
275
+
276
+ # --- Placeholder commands for planned features ---
277
+ # These will be replaced by real implementations in subsequent features.
278
+
279
+
280
+ cli.add_command(setup_command, "setup")
281
+ cli.add_command(profile_command, "profile")
282
+ cli.add_command(recommend_command, "recommend")
283
+ cli.add_command(init_command, "init")
284
+
285
+
286
+ cli.add_command(pull_command, "pull")
287
+ cli.add_command(models_command, "models")
288
+ cli.add_command(up_command, "up")
289
+ cli.add_command(down_command, "down")
290
+ cli.add_command(status_command, "status")
291
+
292
+
293
+ cli.add_command(watch_command, "watch")
294
+ cli.add_command(install_command, "install")
295
+ cli.add_command(uninstall_command, "uninstall")
296
+
297
+ cli.add_command(bench_command, "bench")
298
+ cli.add_command(logs_command, "logs")
299
+
300
+ cli.add_command(config_group, "config")
@@ -50,6 +50,35 @@ class TestCLIHelp:
50
50
  assert result.exit_code == 0
51
51
  assert "mlx-stack" in result.output
52
52
 
53
+ def test_bare_command_shows_banner(self) -> None:
54
+ runner = CliRunner()
55
+ result = runner.invoke(cli, [])
56
+ assert result.exit_code == 0
57
+ # Banner contains box-drawing characters from the ASCII art
58
+ assert "███" in result.output
59
+
60
+ def test_bare_command_shows_version(self) -> None:
61
+ runner = CliRunner()
62
+ result = runner.invoke(cli, [])
63
+ assert __version__ in result.output
64
+
65
+ def test_bare_command_shows_command_categories(self) -> None:
66
+ runner = CliRunner()
67
+ result = runner.invoke(cli, [])
68
+ assert "Setup & Configuration" in result.output
69
+ assert "Model Management" in result.output
70
+ assert "Stack Lifecycle" in result.output
71
+ assert "Diagnostics" in result.output
72
+
73
+ def test_bare_command_shows_get_started(
74
+ self, clean_mlx_stack_home: Path,
75
+ ) -> None:
76
+ """When no profile/stack exists, shows 'Get started' nudge."""
77
+ runner = CliRunner()
78
+ result = runner.invoke(cli, [])
79
+ assert result.exit_code == 0
80
+ assert "mlx-stack setup" in result.output
81
+
53
82
 
54
83
  class TestCLIVersion:
55
84
  """Tests for --version output."""
@@ -410,6 +410,15 @@ class TestFollowSurvivesRotation:
410
410
  output_callback=lambda text: captured.append(text),
411
411
  )
412
412
 
413
+ def wait_for_content(marker: str, timeout: float = 5.0) -> bool:
414
+ """Wait until marker appears in captured output."""
415
+ end = time.monotonic() + timeout
416
+ while time.monotonic() < end:
417
+ if any(marker in c for c in captured):
418
+ return True
419
+ time.sleep(0.05)
420
+ return False
421
+
413
422
  thread = threading.Thread(target=follow_thread, daemon=True)
414
423
  thread.start()
415
424
 
@@ -423,7 +432,9 @@ class TestFollowSurvivesRotation:
423
432
  with open(log, "a") as f:
424
433
  f.write("after-truncation\n")
425
434
 
426
- time.sleep(1.0)
435
+ assert wait_for_content("after-truncation"), (
436
+ f"Timed out waiting for 'after-truncation' in captured output: {captured}"
437
+ )
427
438
 
428
439
  import ctypes
429
440
 
@@ -435,9 +446,6 @@ class TestFollowSurvivesRotation:
435
446
  )
436
447
  thread.join(timeout=3)
437
448
 
438
- combined = "\n".join(captured)
439
- assert "after-truncation" in combined
440
-
441
449
  def test_follow_continues_after_multiple_rotations(
442
450
  self, mlx_stack_home: Path, logs_dir: Path
443
451
  ) -> None:
@@ -363,11 +363,11 @@ wheels = [
363
363
 
364
364
  [[package]]
365
365
  name = "pygments"
366
- version = "2.19.2"
366
+ version = "2.20.0"
367
367
  source = { registry = "https://pypi.org/simple" }
368
- sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
368
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
369
369
  wheels = [
370
- { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
370
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
371
371
  ]
372
372
 
373
373
  [[package]]
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.3.5"
3
- }