mlx-stack 0.3.6__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.7/.release-please-manifest.json +3 -0
  2. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/CHANGELOG.md +18 -1
  3. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/PKG-INFO +1 -1
  4. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/_version.py +2 -2
  5. mlx_stack-0.3.7/src/mlx_stack/cli/main.py +300 -0
  6. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli.py +29 -0
  7. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/uv.lock +3 -3
  8. mlx_stack-0.3.6/.release-please-manifest.json +0 -3
  9. mlx_stack-0.3.6/src/mlx_stack/cli/main.py +0 -190
  10. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/init.sh +0 -0
  11. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/library/architecture.md +0 -0
  12. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/library/environment.md +0 -0
  13. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/library/user-testing.md +0 -0
  14. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/services.yaml +0 -0
  15. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/settings.json +0 -0
  16. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/skills/cli-feature/SKILL.md +0 -0
  17. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/configuration-management.json +0 -0
  18. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/dependency-management.json +0 -0
  19. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-catalog-errors-and-families.json +0 -0
  20. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-deps-binary-and-ansi.json +0 -0
  21. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-scaffolding-data-home.json +0 -0
  22. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/hardware-detection.json +0 -0
  23. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/model-catalog.json +0 -0
  24. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/project-scaffolding.json +0 -0
  25. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.json +0 -0
  26. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.round1.json +0 -0
  27. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-basic.json +0 -0
  28. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-deps.json +0 -0
  29. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-profile-catalog.json +0 -0
  30. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-setup-profile-core.json +0 -0
  31. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/synthesis.json +0 -0
  32. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/down-command.json +0 -0
  33. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-preflight-and-readonly.json +0 -0
  34. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-process-robustness.json +0 -0
  35. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-typecheck.json +0 -0
  36. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/process-management.json +0 -0
  37. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/status-command.json +0 -0
  38. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/up-command.json +0 -0
  39. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.json +0 -0
  40. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.round1.json +0 -0
  41. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g1-deps-up-basics.json +0 -0
  42. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g2-up-startup.json +0 -0
  43. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g3-up-resilience.json +0 -0
  44. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g4-down.json +0 -0
  45. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g5-status.json +0 -0
  46. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g6-cross.json +0 -0
  47. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g1-fixes.json +0 -0
  48. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g2-cross-blockers.json +0 -0
  49. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.json +0 -0
  50. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.round1.json +0 -0
  51. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/reviews/fix-cross-area-test-rigor.json +0 -0
  52. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/reviews/misc-cross-area-validation.json +0 -0
  53. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.json +0 -0
  54. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.round1.json +0 -0
  55. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/flows/r1-g1-cross-flows.json +0 -0
  56. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/flows/r2-g4-cross-port5050.json +0 -0
  57. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.json +0 -0
  58. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.round1.json +0 -0
  59. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-lint-errors.json +0 -0
  60. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-scrutiny-issues.json +0 -0
  61. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-typecheck-errors.json +0 -0
  62. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/launchd-integration.json +0 -0
  63. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/log-rotation.json +0 -0
  64. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/logs-command.json +0 -0
  65. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/ops-cross-area-validation.json +0 -0
  66. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/watchdog-command.json +0 -0
  67. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.json +0 -0
  68. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.round1.json +0 -0
  69. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g1-log.json +0 -0
  70. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g2-logs-command.json +0 -0
  71. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g3-watch.json +0 -0
  72. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g4-launchd.json +0 -0
  73. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g5-cross-ops.json +0 -0
  74. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/synthesis.json +0 -0
  75. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/community-docs.json +0 -0
  76. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/developing-guide.json +0 -0
  77. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/fix-public-ready-scrutiny.json +0 -0
  78. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/github-actions-ci.json +0 -0
  79. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/readme-rewrite.json +0 -0
  80. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.json +0 -0
  81. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.round1.json +0 -0
  82. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-init-and-models-issues.json +0 -0
  83. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-recommendation-scoring-issues.json +0 -0
  84. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-scoring-lint.json +0 -0
  85. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/init-command.json +0 -0
  86. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/models-command.json +0 -0
  87. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/recommend-command.json +0 -0
  88. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/scoring-engine.json +0 -0
  89. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.json +0 -0
  90. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.round1.json +0 -0
  91. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g1-recommend-budget-ranking.json +0 -0
  92. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g2-recommend-output-integration.json +0 -0
  93. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g3-init-core-routing.json +0 -0
  94. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g4-init-cloud-overwrite.json +0 -0
  95. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g5-init-hardware-summary.json +0 -0
  96. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g6-models-local.json +0 -0
  97. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g7-models-catalog.json +0 -0
  98. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g1-recommend.json +0 -0
  99. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g2-models-catalog-filters.json +0 -0
  100. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g3-cross-012.json +0 -0
  101. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.json +0 -0
  102. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.round1.json +0 -0
  103. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/bench-command.json +0 -0
  104. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/fix-tooling-scrutiny-issues.json +0 -0
  105. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/pull-command.json +0 -0
  106. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.json +0 -0
  107. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.round1.json +0 -0
  108. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g1-pull-core.json +0 -0
  109. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g2-pull-errors.json +0 -0
  110. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g3-bench-core.json +0 -0
  111. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g4-bench-advanced.json +0 -0
  112. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g1-pull.json +0 -0
  113. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g2-bench.json +0 -0
  114. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g1-pull.json +0 -0
  115. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g2-bench.json +0 -0
  116. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r4-g1-bench.json +0 -0
  117. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.json +0 -0
  118. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round1.json +0 -0
  119. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round2.json +0 -0
  120. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round3.json +0 -0
  121. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/release.yml +0 -0
  122. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/workflows/ci.yml +0 -0
  123. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/workflows/integration-nightly.yml +0 -0
  124. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/workflows/integration-prerelease.yml +0 -0
  125. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/workflows/publish.yml +0 -0
  126. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.github/workflows/release-please.yml +0 -0
  127. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/.gitignore +0 -0
  128. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/CODE_OF_CONDUCT.md +0 -0
  129. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/CONTRIBUTING.md +0 -0
  130. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/DEVELOPING.md +0 -0
  131. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/LICENSE +0 -0
  132. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/Makefile +0 -0
  133. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/README.md +0 -0
  134. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/SECURITY.md +0 -0
  135. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/pyproject.toml +0 -0
  136. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/release-please-config.json +0 -0
  137. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/__init__.py +0 -0
  138. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/__init__.py +0 -0
  139. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/bench.py +0 -0
  140. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/config.py +0 -0
  141. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/down.py +0 -0
  142. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/init.py +0 -0
  143. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/install.py +0 -0
  144. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/logs.py +0 -0
  145. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/models.py +0 -0
  146. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/profile.py +0 -0
  147. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/pull.py +0 -0
  148. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/recommend.py +0 -0
  149. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/setup.py +0 -0
  150. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/status.py +0 -0
  151. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/up.py +0 -0
  152. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/cli/watch.py +0 -0
  153. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/__init__.py +0 -0
  154. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/benchmark.py +0 -0
  155. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/catalog.py +0 -0
  156. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/config.py +0 -0
  157. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/deps.py +0 -0
  158. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/discovery.py +0 -0
  159. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/hardware.py +0 -0
  160. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/launchd.py +0 -0
  161. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/litellm_gen.py +0 -0
  162. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/log_rotation.py +0 -0
  163. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/log_viewer.py +0 -0
  164. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/models.py +0 -0
  165. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/onboarding.py +0 -0
  166. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/paths.py +0 -0
  167. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/process.py +0 -0
  168. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/pull.py +0 -0
  169. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/scoring.py +0 -0
  170. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_down.py +0 -0
  171. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_init.py +0 -0
  172. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_status.py +0 -0
  173. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_up.py +0 -0
  174. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/core/watchdog.py +0 -0
  175. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/__init__.py +0 -0
  176. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/benchmark_data.json +0 -0
  177. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/__init__.py +0 -0
  178. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-32b.yaml +0 -0
  179. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-8b.yaml +0 -0
  180. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-12b.yaml +0 -0
  181. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-27b.yaml +0 -0
  182. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-4b.yaml +0 -0
  183. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/llama3.3-8b.yaml +0 -0
  184. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-49b.yaml +0 -0
  185. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-8b.yaml +0 -0
  186. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3-8b.yaml +0 -0
  187. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-0.8b.yaml +0 -0
  188. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-14b.yaml +0 -0
  189. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-32b.yaml +0 -0
  190. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-3b.yaml +0 -0
  191. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-72b.yaml +0 -0
  192. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-8b.yaml +0 -0
  193. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/py.typed +0 -0
  194. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/src/mlx_stack/utils/__init__.py +0 -0
  195. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/__init__.py +0 -0
  196. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/conftest.py +0 -0
  197. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/factories.py +0 -0
  198. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/fakes.py +0 -0
  199. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/__init__.py +0 -0
  200. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/conftest.py +0 -0
  201. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/report.py +0 -0
  202. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_catalog_validation.py +0 -0
  203. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_harness_compatibility.py +0 -0
  204. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_inference_e2e.py +0 -0
  205. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_launchd_e2e.py +0 -0
  206. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_model_smoke.py +0 -0
  207. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/integration/test_stack_integration.py +0 -0
  208. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/__init__.py +0 -0
  209. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/conftest.py +0 -0
  210. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_benchmark.py +0 -0
  211. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_catalog.py +0 -0
  212. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_bench.py +0 -0
  213. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_config.py +0 -0
  214. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_down.py +0 -0
  215. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_init.py +0 -0
  216. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_install.py +0 -0
  217. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_logs.py +0 -0
  218. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_models.py +0 -0
  219. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_profile.py +0 -0
  220. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_pull.py +0 -0
  221. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_recommend.py +0 -0
  222. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_setup.py +0 -0
  223. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_status.py +0 -0
  224. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_up.py +0 -0
  225. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cli_watch.py +0 -0
  226. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_config.py +0 -0
  227. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_cross_area.py +0 -0
  228. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_data_dir.py +0 -0
  229. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_deps.py +0 -0
  230. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_discovery.py +0 -0
  231. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_hardware.py +0 -0
  232. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_launchd.py +0 -0
  233. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_lifecycle_fixes.py +0 -0
  234. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_litellm_gen.py +0 -0
  235. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_log_rotation.py +0 -0
  236. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_log_viewer.py +0 -0
  237. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_models.py +0 -0
  238. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_onboarding.py +0 -0
  239. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_ops_cross_area.py +0 -0
  240. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_paths.py +0 -0
  241. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_process.py +0 -0
  242. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_robustness_fixes.py +0 -0
  243. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_scoring.py +0 -0
  244. {mlx_stack-0.3.6 → mlx_stack-0.3.7}/tests/unit/test_watchdog.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.7"
3
+ }
@@ -4,12 +4,29 @@ 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
+
7
19
  ## [0.3.6](https://github.com/weklund/mlx-stack/compare/v0.3.5...v0.3.6) (2026-04-04)
8
20
 
9
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
+
10
27
  ### Bug Fixes
11
28
 
12
- * add explicit permissions to CI and integration workflows ([#34](https://github.com/weklund/mlx-stack/issues/34)) ([0f8bfb0](https://github.com/weklund/mlx-stack/commit/0f8bfb0a17df82142261284f8d6405918ae6b759))
29
+ * replace sleep-based sync with polling in flaky follow test ([#34](https://github.com/weklund/mlx-stack/issues/34))
13
30
 
14
31
  ## [0.3.5](https://github.com/weklund/mlx-stack/compare/v0.3.4...v0.3.5) (2026-04-04)
15
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-stack
3
- Version: 0.3.6
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
@@ -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.6'
22
- __version_tuple__ = version_tuple = (0, 3, 6)
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."""
@@ -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.6"
3
- }
@@ -1,190 +0,0 @@
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
-
36
- class RichGroup(click.Group):
37
- """Custom Click Group with Rich-formatted help and typo suggestions."""
38
-
39
- def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
40
- """Format help text using Rich tables."""
41
- console_out = Console()
42
-
43
- # Title
44
- console_out.print()
45
- title = Text("mlx-stack", style="bold cyan")
46
- title.append(" — CLI control plane for local LLM infrastructure on Apple Silicon")
47
- console_out.print(title)
48
- console_out.print()
49
-
50
- # Usage
51
- usage = Text("Usage: ", style="bold") + Text("mlx-stack [OPTIONS] COMMAND [ARGS]...")
52
- console_out.print(usage)
53
- console_out.print()
54
-
55
- # Options
56
- console_out.print(Text("Options:", style="bold yellow"))
57
- options_table = Table(show_header=False, box=None, padding=(0, 2))
58
- options_table.add_column(style="green", min_width=20)
59
- options_table.add_column()
60
- options_table.add_row("--version", "Show version and exit.")
61
- options_table.add_row("--help", "Show this message and exit.")
62
- console_out.print(options_table)
63
- console_out.print()
64
-
65
- # Commands grouped by category
66
- commands = self.list_commands(ctx)
67
- if commands:
68
- # Group commands by category
69
- categories: dict[str, list[tuple[str, str]]] = {
70
- "Setup & Configuration": [],
71
- "Model Management": [],
72
- "Stack Lifecycle": [],
73
- "Diagnostics": [],
74
- }
75
-
76
- command_categories = {
77
- "setup": "Setup & Configuration",
78
- "profile": "Setup & Configuration",
79
- "config": "Setup & Configuration",
80
- "init": "Setup & Configuration",
81
- "recommend": "Model Management",
82
- "models": "Model Management",
83
- "pull": "Model Management",
84
- "up": "Stack Lifecycle",
85
- "down": "Stack Lifecycle",
86
- "status": "Stack Lifecycle",
87
- "watch": "Stack Lifecycle",
88
- "install": "Stack Lifecycle",
89
- "uninstall": "Stack Lifecycle",
90
- "bench": "Diagnostics",
91
- "logs": "Diagnostics",
92
- }
93
-
94
- for cmd_name in commands:
95
- cmd = self.get_command(ctx, cmd_name)
96
- if cmd is None:
97
- continue
98
- help_text = cmd.get_short_help_str(limit=80)
99
- category = command_categories.get(cmd_name, "Other")
100
- if category in categories:
101
- categories[category].append((cmd_name, help_text))
102
- else:
103
- categories.setdefault("Other", []).append((cmd_name, help_text))
104
-
105
- for category_name, cmds in categories.items():
106
- if not cmds:
107
- continue
108
- console_out.print(Text(f"{category_name}:", style="bold yellow"))
109
- cmd_table = Table(show_header=False, box=None, padding=(0, 2))
110
- cmd_table.add_column(style="green", min_width=20)
111
- cmd_table.add_column()
112
- for cmd_name, help_text in cmds:
113
- cmd_table.add_row(cmd_name, help_text)
114
- console_out.print(cmd_table)
115
- console_out.print()
116
-
117
- def resolve_command(
118
- self, ctx: click.Context, args: list[str]
119
- ) -> tuple[str | None, click.Command | None, list[str]]:
120
- """Override resolve_command to provide typo suggestions."""
121
- try:
122
- return super().resolve_command(ctx, args)
123
- except click.UsageError:
124
- # Get the attempted command name
125
- if args:
126
- cmd_name = args[0]
127
- available = self.list_commands(ctx)
128
- matches = difflib.get_close_matches(cmd_name, available, n=3, cutoff=0.5)
129
-
130
- error_msg = f"Error: No such command '{cmd_name}'."
131
- if matches:
132
- suggestions = ", ".join(f"'{m}'" for m in matches)
133
- error_msg += f"\n\nDid you mean one of these?\n {suggestions}"
134
- error_msg += "\n\nRun 'mlx-stack --help' for a list of available commands."
135
-
136
- console.print(f"[red]{error_msg}[/red]")
137
- ctx.exit(2)
138
- raise SystemExit(2) # noqa: B904 — we want to exit, not chain
139
- raise
140
-
141
-
142
- def version_callback(ctx: click.Context, _param: click.Parameter, value: bool) -> None:
143
- """Print version and exit."""
144
- if not value or ctx.resilient_parsing:
145
- return
146
- click.echo(f"mlx-stack, version {__version__}")
147
- ctx.exit(0)
148
-
149
-
150
- @click.group(cls=RichGroup, invoke_without_command=True)
151
- @click.option(
152
- "--version",
153
- is_flag=True,
154
- callback=version_callback,
155
- expose_value=False,
156
- is_eager=True,
157
- help="Show version and exit.",
158
- )
159
- @click.pass_context
160
- def cli(ctx: click.Context) -> None:
161
- """CLI control plane for local LLM infrastructure on Apple Silicon."""
162
- if ctx.invoked_subcommand is None:
163
- click.echo(ctx.get_help())
164
-
165
-
166
- # --- Placeholder commands for planned features ---
167
- # These will be replaced by real implementations in subsequent features.
168
-
169
-
170
- cli.add_command(setup_command, "setup")
171
- cli.add_command(profile_command, "profile")
172
- cli.add_command(recommend_command, "recommend")
173
- cli.add_command(init_command, "init")
174
-
175
-
176
- cli.add_command(pull_command, "pull")
177
- cli.add_command(models_command, "models")
178
- cli.add_command(up_command, "up")
179
- cli.add_command(down_command, "down")
180
- cli.add_command(status_command, "status")
181
-
182
-
183
- cli.add_command(watch_command, "watch")
184
- cli.add_command(install_command, "install")
185
- cli.add_command(uninstall_command, "uninstall")
186
-
187
- cli.add_command(bench_command, "bench")
188
- cli.add_command(logs_command, "logs")
189
-
190
- cli.add_command(config_group, "config")
File without changes