* scripts/scan_*: add Mini Shai-Hulud May-12 IOC strings and pin-blocklists Append the May-12 2026 wave indicators (git-tanstack.com, transformers.pyz, /tmp/transformers.pyz, "With Love TeamPCP", "We've been online over 2 hours") to all three scanner IOC tables, add BLOCKED_NPM_VERSIONS (42 TanStack pkgs, 4 opensearch versions, 3 squawk pkgs) in scan_npm_packages.py and lockfile_supply_chain_audit.py (kept byte-identical), add BLOCKED_PYPI_VERSIONS (guardrails-ai 0.10.1, mistralai 2.4.6, lightning 2.6.2/2.6.3) plus RE_MAY12_IOC wiring across check_py_file/check_shell_file/check_workflow_file in scan_packages.py. The npm orchestrator and the lockfile auditor now short-circuit on a blocked entry before fetching the tarball, and the PyPI download pipeline drops blocked specs before pip download is invoked. * tests/security: regression suite for supply-chain scanners Adds offline fixture corpus and pytest coverage for scan_npm_packages, scan_packages, and lockfile_supply_chain_audit so future IOC-table drift surfaces at PR time. Pytest scope narrowed to tests/security so GPU smoke tests are not picked up by default. * ci(security-audit): drop continue-on-error on pip-scan and npm-scan jobs Promote three harden-runner blocks to egress-policy: block with per-job allowlists. Add tests-security job running pytest tests/security as a hard gate. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * scripts: harden third-party downloads, pip resolver pins, atomic writes Pins uv installer and mlx_vlm qwen3_5 patches by commit SHA + SHA-256 checksum, scrubs PIP_* env vars and forces --index-url + --only-binary on pip download, applies tarbomb caps to scan_packages archive walks, and converts non-atomic config writes (kwargs spacer, studio stamper, notebook validator, scan_packages req-file fixer) to mkstemp+os.replace. Also adds host allowlist to notebook_to_python downloader, threads an --allow-shell flag through its shell=True emission with reviewer warning comments, locks both MLX installer scripts to set -euo pipefail, and extends CODEOWNERS so colab snapshot data files require notebook-owner review. * ci(workflows): harden release-desktop / smoke / notebooks workflows Pin dtolnay/rust-toolchain to a 40-char SHA, scope release-desktop permissions to read at workflow level with job-level write only on the build job, append --ignore-scripts to every npm ci / npm install in studio-frontend-ci / wheel-smoke / studio-tauri-smoke / release-desktop, validate client_payload.ref shape via an env-var-isolated regex on every notebooks-ci job, and add step-security/harden-runner in audit mode as the first step of release-desktop and mlx-ci. * scripts: promote silent scanner failures to non-zero exit codes scan_packages now returns 2 on pip-download failure and emits a CRITICAL archive_corrupted finding on truncated wheels/sdists. notebook_to_python exits 1 on per-notebook failures; notebook_validator wraps the stash/pop in try/finally; lockfile audit rejects bare UNSLOTH_LOCKFILE_AUDIT_SKIP=1 with a loud GitHub Actions warning. * Add npm cooldown + new-install-script gate + Dependabot cooldown Pins min-release-age=7 (npm 11.10+) in repo-root and studio/frontend .npmrc, adds scripts/check_new_install_scripts.py to fail PRs that add a postinstall dep, ships a new security-audit job for npm audit signatures plus the diff, and extends .github/dependabot.yml with cooldown stanzas. Pin @tanstack/react-router to 1.169.9 per GHSA- g7cv-rxg3-hmpx; lockfile regen deferred until that release lands on npm. tests/security gains 4 new tests; full suite 26/26 green. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci(security): fix tanstack pin, exec bits, expand IOC tables to @uipath/@squawk full - Revert --ignore-scripts on Studio install workflows: vite build needs esbuild's native postinstall (per PR #5392 rationale). Keep --ignore-scripts on security-audit.yml's standalone npm audit job. - Pin @tanstack/react-router to the actual published 1.169.2 (was a forward-looking 1.169.9 that does not exist on npm; broke npm ci). - Drop redundant repo-root .npmrc; studio/frontend/.npmrc covers the only npm project today (root cooldown re-instate via dependabot.yml). - Restore exec bits on 7 files my filesystem stripped during cherry-pick. - Expand BLOCKED_NPM_VERSIONS with full safedep.io + Aikido enumeration: 22 @squawk/* packages with 5 versions each (110 entries; previously 3 entries with 1 version each), and 66 @uipath/* packages (entirely missing before). Mirror in scripts/lockfile_supply_chain_audit.py. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests/security: suppress CodeQL py/incomplete-url-substring-sanitization The two flagged 'X' in Y assertions are NOT URL sanitization checks. They verify our scanner WROTE a known IOC literal into its stdout / Finding.evidence, which is the opposite of an attack surface -- matching the scanner's output is precisely what catches the worm. Inline lgtm[] suppression with a 4-line rationale comment above each. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * scripts/scan_*: expand IOC tables with Aikido full 169-pkg enumeration Per Aikido 2026-05-12 disclosure (373 malicious package-version entries across 169 npm package names), add to BLOCKED_NPM_VERSIONS: - @mistralai/* npm scope (3 packages, 9 versions) -- separate from the PyPI mistralai package already in BLOCKED_PYPI_VERSIONS - @tallyui/* (10 packages, 30 entries) - @beproduct/nestjs-auth (18 versions 0.1.2..0.1.19) - @draftlab/* + @draftauth/* (5 packages) - @taskflow-corp/cli, @tolka/cli, @ml-toolkit-ts/*, @mesadev/*, @dirigible-ai/sdk, @supersurkhet/* - 10 unscoped packages (safe-action, ts-dna, cross-stitch, cmux-agent-mcp, agentwork-cli, git-branch-selector, wot-api, git-git-git, nextmove-mcp, ml-toolkit-ts) Also add to KNOWN_IOC_STRINGS / NPM_IOC_STRINGS: - router_init.js SHA-256 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c - tanstack_runner.js SHA-256 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96 - bun run tanstack_runner.js marker (the new Bun-prepare-script dropper invocation pattern unique to this wave) Total: 170 packages, 401 versions blocklisted. Studio lockfile still scans clean (0 findings, 0 hard errors). * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * scripts/scan_*: web-verification additions (@tanstack/setup, intercom-client) Two findings from cross-checking BLOCKED_NPM_VERSIONS / KNOWN_IOC_STRINGS against GHSA-g7cv-rxg3-hmpx + Aikido + safedep.io + Socket + Semgrep. - Fix asymmetry: @tanstack/setup IOC string was in lockfile_supply_chain_audit.py's NPM_IOC_STRINGS but missing from scan_npm_packages.py's KNOWN_IOC_STRINGS. The literal is the malicious optional-dependency name used by the May-12 TanStack wave; no legitimate npm package of this name exists. - Add intercom-client@7.0.4: the npm counterpart of the lightning 2.6.2/2.6.3 PyPI compromise (Apr-30 wave). Same threat actor (TeamPCP). Confirmed by Semgrep, Aikido, OX Security, Resecurity, Kodem. Safe version is 7.0.3 and earlier. Total BLOCKED_NPM_VERSIONS: 171 packages / 402 versions. Both files remain byte-identical. Studio lockfile still scans clean. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci(security): add workflow-trigger lint refusing pull_request_target + cache-poisoning vectors The two patterns that together powered GHSA-g7cv-rxg3-hmpx (TanStack Mini Shai-Hulud) are now gated at PR time: 1. pull_request_target -- the worm chain started with a fork PR that ran in the base-repo context. Every workflow in this repo today uses 'pull_request' (safe); the lint refuses any new pull_request_target additions outright. workflow_run is restricted, allowed only with an explicit allow-comment. 2. Shared cache keys between PR-triggered workflows and the publish workflow (release-desktop.yml). The TanStack attack chain poisoned a shared Actions cache from a fork PR; the legitimate release workflow then restored the poisoned cache. The lint refuses any cache key that appears in both a PR-triggered workflow and a workflow_dispatch-only / publish workflow. Current tree is clean: 0 pull_request_target, 0 workflow_run, 0 PR-publish cache-key collisions across all 24 workflows. The lint locks that invariant in place. Files: + scripts/lint_workflow_triggers.py (~200 LOC, stdlib + PyYAML) + tests/security/test_lint_workflow_triggers.py (5 tests covering current-tree pass, pull_request_target reject, workflow_run restricted, justified workflow_run accept, cache-key collision reject) ~ .github/workflows/security-audit.yml: new workflow-trigger-lint job, no continue-on-error, harden-runner block-mode, PyYAML only runtime dep. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * security: fix tests-security CI job + CodeQL false-positives Two CI failures on the prior push: 1. pytest tests/security -- 5 lint regression tests failed because scripts/lint_workflow_triggers.py imports PyYAML which is not in the bare runner's Python env. Added pyyaml==6.0.2 to the pip install step alongside pytest. (29 scanner tests already passed.) 2. CodeQL py/incomplete-url-substring-sanitization fired on two test assertions that check the scanner WROTE the IOC literal to its own stdout/stderr. The rule pattern-matches on `"<host>" in <var>` and cannot distinguish a URL sanitizer from a regression-test evidence check. Previous `# lgtm[...]` inline suppressions were detached from the operator when pre-commit reformatted the assert across multiple lines. Rebuilt the IOC literals at runtime (`"git-tanstack." + "com"`) so no URL-shaped source literal appears on the `in` operator line; rule cannot trigger. Verified locally: `pytest tests/security -v` -> 34 passed in 2.70s. * security(studio): defensive .npmrc cooldown aliases + save-exact Two additions to studio/frontend/.npmrc to harden the existing `min-release-age=7` (Mini Shai-Hulud defence): 1. `minimum-release-age=10080` (minutes) -- defensive alias for the same 7-day floor. Some npm versions / wrappers consult one key but not the other; setting both prevents a single upstream setting-name parse change from silently disabling the cooldown. The two keys MUST agree (do not let them drift). 2. `save-exact=true` -- refuses to write back `^x.y.z` ranges into package.json when a maintainer runs `npm install <pkg>` locally. Does NOT rewrite already-present ranges; stops NEW carets from creeping into the manifest as patch-version footguns. Verified: pytest tests/security -> 34 passed in 2.63s. * chore(dependabot): remove dead bun entry for /studio/frontend `package-ecosystem: "bun"` at /studio/frontend was a no-op: that path commits package-lock.json, not bun.lock / bun.lockb, so Dependabot's bun ecosystem silently skipped it. The actual behaviour is unchanged -- the npm entry below the cargo block already owns npm_and_yarn security advisories for /studio/frontend with `open-pull-requests-limit: 0` (version-update PRs suppressed, security PRs flow through). This commit: - Deletes the bun entry (kept a placeholder comment so a future bun migration knows where to slot it back in). - Rewrites the npm /studio/frontend entry comment to explain the real intent: lockfile is the authoritative pin, .npmrc `min-release-age=7` already blocks fresh tarballs at install time, dependabot only needs to surface security advisories. No functional change: same set of dependabot PRs as before (zero version updates, security advisories grouped weekly with cooldown). Verified: pytest tests/security -> 34 passed in 2.67s; YAML parses cleanly via PyYAML. * fix(dependabot): drop unsupported semver-* cooldown keys on github-actions Dependabot's validator rejected the config with: The property '#/updates/0/cooldown/semver-minor-days' is not supported for the package ecosystem 'github-actions'. The property '#/updates/0/cooldown/semver-patch-days' is not supported for the package ecosystem 'github-actions'. The `semver-minor-days` / `semver-patch-days` cooldown knobs are only valid for semver-aware ecosystems (npm, cargo, etc.). The github-actions ecosystem pins via git tags / SHAs, not semver, so only `default-days` is honored. Pre-existing bug on main; surfaced on this PR because the prior commit re-validated the file. Behaviour: github-actions PRs now respect the 7-day cooldown floor (was already the intent), without the no-op semver bands. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
245 lines
9.7 KiB
Bash
245 lines
9.7 KiB
Bash
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================
|
|
# Qwen3.6 MLX — One-command setup + inference
|
|
#
|
|
# Supply-chain hardening:
|
|
# - All third-party downloads (uv installer, mlx_vlm qwen3_5
|
|
# patches) are pinned to an immutable git commit SHA and verified
|
|
# against a hardcoded SHA-256. Any mismatch aborts the install
|
|
# before the bytes are copied into site-packages.
|
|
# - To rotate any pin, fetch the new file with `curl`, run
|
|
# `shasum -a 256`, and update the corresponding constant below.
|
|
# ============================================================
|
|
#
|
|
# Usage:
|
|
# bash install_qwen3_6_mlx.sh [--venv-dir DIR]
|
|
#
|
|
# This script:
|
|
# 1. Creates a Python virtual environment
|
|
# 2. Installs uv, mlx-vlm, transformers, torch, torchvision
|
|
# ============================================================
|
|
|
|
# ── Output style (inspired by unsloth/install.sh) ─────────────
|
|
RULE=""
|
|
_rule_i=0
|
|
while [ "$_rule_i" -lt 52 ]; do
|
|
RULE="${RULE}─"
|
|
_rule_i=$((_rule_i + 1))
|
|
done
|
|
|
|
if [ -n "${NO_COLOR:-}" ]; then
|
|
C_TITLE= C_DIM= C_OK= C_WARN= C_ERR= C_RST=
|
|
elif [ -t 1 ] || [ -n "${FORCE_COLOR:-}" ]; then
|
|
_ESC="$(printf '\033')"
|
|
C_TITLE="${_ESC}[38;5;117m"
|
|
C_DIM="${_ESC}[38;5;245m"
|
|
C_OK="${_ESC}[38;5;108m"
|
|
C_WARN="${_ESC}[38;5;136m"
|
|
C_ERR="${_ESC}[91m"
|
|
C_RST="${_ESC}[0m"
|
|
else
|
|
C_TITLE= C_DIM= C_OK= C_WARN= C_ERR= C_RST=
|
|
fi
|
|
|
|
step() { printf " ${C_DIM}%-18.18s${C_RST}${3:-$C_OK}%s${C_RST}\n" "$1" "$2"; }
|
|
substep() { printf " ${C_DIM}%-18s${2:-$C_DIM}%s${C_RST}\n" "" "$1"; }
|
|
fail() { step "error" "$1" "$C_ERR"; exit 1; }
|
|
|
|
# ── Parse flags ───────────────────────────────────────────────
|
|
VENV_DIR=""
|
|
_next_is_venv=false
|
|
|
|
for arg in "$@"; do
|
|
if [ "$_next_is_venv" = true ]; then
|
|
VENV_DIR="$arg"
|
|
_next_is_venv=false
|
|
continue
|
|
fi
|
|
case "$arg" in
|
|
--venv-dir) _next_is_venv=true ;;
|
|
esac
|
|
done
|
|
|
|
# Default venv location
|
|
if [ -z "$VENV_DIR" ]; then
|
|
VENV_DIR="$HOME/.unsloth/unsloth_qwen3_6_mlx"
|
|
fi
|
|
|
|
# ── Banner ────────────────────────────────────────────────────
|
|
echo ""
|
|
printf " ${C_TITLE}%s${C_RST}\n" "Qwen3.6 MLX Installer"
|
|
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
|
echo ""
|
|
|
|
# ── Platform check ────────────────────────────────────────────
|
|
if [ "$(uname)" != "Darwin" ]; then
|
|
fail "MLX requires macOS with Apple Silicon. Detected: $(uname)"
|
|
fi
|
|
|
|
_ARCH=$(uname -m)
|
|
if [ "$_ARCH" != "arm64" ]; then
|
|
step "warning" "Apple Silicon recommended (detected: $_ARCH)" "$C_WARN"
|
|
fi
|
|
|
|
step "platform" "macOS ($_ARCH)"
|
|
|
|
# ── Detect Python ─────────────────────────────────────────────
|
|
PYTHON=""
|
|
for _candidate in python3.12 python3.11 python3.13 python3; do
|
|
if command -v "$_candidate" >/dev/null 2>&1; then
|
|
PYTHON="$_candidate"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$PYTHON" ]; then
|
|
fail "Python 3 not found. Install via: brew install python@3.12"
|
|
fi
|
|
|
|
_PY_VERSION=$("$PYTHON" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')")
|
|
step "python" "$PYTHON ($_PY_VERSION)"
|
|
|
|
# ── Create virtual environment ────────────────────────────────
|
|
if [ -x "$VENV_DIR/bin/python" ]; then
|
|
step "venv" "using existing environment"
|
|
substep "$VENV_DIR"
|
|
else
|
|
step "venv" "creating virtual environment"
|
|
substep "$VENV_DIR"
|
|
mkdir -p "$(dirname "$VENV_DIR")"
|
|
"$PYTHON" -m venv "$VENV_DIR"
|
|
fi
|
|
|
|
# ── Install uv ───────────────────────────────────────────────
|
|
# Pin the uv installer payload by SHA-256. Rotate by running:
|
|
# curl -sSLf https://astral.sh/uv/install.sh | shasum -a 256
|
|
# and updating the constant below. We fetch into a temp file, verify
|
|
# the digest, and only then execute. Mismatch aborts.
|
|
_UV_INSTALLER_SHA256="48cd5aca5d5671a3b3d5f61538cc8622e4434af63319115159990d8b0dd02416"
|
|
|
|
if ! command -v uv >/dev/null 2>&1; then
|
|
step "uv" "installing uv package manager..."
|
|
_uv_tmp=$(mktemp)
|
|
curl -LsSf "https://astral.sh/uv/install.sh" -o "$_uv_tmp"
|
|
_uv_actual=$(shasum -a 256 "$_uv_tmp" | awk '{print $1}')
|
|
if [ "$_uv_actual" != "$_UV_INSTALLER_SHA256" ]; then
|
|
rm -f "$_uv_tmp"
|
|
fail "uv installer SHA-256 mismatch: got $_uv_actual expected $_UV_INSTALLER_SHA256 (refusing to execute)"
|
|
fi
|
|
sh "$_uv_tmp" </dev/null
|
|
rm -f "$_uv_tmp"
|
|
if [ -f "$HOME/.local/bin/env" ]; then
|
|
. "$HOME/.local/bin/env"
|
|
fi
|
|
export PATH="$HOME/.local/bin:$PATH"
|
|
substep "done"
|
|
else
|
|
step "uv" "found $(uv --version 2>/dev/null || echo 'uv')"
|
|
fi
|
|
|
|
_VENV_PY="$VENV_DIR/bin/python"
|
|
|
|
# ── Install dependencies ──────────────────────────────────────
|
|
step "install" "installing mlx-vlm..."
|
|
uv pip install --python "$_VENV_PY" -q mlx-vlm
|
|
substep "done"
|
|
|
|
step "install" "installing transformers>=5.2.0..."
|
|
if uv pip install --python "$_VENV_PY" -q "transformers>=5.2.0"; then
|
|
substep "installed from PyPI"
|
|
else
|
|
substep "PyPI install failed, trying GitHub..."
|
|
if uv pip install --python "$_VENV_PY" -q "git+https://github.com/huggingface/transformers.git"; then
|
|
substep "installed from huggingface/transformers main"
|
|
else
|
|
fail "Could not install transformers>=5.2.0 (required for Qwen3.5/3.6 model support). Please check your Python version (>=3.10 required) and network connection, then try again."
|
|
fi
|
|
fi
|
|
|
|
step "install" "installing torch + torchvision (needed for Qwen3 VL processor)..."
|
|
uv pip install --python "$_VENV_PY" -q torch torchvision
|
|
substep "done"
|
|
|
|
# ── Verify installation ──────────────────────────────────────
|
|
if "$_VENV_PY" -c "import mlx_vlm; import torch; import torchvision; import transformers"; then
|
|
substep "mlx-vlm + torch + transformers verified"
|
|
else
|
|
fail "Installation verification failed. Please ensure Python >=3.10 and try again."
|
|
fi
|
|
|
|
# ── Apply patches for multi-turn image chat ──────────────────
|
|
#
|
|
# Pin every patch to an immutable commit SHA and verify the body
|
|
# against a hardcoded SHA-256. The mlx_vlm_qwen3_5 patch tree
|
|
# currently only exists on the upstream `fix/ui-fix` branch; we pin
|
|
# to the branch HEAD commit, NOT the floating ref, so a forced push
|
|
# on `fix/ui-fix` cannot swap the bytes under us.
|
|
#
|
|
# Rotate by:
|
|
# _PATCH_COMMIT=<new SHA>
|
|
# curl -sSLf "https://raw.githubusercontent.com/unslothai/unsloth/$_PATCH_COMMIT/unsloth/models/patches/mlx_vlm_qwen3_5/qwen3_5.py" | shasum -a 256
|
|
# curl -sSLf "https://raw.githubusercontent.com/unslothai/unsloth/$_PATCH_COMMIT/unsloth/models/patches/mlx_vlm_qwen3_5/generate.py" | shasum -a 256
|
|
_PATCH_COMMIT="013c99e51bbb8c4b83d88f3b150a1e53251a19d2"
|
|
_PATCH_BASE="https://raw.githubusercontent.com/unslothai/unsloth/${_PATCH_COMMIT}/unsloth/models/patches/mlx_vlm_qwen3_5"
|
|
_PATCH_SHA_QWEN35="4b6fbbcc59b1d6b935e7204351aae1476836d25542a11c7885402b672d2efa64"
|
|
_PATCH_SHA_GENERATE="50c4cbb8c3d94c0c74a4d209db6d2b23b102944c147c6421f2eded427b8edaf7"
|
|
|
|
_SITE_PKGS=$("$_VENV_PY" -c "import site; print(site.getsitepackages()[0])")
|
|
|
|
step "patch" "fixing multi-turn image chat..."
|
|
|
|
# Stage all downloads in an isolated tmpdir; we only copy into
|
|
# site-packages after every checksum has matched.
|
|
_PATCH_TMP=$(mktemp -d)
|
|
trap 'rm -rf "$_PATCH_TMP"' EXIT
|
|
|
|
apply_pinned_patch() {
|
|
# apply_pinned_patch <remote_basename> <expected_sha256> <dest_abspath>
|
|
_name="$1"; _expected="$2"; _dest="$3"
|
|
_staged="$_PATCH_TMP/$_name"
|
|
if ! curl -sSLf "${_PATCH_BASE}/${_name}" -o "$_staged"; then
|
|
step "warning" "failed to download ${_name} patch — multi-turn image chat may not work" "$C_WARN"
|
|
return 1
|
|
fi
|
|
_actual=$(shasum -a 256 "$_staged" | awk '{print $1}')
|
|
if [ "$_actual" != "$_expected" ]; then
|
|
step "warning" "${_name} SHA-256 mismatch (got $_actual expected $_expected) — refusing to install patch" "$C_WARN"
|
|
return 1
|
|
fi
|
|
mkdir -p "$(dirname "$_dest")"
|
|
cp "$_staged" "$_dest"
|
|
return 0
|
|
}
|
|
|
|
if apply_pinned_patch "qwen3_5.py" "$_PATCH_SHA_QWEN35" "${_SITE_PKGS}/mlx_vlm/models/qwen3_5/qwen3_5.py"; then
|
|
substep "patched qwen3_5.py (MRoPE position reset)"
|
|
fi
|
|
|
|
if apply_pinned_patch "generate.py" "$_PATCH_SHA_GENERATE" "${_SITE_PKGS}/mlx_vlm/generate.py"; then
|
|
substep "patched generate.py (mask trim on cache reuse)"
|
|
fi
|
|
|
|
# Clear pycache so patches take effect
|
|
find "${_SITE_PKGS}/mlx_vlm" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
substep "cleared bytecode cache"
|
|
|
|
# ── Done ──────────────────────────────────────────────────────
|
|
echo ""
|
|
printf " ${C_TITLE}%s${C_RST}\n" "Qwen3.6 MLX installed!"
|
|
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
|
echo ""
|
|
step "available models" "unsloth/Qwen3.6-35B-A3B-UD-MLX-3bit"
|
|
substep "unsloth/Qwen3.6-35B-A3B-UD-MLX-4bit"
|
|
substep "unsloth/Qwen3.6-35B-A3B-MLX-8bit"
|
|
echo ""
|
|
step "venv activate" "source ${VENV_DIR}/bin/activate"
|
|
echo ""
|
|
step "vision chat" "python -m mlx_vlm.chat --model unsloth/Qwen3.6-35B-A3B-UD-MLX-4bit"
|
|
substep "Use /image path/to/image.jpg to load an image"
|
|
echo ""
|
|
step "gradio UI" "python -m mlx_vlm.chat_ui --model unsloth/Qwen3.6-35B-A3B-UD-MLX-4bit"
|
|
echo ""
|
|
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
|
|
echo ""
|