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