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.
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/ci.yml +3 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/integration-nightly.yml +3 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/integration-prerelease.yml +3 -0
- mlx_stack-0.3.7/.release-please-manifest.json +3 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CHANGELOG.md +24 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/PKG-INFO +44 -17
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/README.md +43 -16
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/_version.py +2 -2
- mlx_stack-0.3.7/src/mlx_stack/cli/main.py +300 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli.py +29 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_ops_cross_area.py +12 -4
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/uv.lock +3 -3
- mlx_stack-0.3.5/.release-please-manifest.json +0 -3
- mlx_stack-0.3.5/src/mlx_stack/cli/main.py +0 -190
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/init.sh +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/architecture.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/environment.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/library/user-testing.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/services.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/settings.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/skills/cli-feature/SKILL.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/configuration-management.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/dependency-management.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-catalog-errors-and-families.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-deps-binary-and-ansi.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/fix-scaffolding-data-home.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/hardware-detection.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/model-catalog.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/reviews/project-scaffolding.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-basic.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-config-deps.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-profile-catalog.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/flows/foundation-setup-profile-core.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/foundation/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/down-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-preflight-and-readonly.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-process-robustness.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/fix-lifecycle-typecheck.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/process-management.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/status-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/reviews/up-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g1-deps-up-basics.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g2-up-startup.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g3-up-resilience.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g4-down.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g5-status.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r1-g6-cross.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g1-fixes.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/flows/r2-g2-cross-blockers.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/lifecycle/user-testing/synthesis.round1.json +0 -0
- {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
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/reviews/misc-cross-area-validation.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/scrutiny/synthesis.round1.json +0 -0
- {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
- {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
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/misc-cross-area/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-lint-errors.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-scrutiny-issues.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/fix-ops-typecheck-errors.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/launchd-integration.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/log-rotation.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/logs-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/ops-cross-area-validation.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/reviews/watchdog-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g1-log.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g2-logs-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g3-watch.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g4-launchd.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/flows/g5-cross-ops.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/ops/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/community-docs.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/developing-guide.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/fix-public-ready-scrutiny.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/github-actions-ci.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/reviews/readme-rewrite.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/public-ready/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-init-and-models-issues.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-recommendation-scoring-issues.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/fix-scoring-lint.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/init-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/models-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/recommend-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/reviews/scoring-engine.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g1-recommend-budget-ranking.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g2-recommend-output-integration.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g3-init-core-routing.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g4-init-cloud-overwrite.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g5-init-hardware-summary.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g6-models-local.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/g7-models-catalog.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g1-recommend.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g2-models-catalog-filters.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/flows/r2-g3-cross-012.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/recommendation/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/bench-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/fix-tooling-scrutiny-issues.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/reviews/pull-command.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/scrutiny/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g1-pull-core.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g2-pull-errors.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g3-bench-core.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/g4-bench-advanced.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g1-pull.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r2-g2-bench.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g1-pull.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r3-g2-bench.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/flows/r4-g1-bench.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round1.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round2.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.factory/validation/tooling/user-testing/synthesis.round3.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/release.yml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/publish.yml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.github/workflows/release-please.yml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/.gitignore +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CODE_OF_CONDUCT.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/CONTRIBUTING.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/DEVELOPING.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/LICENSE +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/Makefile +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/SECURITY.md +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/pyproject.toml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/release-please-config.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/bench.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/config.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/down.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/init.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/install.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/logs.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/models.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/profile.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/pull.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/recommend.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/setup.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/status.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/up.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/cli/watch.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/benchmark.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/catalog.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/config.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/deps.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/discovery.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/hardware.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/launchd.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/litellm_gen.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/log_rotation.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/log_viewer.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/models.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/onboarding.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/paths.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/process.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/pull.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/scoring.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_down.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_init.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_status.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/stack_up.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/core/watchdog.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/benchmark_data.json +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-32b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/deepseek-r1-8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-12b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-27b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/gemma3-4b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/llama3.3-8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-49b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/nemotron-8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3-8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-0.8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-14b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-32b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-3b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-72b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/data/catalog/qwen3.5-8b.yaml +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/py.typed +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/src/mlx_stack/utils/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/conftest.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/factories.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/fakes.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/conftest.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/report.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_catalog_validation.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_harness_compatibility.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_inference_e2e.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_launchd_e2e.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_model_smoke.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/integration/test_stack_integration.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/__init__.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/conftest.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_benchmark.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_catalog.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_bench.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_config.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_down.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_init.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_install.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_logs.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_models.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_profile.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_pull.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_recommend.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_setup.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_status.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_up.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cli_watch.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_config.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_cross_area.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_data_dir.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_deps.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_discovery.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_hardware.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_launchd.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_lifecycle_fixes.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_litellm_gen.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_log_rotation.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_log_viewer.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_models.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_onboarding.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_paths.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_process.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_robustness_fixes.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_scoring.py +0 -0
- {mlx_stack-0.3.5 → mlx_stack-0.3.7}/tests/unit/test_watchdog.py +0 -0
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
446
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
417
|
-
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
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
|
-
|
|
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.
|
|
366
|
+
version = "2.20.0"
|
|
367
367
|
source = { registry = "https://pypi.org/simple" }
|
|
368
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
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/
|
|
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]]
|