2025-12-17 14:17:46 -08:00
#!/usr/bin/env python3
""" scripts/setup.py
Unified project setup helper ( Python - only ) .
This script installs Python dependencies from ` requirements . txt ` and then
downloads Playwright browser binaries by running ` python - m playwright install ` .
2025-12-17 17:42:46 -08:00
By default this script installs * * Chromium * * only to conserve space ; pass
` - - browsers all ` to install all supported engines ( chromium , firefox , webkit ) .
2025-12-17 14:17:46 -08:00
Usage :
python . / scripts / setup . py # install deps and playwright browsers
python . / scripts / setup . py - - skip - deps
python . / scripts / setup . py - - playwright - only
Optional flags :
- - skip - deps Skip ` pip install - r requirements . txt ` step
- - no - playwright Skip running ` python - m playwright install ` ( still installs deps )
- - playwright - only Install only Playwright browsers ( installs playwright package if missing )
2025-12-17 17:42:46 -08:00
- - browsers Comma - separated list of Playwright browsers to install ( default : chromium )
- - install - editable Install the project in editable mode ( pip install - e . ) for running tests
- - install - deno Install the Deno runtime using the official installer
- - deno - version Pin a specific Deno version to install ( e . g . , v1 .34 .3 )
2025-12-17 14:17:46 -08:00
- - upgrade - pip Upgrade pip , setuptools , and wheel before installing deps
"""
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
2025-12-17 17:42:46 -08:00
import platform
import shutil
2025-12-23 16:36:39 -08:00
import os
import time
2025-12-17 14:17:46 -08:00
def run ( cmd : list [ str ] ) - > None :
print ( f " > { ' ' . join ( cmd ) } " )
subprocess . check_call ( cmd )
def playwright_package_installed ( ) - > bool :
try :
import playwright # type: ignore
return True
except Exception :
return False
2025-12-17 17:42:46 -08:00
def _build_playwright_install_cmd ( browsers : str | None ) - > list [ str ] :
""" Return the command to install Playwright browsers.
- If browsers is None or empty : default to install Chromium only .
- If browsers contains ' all ' : install all engines by running ' playwright install ' with no extra args .
- Otherwise , validate entries and return a command that installs the named engines .
"""
base = [ sys . executable , " -m " , " playwright " , " install " ]
if not browsers :
return base + [ " chromium " ]
items = [ b . strip ( ) . lower ( ) for b in browsers . split ( " , " ) if b . strip ( ) ]
if not items :
return base + [ " chromium " ]
if " all " in items :
return base
allowed = { " chromium " , " firefox " , " webkit " }
invalid = [ b for b in items if b not in allowed ]
if invalid :
raise ValueError ( f " invalid browsers specified: { invalid } . Valid choices: chromium, firefox, webkit, or ' all ' " )
return base + items
def _install_deno ( version : str | None = None ) - > int :
""" Install Deno runtime for the current platform.
Uses the official Deno install scripts :
- Unix / macOS : curl - fsSL https : / / deno . land / x / install / install . sh | sh [ - s < version > ]
- Windows : powershell iwr https : / / deno . land / x / install / install . ps1 - useb | iex ; Install - Deno [ - Version < version > ]
Returns exit code 0 on success , non - zero otherwise .
"""
system = platform . system ( ) . lower ( )
try :
if system == " windows " :
# Use official PowerShell installer
if version :
ver = version if version . startswith ( " v " ) else f " v { version } "
ps_cmd = f " iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno -Version { ver } "
else :
ps_cmd = " iwr https://deno.land/x/install/install.ps1 -useb | iex "
run ( [ " powershell " , " -NoProfile " , " -ExecutionPolicy " , " Bypass " , " -Command " , ps_cmd ] )
else :
# POSIX: use curl + sh installer
if version :
ver = version if version . startswith ( " v " ) else f " v { version } "
cmd = f " curl -fsSL https://deno.land/x/install/install.sh | sh -s { ver } "
else :
cmd = " curl -fsSL https://deno.land/x/install/install.sh | sh "
run ( [ " sh " , " -c " , cmd ] )
# Check that 'deno' is now available in PATH
if shutil . which ( " deno " ) :
print ( f " Deno installed at: { shutil . which ( ' deno ' ) } " )
return 0
else :
print ( " Deno installation completed but ' deno ' not found in PATH. You may need to add Deno ' s bin directory to your PATH manually. " , file = sys . stderr )
return 1
except subprocess . CalledProcessError as exc :
print ( f " Deno install failed: { exc } " , file = sys . stderr )
return int ( exc . returncode or 1 )
2025-12-17 14:17:46 -08:00
def main ( ) - > int :
parser = argparse . ArgumentParser ( description = " Setup Medios-Macina: install deps and Playwright browsers " )
parser . add_argument ( " --skip-deps " , action = " store_true " , help = " Skip installing Python dependencies from requirements.txt " )
parser . add_argument ( " --no-playwright " , action = " store_true " , help = " Skip running ' playwright install ' (only install packages) " )
parser . add_argument ( " --playwright-only " , action = " store_true " , help = " Only run ' playwright install ' (skips dependency installation) " )
2025-12-17 17:42:46 -08:00
parser . add_argument ( " --browsers " , type = str , default = " chromium " , help = " Comma-separated list of browsers to install: chromium,firefox,webkit or ' all ' (default: chromium) " )
parser . add_argument ( " --install-editable " , action = " store_true " , help = " Install the project in editable mode (pip install -e .) for running tests " )
deno_group = parser . add_mutually_exclusive_group ( )
deno_group . add_argument ( " --install-deno " , action = " store_true " , help = " Install the Deno runtime (default behavior; kept for explicitness) " )
deno_group . add_argument ( " --no-deno " , action = " store_true " , help = " Skip installing Deno runtime (opt out) " )
parser . add_argument ( " --deno-version " , type = str , default = None , help = " Specific Deno version to install (e.g., v1.34.3) " )
2025-12-17 14:17:46 -08:00
parser . add_argument ( " --upgrade-pip " , action = " store_true " , help = " Upgrade pip/setuptools/wheel before installing requirements " )
args = parser . parse_args ( )
repo_root = Path ( __file__ ) . resolve ( ) . parent . parent
if sys . version_info < ( 3 , 8 ) :
print ( " Warning: Python 3.8+ is recommended. " , file = sys . stderr )
2025-12-23 16:36:39 -08:00
# Opinionated: always create or use a local venv at the project root (.venv)
venv_dir = repo_root / " .venv "
def _venv_python ( p : Path ) - > Path :
if platform . system ( ) . lower ( ) == " windows " :
return p / " Scripts " / " python.exe "
return p / " bin " / " python "
def _ensure_local_venv ( ) - > Path :
""" Create (if missing) and return the path to the venv ' s python executable.
This is intentionally opinionated : we keep a venv at ` . / . venv ` in the repo root
and use that for all package operations to keep developer environments reproducible .
"""
try :
if not venv_dir . exists ( ) :
print ( f " Creating local virtualenv at: { venv_dir } " )
run ( [ sys . executable , " -m " , " venv " , str ( venv_dir ) ] )
else :
print ( f " Using existing virtualenv at: { venv_dir } " )
py = _venv_python ( venv_dir )
if not py . exists ( ) :
# Try recreating venv if python is missing
print ( f " Local venv python not found at { py } ; recreating venv " )
run ( [ sys . executable , " -m " , " venv " , str ( venv_dir ) ] )
py = _venv_python ( venv_dir )
if not py . exists ( ) :
raise RuntimeError ( f " Unable to locate venv python at { py } " )
return py
except subprocess . CalledProcessError as exc :
print ( f " Failed to create or prepare local venv: { exc } " , file = sys . stderr )
raise
# Ensure a local venv is present and use it for subsequent installs.
venv_python = _ensure_local_venv ( )
print ( f " Using venv python: { venv_python } " )
# Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode.
# Ignore `--skip-deps` and `--install-editable` flags to keep the setup deterministic.
args . skip_deps = False
args . install_editable = True
args . no_playwright = False
2025-12-17 14:17:46 -08:00
try :
if args . playwright_only :
if not playwright_package_installed ( ) :
print ( " ' playwright ' package not found; installing it via pip... " )
run ( [ sys . executable , " -m " , " pip " , " install " , " playwright " ] )
print ( " Installing Playwright browsers (this may download several hundred MB)... " )
2025-12-17 17:42:46 -08:00
try :
cmd = _build_playwright_install_cmd ( args . browsers )
except ValueError as exc :
print ( f " Error: { exc } " , file = sys . stderr )
return 2
run ( cmd )
2025-12-17 14:17:46 -08:00
print ( " Playwright browsers installed successfully. " )
return 0
if args . upgrade_pip :
2025-12-23 16:36:39 -08:00
print ( " Upgrading pip, setuptools, and wheel in local venv... " )
run ( [ str ( venv_python ) , " -m " , " pip " , " install " , " --upgrade " , " pip " , " setuptools " , " wheel " ] )
2025-12-17 14:17:46 -08:00
if not args . skip_deps :
req_file = repo_root / " requirements.txt "
if not req_file . exists ( ) :
print ( f " requirements.txt not found at { req_file } ; skipping dependency installation. " , file = sys . stderr )
else :
2025-12-23 16:36:39 -08:00
print ( f " Installing Python dependencies into local venv from { req_file } ... " )
run ( [ str ( venv_python ) , " -m " , " pip " , " install " , " -r " , str ( req_file ) ] )
2025-12-17 14:17:46 -08:00
if not args . no_playwright :
if not playwright_package_installed ( ) :
2025-12-23 16:36:39 -08:00
print ( " ' playwright ' package not installed in venv; installing it... " )
run ( [ str ( venv_python ) , " -m " , " pip " , " install " , " playwright " ] )
2025-12-17 14:17:46 -08:00
print ( " Installing Playwright browsers (this may download several hundred MB)... " )
2025-12-17 17:42:46 -08:00
try :
cmd = _build_playwright_install_cmd ( args . browsers )
except ValueError as exc :
print ( f " Error: { exc } " , file = sys . stderr )
return 2
2025-12-23 16:36:39 -08:00
# Run Playwright install using the venv's python so binaries are available in venv
cmd [ 0 ] = str ( venv_python )
2025-12-17 17:42:46 -08:00
run ( cmd )
# Optional: install the project in editable mode so tests can import the package
2025-12-23 16:36:39 -08:00
# Install the project into the local venv (editable mode is the default, opinionated)
print ( " Installing project into local venv (editable mode) " )
run ( [ str ( venv_python ) , " -m " , " pip " , " install " , " -e " , " . " ] )
2025-12-17 17:42:46 -08:00
# Optional: install Deno runtime (default: install unless --no-deno is passed)
install_deno_requested = True
if getattr ( args , " no_deno " , False ) :
install_deno_requested = False
elif getattr ( args , " install_deno " , False ) :
install_deno_requested = True
if install_deno_requested :
2025-12-23 16:36:39 -08:00
print ( " Installing Deno runtime (local/system)... " )
2025-12-17 17:42:46 -08:00
rc = _install_deno ( args . deno_version )
if rc != 0 :
print ( " Deno installation failed. " , file = sys . stderr )
return rc
2025-12-17 14:17:46 -08:00
2025-12-23 16:36:39 -08:00
# Write project-local launcher scripts (project root) that prefer the local .venv
def _write_launchers ( ) :
sh = repo_root / " mm "
ps1 = repo_root / " mm.ps1 "
bat = repo_root / " mm.bat "
sh_text = """ #!/usr/bin/env bash
set - e
SCRIPT_DIR = " $(cd " $ ( dirname " $ {BASH_SOURCE[0]} " ) " && pwd) "
2025-12-24 02:13:21 -08:00
REPO = " $SCRIPT_DIR "
VENV = " $REPO/.venv "
PY = " $VENV/bin/python "
CLI_SCRIPT = " $REPO/CLI.py "
if [ - x " $PY " ] ; then
exec " $PY " " $CLI_SCRIPT " " $@ "
2025-12-23 16:36:39 -08:00
else
2025-12-24 02:13:21 -08:00
exec python " $CLI_SCRIPT " " $@ "
2025-12-23 16:36:39 -08:00
fi
"""
try :
sh . write_text ( sh_text , encoding = " utf-8 " )
sh . chmod ( sh . stat ( ) . st_mode | 0o111 )
except Exception :
pass
ps1_text = r """ Param([Parameter(ValueFromRemainingArguments=$true)] $args)
$ scriptDir = Split - Path - Parent $ MyInvocation . MyCommand . Path
2025-12-24 02:13:21 -08:00
$ repo = $ scriptDir
$ venv = Join - Path $ repo ' .venv '
2025-12-23 16:36:39 -08:00
$ py = Join - Path $ venv ' Scripts \ python.exe '
2025-12-24 02:13:21 -08:00
$ cli = Join - Path $ repo ' CLI.py '
if ( Test - Path $ py ) { & $ py $ cli @args ; exit $ LASTEXITCODE }
if ( Test - Path $ cli ) { & $ py $ cli @args ; exit $ LASTEXITCODE }
2025-12-23 16:36:39 -08:00
# fallback
2025-12-24 02:13:21 -08:00
python $ cli @args
2025-12-23 16:36:39 -08:00
"""
try :
ps1 . write_text ( ps1_text , encoding = " utf-8 " )
except Exception :
pass
bat_text = (
" @echo off \r \n "
" set SCRIPT_DIR= % ~dp0 \r \n "
2025-12-24 02:13:21 -08:00
" if exist \" % SCRIPT_DIR % \\ .venv \\ Scripts \\ python.exe \" \" % SCRIPT_DIR % \\ .venv \\ Scripts \\ python.exe \" \" % SCRIPT_DIR % \\ CLI.py \" % * \r \n "
" if exist \" % SCRIPT_DIR % \\ CLI.py \" python \" % SCRIPT_DIR % \\ CLI.py \" % * \r \n "
" python -m medeia_macina.cli_entry % * \r \n "
2025-12-23 16:36:39 -08:00
)
try :
bat . write_text ( bat_text , encoding = " utf-8 " )
except Exception :
pass
_write_launchers ( )
# Install user-global shims so `mm` can be executed from any shell session.
def _install_user_shims ( repo : Path ) - > None :
try :
home = Path . home ( )
system = platform . system ( ) . lower ( )
if system == " windows " :
user_bin = Path ( os . environ . get ( " USERPROFILE " , str ( home ) ) ) / " bin "
user_bin . mkdir ( parents = True , exist_ok = True )
# Write mm.cmd (CMD shim)
mm_cmd = user_bin / " mm.cmd "
cmd_text = (
f " @echo off \r \n "
f " set REPO= { repo } \r \n "
f " if exist \" %REPO% \\ .venv \\ Scripts \\ mm.exe \" \" %REPO% \\ .venv \\ Scripts \\ mm.exe \" %* \r \n "
2025-12-24 03:50:10 -08:00
f " if defined MM_DEBUG ( \r \n "
f " echo MM_DEBUG: REPO=%REPO% \r \n "
f " if exist \" %REPO% \\ .venv \\ Scripts \\ python.exe \" \" %REPO% \\ .venv \\ Scripts \\ python.exe \" -c \" import sys,importlib,importlib.util; print( ' sys.executable: ' , sys.executable); print( ' sys.path (first 8): ' , sys.path[:8]); \" \r \n "
f " ) \r \n "
f " if exist \" %REPO% \\ .venv \\ Scripts \\ python.exe \" \" %REPO% \\ .venv \\ Scripts \\ python.exe \" -m medeia_macina.cli_entry %* \r \n "
f " python -m medeia_macina.cli_entry %* \r \n "
2025-12-23 16:36:39 -08:00
)
if mm_cmd . exists ( ) :
bak = mm_cmd . with_suffix ( f " .bak { int ( time . time ( ) ) } " )
mm_cmd . replace ( bak )
mm_cmd . write_text ( cmd_text , encoding = " utf-8 " )
# Write mm.ps1 (PowerShell shim)
mm_ps1 = user_bin / " mm.ps1 "
ps1_text = (
" Param([Parameter(ValueFromRemainingArguments=$true)] $args) \n "
f " $repo = \" { repo } \" \n "
" $venv = Join-Path $repo ' .venv ' \n "
" $exe = Join-Path $venv ' Scripts \\ mm.exe ' \n "
" if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE } \n "
" $py = Join-Path $venv ' Scripts \\ python.exe ' \n "
2025-12-24 03:50:10 -08:00
" if (Test-Path $py) { \n "
" if ($env:MM_DEBUG) { \n "
" Write-Host \" MM_DEBUG: diagnostics \" -ForegroundColor Yellow \n "
" & $py -c \" import sys,importlib,importlib.util,traceback; print( ' sys.executable: ' , sys.executable); print( ' sys.path (first 8): ' , sys.path[:8]); \" \n "
" } \n "
" & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE \n "
" } \n "
" python -m medeia_macina.cli_entry @args \n "
2025-12-23 16:36:39 -08:00
)
if mm_ps1 . exists ( ) :
bak = mm_ps1 . with_suffix ( f " .bak { int ( time . time ( ) ) } " )
mm_ps1 . replace ( bak )
mm_ps1 . write_text ( ps1_text , encoding = " utf-8 " )
# Attempt to add user_bin to the user's PATH if it's not present.
try :
cur = os . environ . get ( " PATH " , " " )
str_bin = str ( user_bin )
if str_bin not in cur :
ps_cmd = (
" $bin = ' {bin} ' ; "
" $cur = [Environment]::GetEnvironmentVariable( ' PATH ' , ' User ' ); "
" if ($cur -notlike \" *$bin* \" ) { [Environment]::SetEnvironmentVariable( ' PATH ' , ($bin + ' ; ' + ($cur -ne $null ? $cur : ' ' )), ' User ' )} "
) . format ( bin = str_bin . replace ( ' \\ ' , ' \\ \\ ' ) )
subprocess . run ( [ " powershell " , " -NoProfile " , " -Command " , ps_cmd ] , check = False )
except Exception :
pass
print ( f " Installed global launchers to: { user_bin } " )
else :
# POSIX
user_bin = Path ( os . environ . get ( " XDG_BIN_HOME " , str ( home / " .local/bin " ) ) )
user_bin . mkdir ( parents = True , exist_ok = True )
mm_sh = user_bin / " mm "
sh_text = (
" #!/usr/bin/env bash \n "
2025-12-24 03:06:16 -08:00
" set -e \n "
2025-12-24 03:09:59 -08:00
f " REPO= \" { repo } \" \n "
2025-12-24 03:50:10 -08:00
" # Prefer git top-level when available to avoid embedding a parent path. \n "
" if command -v git >/dev/null 2>&1; then \n "
" gitroot=$(git -C \" $REPO \" rev-parse --show-toplevel 2>/dev/null || true) \n "
" if [ -n \" $gitroot \" ]; then \n "
" REPO= \" $gitroot \" \n "
" fi \n "
" fi \n "
" # If git not available or didn ' t resolve, walk up from CWD to find a project root. \n "
2025-12-24 03:14:30 -08:00
" if [ ! -f \" $REPO/CLI.py \" ] && [ ! -f \" $REPO/pyproject.toml \" ]; then \n "
2025-12-24 03:09:59 -08:00
" CUR= \" $(pwd -P) \" \n "
" while [ \" $CUR \" != \" / \" ] && [ \" $CUR \" != \" \" ]; do \n "
2025-12-24 03:14:30 -08:00
" if [ -f \" $CUR/CLI.py \" ] || [ -f \" $CUR/pyproject.toml \" ]; then \n "
2025-12-24 03:09:59 -08:00
" REPO= \" $CUR \" \n "
" break \n "
" fi \n "
" CUR= \" $(dirname \" $CUR \" ) \" \n "
" done \n "
2025-12-24 03:06:16 -08:00
" fi \n "
" VENV= \" $REPO/.venv \" \n "
2025-12-24 03:50:10 -08:00
" # Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics \n "
" if [ -n \" $ {MM_DEBUG:-} \" ]; then \n "
" echo \" MM_DEBUG: diagnostics \" >&2 \n "
" echo \" Resolved REPO: $REPO \" >&2 \n "
" echo \" Resolved VENV: $VENV \" >&2 \n "
" echo \" VENV exists: $( [ -d \" $VENV \" ] && echo yes || echo no ) \" >&2 \n "
" echo \" Candidates: \" >&2 \n "
" echo \" VENV/bin/mm: $( [ -x \" $VENV/bin/mm \" ] && echo yes || echo no ) \" >&2 \n "
" echo \" VENV/bin/python3: $( [ -x \" $VENV/bin/python3 \" ] && echo yes || echo no ) \" >&2 \n "
" echo \" VENV/bin/python: $( [ -x \" $VENV/bin/python \" ] && echo yes || echo no ) \" >&2 \n "
" echo \" system python3: $(command -v python3 || echo none) \" >&2 \n "
" echo \" system python: $(command -v python || echo none) \" >&2 \n "
" for pycmd in \" $VENV/bin/python3 \" \" $VENV/bin/python \" \" $(command -v python3 2>/dev/null) \" \" $(command -v python 2>/dev/null) \" ; do \n "
" if [ -n \" $pycmd \" ] && [ -x \" $pycmd \" ]; then \n "
" echo \" ---- Testing with: $pycmd ---- \" >&2 \n "
" $pycmd - << ' PY ' \n import sys, importlib, traceback, importlib.util \n print( ' sys.executable: ' , sys.executable) \n print( ' sys.path (first 8): ' , sys.path[:8]) \n for mod in ( ' CLI ' , ' medeia_macina ' , ' medeia_macina.cli_entry ' ): \n try: \n spec = importlib.util.find_spec(mod) \n print(mod, ' spec: ' , spec) \n if spec: \n m = importlib.import_module(mod) \n print(mod, ' loaded at ' , getattr(m, ' __file__ ' , None)) \n except Exception: \n print(mod, ' import failed ' ) \n traceback.print_exc() \n PY \n "
" fi \n "
" done \n "
" echo \" MM_DEBUG: end diagnostics \" >&2 \n "
" fi \n "
" # Packaged console script in the venv if available \n "
2025-12-23 16:36:39 -08:00
" if [ -x \" $VENV/bin/mm \" ]; then \n "
" exec \" $VENV/bin/mm \" \" $@ \" \n "
2025-12-24 03:06:16 -08:00
" fi \n "
2025-12-24 03:50:10 -08:00
" # Prefer venv ' s python3, then venv ' s python \n "
2025-12-24 03:06:16 -08:00
" if [ -x \" $VENV/bin/python3 \" ]; then \n "
" exec \" $VENV/bin/python3 \" -m medeia_macina.cli_entry \" $@ \" \n "
" fi \n "
" if [ -x \" $VENV/bin/python \" ]; then \n "
2025-12-24 03:03:36 -08:00
" exec \" $VENV/bin/python \" -m medeia_macina.cli_entry \" $@ \" \n "
2025-12-24 03:06:16 -08:00
" fi \n "
2025-12-24 03:50:10 -08:00
" # Fallback to system python3, then system python (only if it ' s Python 3) \n "
2025-12-24 03:06:16 -08:00
" if command -v python3 >/dev/null 2>&1; then \n "
2025-12-24 03:03:36 -08:00
" exec python3 -m medeia_macina.cli_entry \" $@ \" \n "
2025-12-23 16:36:39 -08:00
" fi \n "
2025-12-24 03:06:16 -08:00
" if command -v python >/dev/null 2>&1; then \n "
" if python -c ' import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1) ' ; then \n "
" exec python -m medeia_macina.cli_entry \" $@ \" \n "
" fi \n "
" fi \n "
" echo ' Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv. ' >&2 \n "
" exit 127 \n "
2025-12-23 16:36:39 -08:00
)
if mm_sh . exists ( ) :
bak = mm_sh . with_suffix ( f " .bak { int ( time . time ( ) ) } " )
mm_sh . replace ( bak )
mm_sh . write_text ( sh_text , encoding = " utf-8 " )
mm_sh . chmod ( mm_sh . stat ( ) . st_mode | 0o111 )
# Ensure the user's bin is on PATH for future sessions by adding to ~/.profile
cur_path = os . environ . get ( " PATH " , " " )
if str ( user_bin ) not in cur_path :
profile = home / " .profile "
snippet = (
" # Added by Medeia-Macina setup: ensure user local bin is on PATH \n "
" if [ -d \" $HOME/.local/bin \" ] && [[ \" :$PATH: \" != * \" :$HOME/.local/bin: \" * ]]; then \n "
" PATH= \" $HOME/.local/bin:$PATH \" \n "
" fi \n "
)
try :
txt = profile . read_text ( ) if profile . exists ( ) else " "
if snippet . strip ( ) not in txt :
with profile . open ( " a " , encoding = " utf-8 " ) as fh :
fh . write ( " \n " + snippet )
except Exception :
pass
print ( f " Installed global launcher to: { mm_sh } " )
except Exception as exc : # pragma: no cover - best effort
print ( f " Failed to install global shims: { exc } " , file = sys . stderr )
_install_user_shims ( repo_root )
2025-12-17 14:17:46 -08:00
print ( " Setup complete. " )
return 0
except subprocess . CalledProcessError as exc :
print ( f " Error: command failed with exit { exc . returncode } : { exc } " , file = sys . stderr )
return int ( exc . returncode or 1 )
except Exception as exc : # pragma: no cover - defensive
print ( f " Unexpected error: { exc } " , file = sys . stderr )
return 2
if __name__ == " __main__ " :
raise SystemExit ( main ( ) )