2025-12-23 16:36:39 -08:00
#!/usr/bin/env bash
# Bootstrap script for POSIX (Linux/macOS) to create a Python venv and install the project.
# Usage: scripts/bootstrap.sh [--editable] [--venv <path>] [--python <python>] [--desktop] [--no-install]
2025-12-24 02:22:18 -08:00
# Ensure script is running under Bash. Some users invoke this script with `sh` (dash)
# which does not support the Bash features used below (e.g., [[ ]], arrays, read -p).
# If not running under Bash, re-exec using a discovered bash binary.
if [ -z " ${ BASH_VERSION :- } " ] ; then
if command -v bash >/dev/null 2>& 1; then
echo " This script requires Bash; re-execing as 'bash $0 '... "
exec bash " $0 " " $@ "
else
echo " ERROR: This script requires Bash. Please run with 'bash $0 ' or install Bash. " >& 2
exit 2
fi
fi
2025-12-23 16:36:39 -08:00
set -euo pipefail
VENV_PATH = ".venv"
EDITABLE = false
DESKTOP = false
PYTHON_CMD = ""
NOINSTALL = false
FORCE = false
2025-12-24 02:13:21 -08:00
QUIET = false
2025-12-24 02:32:15 -08:00
FIX_URLLIB3 = false
2025-12-24 02:13:21 -08:00
# Playwright options
PLAYWRIGHT_BROWSERS = "chromium" # comma-separated (chromium,firefox,webkit) or 'all'
NO_PLAYWRIGHT = false
2025-12-24 02:42:13 -08:00
REMOVE_PTH = false
2025-12-23 16:36:39 -08:00
2025-12-24 02:32:15 -08:00
attempt_fix_urllib3( ) {
local venv_py = " $1 "
echo " Attempting automatic urllib3 fix in venv: $venv_py " >& 2
# Temporarily disable set -e to inspect return codes ourselves
set +e
" $venv_py " -m pip uninstall urllib3-future -y >/dev/null 2>& 1 || true
" $venv_py " -m pip install --upgrade --force-reinstall urllib3
local pip_ret = $?
# Best-effort install/update for niquests; non-fatal
" $venv_py " -m pip install niquests -U >/dev/null 2>& 1 || true
set -e
if [ [ $pip_ret -ne 0 ] ] ; then
echo " ERROR: pip failed to reinstall urllib3 (exit $pip_ret ) " >& 2
return 2
fi
# Verify fix
if " $venv_py " -c 'from SYS.env_check import check_urllib3_compat; ok,msg = check_urllib3_compat(); import sys; sys.exit(0 if ok else 2)' ; then
echo "Success: urllib3 issues resolved" >& 2
return 0
fi
echo "ERROR: urllib3 fix attempt incomplete (import check failed)" >& 2
2025-12-24 02:42:13 -08:00
# Detect potential interfering .pth files in site-packages
echo "Searching for interfering .pth files in the venv site-packages..." >& 2
local site_pkgs
site_pkgs = $( " $venv_py " - <<'PY'
import json, site, sysconfig
paths = [ ]
try:
paths.extend( site.getsitepackages( ) )
except Exception:
pass
try:
p = sysconfig.get_paths( ) .get( 'purelib' )
if p:
paths.append( p)
except Exception:
pass
seen = [ ]
out = [ ]
for s in paths:
if s and s not in seen:
seen.append( s)
out.append( s)
print( "\n" .join( out) )
PY
)
local pths = ( )
if [ [ -n " $site_pkgs " ] ] ; then
while IFS = read -r sp; do
if [ [ -d " $sp " ] ] ; then
while IFS = read -r f; do
if [ [ -n " $f " ] ] ; then
if grep -qi 'urllib3_future' " $f " >/dev/null 2>& 1 || grep -qi 'urllib3-future' " $f " >/dev/null 2>& 1; then
pths += ( " $f " )
fi
fi
done < <( find " $sp " -maxdepth 1 -type f -name '*.pth' -print 2>/dev/null)
fi
done <<< " $site_pkgs "
fi
if [ [ ${# pths [@] } -eq 0 ] ] ; then
echo "No obvious interfering .pth files found in site-packages. Manual inspection recommended." >& 2
return 3
fi
echo "Found the following potentially interfering .pth files:" >& 2
for p in " ${ pths [@] } " ; do echo " - $p " >& 2; done
2025-12-24 02:49:05 -08:00
if [ [ " $REMOVE_PTH " = = "true" || " $FIX_URLLIB3 " = = "true" ] ] ; then
echo "Removing .pth files (automatic removal due to --fix-urllib3)..." >& 2
for p in " ${ pths [@] } " ; do
rm -f " $p "
echo " Removed: $p " >& 2
done
2025-12-24 02:42:13 -08:00
else
if [ [ " $QUIET " = = "true" ] ] ; then
echo "Detected interfering .pth files but cannot prompt in quiet mode. Re-run with --remove-pth to remove them automatically." >& 2
return 3
fi
read -p "Remove these files now? (y/N) " resp
if [ [ " $resp " != "y" && " $resp " != "Y" ] ] ; then
echo "User declined to remove .pth files. Aborting." >& 2
return 3
fi
2025-12-24 02:49:05 -08:00
for p in " ${ pths [@] } " ; do
rm -f " $p "
echo " Removed: $p " >& 2
done
2025-12-24 02:42:13 -08:00
fi
# Re-run reinstall & verify
echo "Reinstalling urllib3 after .pth removal..." >& 2
set +e
" $venv_py " -m pip install --upgrade --force-reinstall urllib3
local pip_ret2 = $?
set -e
if [ [ $pip_ret2 -ne 0 ] ] ; then
echo " ERROR: pip reinstall failed after .pth removal (exit $pip_ret2 ) " >& 2
return 2
fi
if " $venv_py " -c 'from SYS.env_check import check_urllib3_compat; ok,msg = check_urllib3_compat(); import sys; sys.exit(0 if ok else 2)' ; then
echo "Success: urllib3 issues resolved after .pth removal" >& 2
return 0
fi
echo "ERROR: urllib3 still not importable after .pth removal" >& 2
2025-12-24 02:32:15 -08:00
return 3
}
usage( ) {
2025-12-23 16:36:39 -08:00
cat <<EOF
Usage: $0 [ options]
Options:
-e, --editable Install project in editable mode ( pip -e .)
-p, --venv <path> Venv path ( default: .venv)
--python <python> Python executable to use ( e.g. python3)
-d, --desktop Create a desktop launcher ( ~/.local/share/applications and ~/Desktop)
-n, --no-install Skip pip install
2025-12-24 02:13:21 -08:00
--no-playwright Skip installing Playwright browsers ( default: install chromium)
--playwright-browsers <list> Comma-separated list of browsers to install ( default: chromium)
-q, --quiet Quiet / non-interactive mode; abort on errors instead of prompting
2025-12-24 02:32:15 -08:00
-F, --fix-urllib3 Attempt to automatically fix known urllib3 issues in the venv if detected
2025-12-24 02:42:13 -08:00
-R, --remove-pth When fixing urllib3, automatically remove interfering .pth files ( like urllib3_future.pth)
2025-12-23 16:36:39 -08:00
-f, --force Overwrite existing venv without prompting
-h, --help Show this help
EOF
}
while [ [ $# -gt 0 ] ] ; do
case " $1 " in
-e| --editable) EDITABLE = true; shift; ;
-p| --venv) VENV_PATH = " $2 " ; shift 2; ;
--python) PYTHON_CMD = " $2 " ; shift 2; ;
-d| --desktop) DESKTOP = true; shift; ;
-n| --no-install) NOINSTALL = true; shift; ;
-f| --force) FORCE = true; shift; ;
2025-12-24 02:32:15 -08:00
-F| --fix-urllib3) FIX_URLLIB3 = true; shift; ;
2025-12-24 02:42:13 -08:00
-R| --remove-pth) REMOVE_PTH = true; shift; ;
2025-12-24 02:13:21 -08:00
-q| --quiet) QUIET = true; shift; ;
2025-12-23 16:36:39 -08:00
-h| --help) usage; exit 0; ;
2025-12-24 02:13:21 -08:00
--no-playwright) NO_PLAYWRIGHT = true; shift; ;
--playwright-browsers) PLAYWRIGHT_BROWSERS = " $2 " ; shift 2; ;
2025-12-23 16:36:39 -08:00
*) echo " Unknown option: $1 " ; usage; exit 1; ;
esac
done
if [ [ -n " $PYTHON_CMD " ] ] ; then
PY = " $PYTHON_CMD "
elif command -v python3 >/dev/null 2>& 1; then
PY = python3
elif command -v python >/dev/null 2>& 1; then
PY = python
else
echo "ERROR: No python executable found; install Python or pass --python <path>" >& 2
exit 2
fi
echo " Using Python: $PY "
2025-12-24 02:22:18 -08:00
# Operate from the repository root (parent of the scripts dir) so relative
# operations (like creating the venv and pip install) act on the project root
# regardless of where this script was invoked from.
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
REPO = " $( cd " $SCRIPT_DIR /.. " && pwd ) "
if ! cd " $REPO " ; then
echo " ERROR: Failed to change to repo root: $REPO " >& 2
exit 2
fi
echo " Operating from repo root: $REPO "
2025-12-23 16:36:39 -08:00
if [ [ -d " $VENV_PATH " ] ] ; then
2025-12-24 02:13:21 -08:00
# Detect whether the existing venv has a working python executable
VENV_PY = ""
for cand in " $VENV_PATH /bin/python " " $VENV_PATH /bin/python3 " " $VENV_PATH /Scripts/python.exe " ; do
if [ [ -x " $cand " ] ] ; then
VENV_PY = " $cand "
break
fi
done
2025-12-23 16:36:39 -08:00
if [ [ " $FORCE " = = "true" ] ] ; then
echo " Removing existing venv $VENV_PATH "
rm -rf " $VENV_PATH "
else
2025-12-24 02:13:21 -08:00
if [ [ -z " $VENV_PY " ] ] ; then
if [ [ " $QUIET " = = "true" ] ] ; then
echo "ERROR: Existing venv appears incomplete or broken (no python executable). Use --force to recreate." >& 2
exit 4
fi
read -p " $VENV_PATH exists but appears invalid (no python executable). Overwrite to recreate? (y/N) " REPLY
if [ [ " $REPLY " != "y" && " $REPLY " != "Y" ] ] ; then
echo "Aborted." ; exit 4
fi
rm -rf " $VENV_PATH "
else
if [ [ " $QUIET " = = "true" ] ] ; then
echo " Using existing venv at $VENV_PATH (quiet mode) "
else
read -p " $VENV_PATH already exists. Overwrite? (y/N) (default: use existing venv) " REPLY
if [ [ " $REPLY " = = "y" || " $REPLY " = = "Y" ] ] ; then
echo " Removing existing venv $VENV_PATH "
rm -rf " $VENV_PATH "
else
echo " Continuing using existing venv at $VENV_PATH "
fi
fi
2025-12-23 16:36:39 -08:00
fi
fi
fi
2025-12-24 02:13:21 -08:00
if [ [ -d " $VENV_PATH " && -n " ${ VENV_PY :- } " && -x " ${ VENV_PY :- } " ] ] ; then
echo " Using existing venv at $VENV_PATH "
else
echo " Creating venv at $VENV_PATH "
$PY -m venv " $VENV_PATH "
VENV_PY = " $VENV_PATH /bin/python "
fi
2025-12-23 16:36:39 -08:00
if [ [ ! -x " $VENV_PY " ] ] ; then
echo " ERROR: venv python not found at $VENV_PY " >& 2
exit 3
fi
2025-12-24 03:37:22 -08:00
if [ [ " $NOINSTALL " != "true" ] ] ; then # If not explicitly requested, auto-select editable install for development checkouts (no prompt)
2025-12-24 03:29:45 -08:00
if [ [ " $EDITABLE " != "true" ] ] ; then
if [ [ -d " $REPO /.git " ] ] || git -C " $REPO " rev-parse --is-inside-work-tree >/dev/null 2>& 1; then
2025-12-24 03:37:22 -08:00
EDITABLE = true
echo "Detected development checkout; performing editable install for development"
2025-12-24 03:29:45 -08:00
fi
fi
2025-12-23 16:36:39 -08:00
echo "Upgrading pip, setuptools, wheel..."
" $VENV_PY " -m pip install -U pip setuptools wheel
if [ [ " $EDITABLE " = = "true" ] ] ; then
echo "Installing project in editable mode..."
2025-12-24 02:22:18 -08:00
" $VENV_PY " -m pip install -e " $REPO "
2025-12-23 16:36:39 -08:00
else
echo "Installing project..."
2025-12-24 02:22:18 -08:00
" $VENV_PY " -m pip install " $REPO "
2025-12-23 16:36:39 -08:00
fi
# Verify the installed CLI module can be imported. This helps catch packaging
# or installation problems early (e.g., missing modules or mispackaged project).
echo "Verifying installed CLI import..."
if " $VENV_PY " -c 'import importlib; importlib.import_module("medeia_macina.cli_entry")' >/dev/null 2>& 1; then
echo "OK: 'medeia_macina.cli_entry' is importable in the venv."
2025-12-24 03:34:12 -08:00
2025-12-24 04:14:11 -08:00
# Ensure the top-level 'CLI' module is importable (required by legacy entrypoints).
# For PEP 660 editable installs, create a small .pth in the venv site-packages
# pointing at the repo root so `import CLI` works from any working directory.
if ! " $VENV_PY " -c 'import importlib; importlib.import_module("CLI")' >/dev/null 2>& 1; then
echo "Top-level 'CLI' not importable; writing venv site-packages .pth pointing at repo root..." >& 2
site_pkg_dir = $( " $VENV_PY " - <<'PY'
import os, site, sysconfig
candidates = [ ]
2025-12-24 04:05:35 -08:00
try:
2025-12-24 04:14:11 -08:00
candidates.append( sysconfig.get_paths( ) .get( 'purelib' ) )
2025-12-24 04:05:35 -08:00
except Exception:
pass
try:
2025-12-24 04:14:11 -08:00
candidates.extend( site.getsitepackages( ) )
2025-12-24 04:05:35 -08:00
except Exception:
pass
2025-12-24 04:14:11 -08:00
for p in candidates:
if p and os.path.isdir( p) :
print( p)
break
2025-12-24 04:05:35 -08:00
PY
)
2025-12-24 04:14:11 -08:00
if [ [ -z " ${ site_pkg_dir :- } " || ! -d " ${ site_pkg_dir :- } " ] ] ; then
echo "ERROR: unable to determine venv site-packages directory to write .pth; aborting." >& 2
exit 6
2025-12-24 04:05:35 -08:00
fi
2025-12-24 03:34:12 -08:00
2025-12-24 04:14:11 -08:00
pth_file = " $site_pkg_dir /medeia_repo.pth "
printf "%s\n" " $REPO " > " $pth_file "
if " $VENV_PY " -c 'import importlib; importlib.import_module("CLI")' >/dev/null 2>& 1; then
echo "OK: top-level 'CLI' is now importable (after .pth)." >& 2
2025-12-24 04:41:30 -08:00
# Also verify we can run the packaged entrypoint using module form. If this fails
# it suggests site-packages/.pth wasn't processed reliably by the interpreter.
if " $VENV_PY " -m medeia_macina.cli_entry --help >/dev/null 2>& 1; then
echo "OK: 'python -m medeia_macina.cli_entry' runs in the venv." >& 2
else
echo "ERROR: 'python -m medeia_macina.cli_entry' failed in the venv despite .pth being written; aborting." >& 2
exit 6
fi
2025-12-24 03:34:12 -08:00
else
2025-12-24 04:14:11 -08:00
echo " ERROR: top-level 'CLI' still not importable after writing .pth ( $pth_file ). " >& 2
exit 6
2025-12-24 03:34:12 -08:00
fi
fi
2025-12-23 16:36:39 -08:00
else
echo "WARNING: Could not import 'medeia_macina.cli_entry' from the venv." >& 2
# Check if legacy top-level module is present; if so, inform the user to prefer the packaged entrypoint
if " $VENV_PY " -c 'import importlib; importlib.import_module("medeia_entry")' >/dev/null 2>& 1; then
echo "Note: 'medeia_entry' top-level module is present. It's recommended to install the project so 'medeia_macina.cli_entry' is available." >& 2
else
echo " Action: Try running: $VENV_PY -m pip install -e . or inspect the venv site-packages to verify the installation. " >& 2
fi
fi
2025-12-24 02:13:21 -08:00
echo "Verifying environment for known issues (urllib3 compatibility)..."
if ! " $VENV_PY " -c 'from SYS.env_check import check_urllib3_compat; ok,msg = check_urllib3_compat(); print(msg); import sys; sys.exit(0 if ok else 2)' ; then
echo "ERROR: Bootstrap detected a potentially broken 'urllib3' installation. See message above." >& 2
echo "You can attempt to fix with:" >& 2
echo " $VENV_PY -m pip uninstall urllib3-future -y " >& 2
echo " $VENV_PY -m pip install --upgrade --force-reinstall urllib3 " >& 2
echo " $VENV_PY -m pip install niquests -U " >& 2
2025-12-24 02:32:15 -08:00
echo "" >& 2
if [ [ " $FIX_URLLIB3 " = = "true" ] ] ; then
echo "Attempting automatic fix (--fix-urllib3 requested)..." >& 2
if attempt_fix_urllib3 " $VENV_PY " ; then
echo "Success: urllib3 issues resolved; continuing..." >& 2
else
echo "ERROR: Automatic fix did not resolve the issue; inspect .pth files in site-packages (e.g., urllib3_future.pth) and remove them, then re-run with --fix-urllib3" >& 2
exit 7
fi
else
if [ [ " $QUIET " = = "true" ] ] ; then
echo "ERROR: Bootstrap detected a potentially broken 'urllib3' installation. Use --fix-urllib3 to attempt an automatic fix." >& 2
exit 7
fi
read -p "Attempt automatic fix now? (y/N) " REPLY
if [ [ " $REPLY " = = "y" || " $REPLY " = = "Y" ] ] ; then
2025-12-24 02:58:58 -08:00
AUTOFIX_INTERACTIVE = 1
2025-12-24 02:32:15 -08:00
if attempt_fix_urllib3 " $VENV_PY " ; then
echo "Success: urllib3 issues resolved; continuing..." >& 2
else
echo "ERROR: Automatic fix did not resolve the issue; aborting." >& 2
exit 7
fi
else
echo "Aborting bootstrap. Re-run with --fix-urllib3 to attempt an automatic fix or run the commands above." >& 2
exit 7
fi
fi
2025-12-24 02:13:21 -08:00
fi
# Install Playwright browsers (default: chromium) unless explicitly disabled
if [ [ " $NO_PLAYWRIGHT " != "true" && " $NOINSTALL " != "true" ] ] ; then
echo " Ensuring Playwright browsers are installed (browsers= $PLAYWRIGHT_BROWSERS )... "
# Install package if missing in venv
if ! " $VENV_PY " -c 'import importlib, sys; importlib.import_module("playwright")' >/dev/null 2>& 1; then
echo "'playwright' package not found in venv; installing via pip..."
" $VENV_PY " -m pip install playwright
fi
# Compute install behavior: 'all' means install all engines, otherwise split comma list
if [ [ " $PLAYWRIGHT_BROWSERS " = = "all" ] ] ; then
echo "Installing all Playwright browsers..."
" $VENV_PY " -m playwright install || echo "Warning: Playwright browser install failed" >& 2
else
IFS = ',' read -ra PWB <<< " $PLAYWRIGHT_BROWSERS "
for b in " ${ PWB [@] } " ; do
b_trimmed = $( echo " $b " | tr -d '[:space:]' )
if [ [ -n " $b_trimmed " ] ] ; then
echo " Installing Playwright browser: $b_trimmed "
" $VENV_PY " -m playwright install " $b_trimmed " || echo " Warning: Playwright install for $b_trimmed failed " >& 2
fi
done
fi
fi
2025-12-23 16:36:39 -08:00
else
echo "Skipping install (--no-install)"
fi
# Install Deno (official installer) - installed automatically
if command -v deno >/dev/null 2>& 1; then
echo " Deno already installed: $( deno --version | head -n 1) "
else
echo "Installing Deno via official installer (https://deno.land)..."
if command -v curl >/dev/null 2>& 1; then
curl -fsSL https://deno.land/install.sh | sh
elif command -v wget >/dev/null 2>& 1; then
wget -qO- https://deno.land/install.sh | sh
else
echo "ERROR: curl or wget is required to install Deno automatically; please install Deno manually." >& 2
fi
export DENO_INSTALL = " ${ DENO_INSTALL :- $HOME /.deno } "
export PATH = " $DENO_INSTALL /bin: $PATH "
if command -v deno >/dev/null 2>& 1; then
echo " Deno installed: $( deno --version | head -n 1) "
else
echo " Warning: Deno installer completed but 'deno' not found on PATH; add $HOME /.deno/bin to your PATH or restart your shell. " >& 2
fi
fi
if [ [ " $DESKTOP " = = "true" ] ] ; then
echo "Creating desktop launcher..."
EXEC_PATH = " $VENV_PATH /bin/mm "
if [ [ ! -x " $EXEC_PATH " ] ] ; then
# fallback to python -m
EXEC_PATH = " $VENV_PY -m medeia_macina.cli_entry "
fi
APPDIR = " $HOME /.local/share/applications "
mkdir -p " $APPDIR "
DESKTOP_FILE = " $APPDIR /medeia-macina.desktop "
cat > " $DESKTOP_FILE " <<EOF
[ Desktop Entry]
Name = Medeia-Macina
Comment = Launch Medeia-Macina
Exec = $EXEC_PATH
2025-12-24 02:13:21 -08:00
Terminal = false
2025-12-23 16:36:39 -08:00
Type = Application
Categories = Utility;
EOF
chmod +x " $DESKTOP_FILE " || true
if [ [ -d " $HOME /Desktop " ] ] ; then
cp " $DESKTOP_FILE " " $HOME /Desktop/ "
chmod +x " $HOME /Desktop/ $( basename " $DESKTOP_FILE " ) " || true
fi
echo " Desktop launcher created: $DESKTOP_FILE "
fi
# Install a global 'mm' launcher so it can be invoked from any shell.
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
REPO = " $( cd " $SCRIPT_DIR /.. " && pwd ) "
USER_BIN = " ${ XDG_BIN_HOME :- $HOME /.local/bin } "
2025-12-24 02:49:05 -08:00
# If running as root, prefer a system-wide location that is likely on PATH (/usr/local/bin)
if [ [ $( id -u) -eq 0 && -w "/usr/local/bin" ] ] ; then
USER_BIN = "/usr/local/bin"
fi
2025-12-23 16:36:39 -08:00
mkdir -p " $USER_BIN "
if [ [ -f " $USER_BIN /mm " ] ] ; then
echo " Backing up existing $USER_BIN /mm to $USER_BIN /mm.bak. $( date +%s) "
mv " $USER_BIN /mm " " $USER_BIN /mm.bak. $( date +%s) "
fi
2025-12-24 03:06:16 -08:00
cat > " $USER_BIN /mm " <<'MM'
2025-12-23 16:36:39 -08:00
#!/usr/bin/env bash
2025-12-24 03:06:16 -08:00
set -e
2025-12-24 03:50:10 -08:00
# REPO is injected at install time; try to resolve canonical project root using
# git when available to avoid mistakenly selecting parent directories.
2025-12-24 04:48:04 -08:00
# Try to locate the repo root dynamically. Use the embedded __REPO__ value as a hint,
# but prefer to discover a repo from the current working directory or git.
2025-12-24 03:09:59 -08:00
REPO = "__REPO__"
2025-12-24 04:48:04 -08:00
# If the placeholder does not appear to point at a repo, attempt discovery.
if [ ! -f " $REPO /CLI.py " ] && [ ! -f " $REPO /pyproject.toml " ] ; then
# First try to find a git toplevel from the current working directory.
if command -v git >/dev/null 2>& 1; then
2025-12-24 03:50:10 -08:00
gitroot = $( git -C " $( pwd -P) " rev-parse --show-toplevel 2>/dev/null || true )
if [ -n " $gitroot " ] ; then
REPO = " $gitroot "
2025-12-24 03:09:59 -08:00
fi
2025-12-24 03:50:10 -08:00
fi
2025-12-24 03:06:16 -08:00
fi
2025-12-24 04:48:04 -08:00
# If still unresolved, walk up from the CWD looking for signs of the project.
if [ ! -f " $REPO /CLI.py " ] && [ ! -f " $REPO /pyproject.toml " ] ; then
CUR = " $( pwd -P) "
while [ " $CUR " != "/" ] && [ " $CUR " != "" ] ; do
if [ -f " $CUR /CLI.py " ] || [ -f " $CUR /pyproject.toml " ] ; then
REPO = " $CUR "
break
fi
CUR = " $( dirname " $CUR " ) "
done
fi
# At this point REPO may still be wrong if mm was invoked outside any project; keep the embedded path as a last resort.
2025-12-24 03:09:59 -08:00
2025-12-23 16:36:39 -08:00
VENV = " $REPO /.venv "
2025-12-24 05:10:07 -08:00
# Ensure tools installed into the venv are discoverable to subprocess-based providers
export PATH = " $VENV /bin: $PATH "
2025-12-24 03:06:16 -08:00
2025-12-24 03:50:10 -08:00
# Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics
if [ -n " ${ MM_DEBUG :- } " ] ; then
echo "MM_DEBUG: diagnostics" >& 2
echo " Resolved REPO: $REPO " >& 2
echo " Resolved VENV: $VENV " >& 2
echo " VENV exists: $( [ -d " $VENV " ] && echo yes || echo no ) " >& 2
echo "Candidates:" >& 2
echo " VENV/bin/mm: $( [ -x " $VENV /bin/mm " ] && echo yes || echo no ) " >& 2
echo " VENV/bin/python3: $( [ -x " $VENV /bin/python3 " ] && echo yes || echo no ) " >& 2
echo " VENV/bin/python: $( [ -x " $VENV /bin/python " ] && echo yes || echo no ) " >& 2
echo " system python3: $( command -v python3 || echo none) " >& 2
echo " system python: $( command -v python || echo none) " >& 2
for pycmd in " $VENV /bin/python3 " " $VENV /bin/python " " $( command -v python3 2>/dev/null) " " $( command -v python 2>/dev/null) " ; do
if [ -n " $pycmd " ] && [ -x " $pycmd " ] ; then
echo " ---- Testing with: $pycmd ---- " >& 2
" $pycmd " - <<'PY'
import sys, importlib, traceback, importlib.util
print( 'sys.executable:' , sys.executable)
print( 'sys.path (first 8):' , sys.path[ :8] )
for mod in ( 'CLI' ,'medeia_macina' ,'medeia_macina.cli_entry' ) :
try:
spec = importlib.util.find_spec( mod)
print( mod, 'spec:' , spec)
if spec:
m = importlib.import_module( mod)
print( mod, 'loaded at' , getattr( m, '__file__' , None) )
except Exception:
print( mod, 'import failed' )
traceback.print_exc( )
PY
fi
done
echo "MM_DEBUG: end diagnostics" >& 2
fi
2025-12-24 04:41:30 -08:00
# Prefer venv's python3, then venv's python (module invocation - more deterministic)
2025-12-24 03:06:16 -08:00
if [ -x " $VENV /bin/python3 " ] ; then
exec " $VENV /bin/python3 " -m medeia_macina.cli_entry " $@ "
fi
if [ -x " $VENV /bin/python " ] ; then
exec " $VENV /bin/python " -m medeia_macina.cli_entry " $@ "
fi
2025-12-24 04:41:30 -08:00
# Fallback: packaged console script in the venv (older pip-generated wrapper)
if [ -x " $VENV /bin/mm " ] ; then
exec " $VENV /bin/mm " " $@ "
fi
2025-12-24 03:09:59 -08:00
# Fallback to system python3, then system python (only if it's Python 3)
2025-12-24 03:06:16 -08:00
if command -v python3 >/dev/null 2>& 1; then
exec python3 -m medeia_macina.cli_entry " $@ "
fi
if command -v python >/dev/null 2>& 1; then
if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)' ; then
exec python -m medeia_macina.cli_entry " $@ "
fi
fi
printf "Error: no suitable Python 3 interpreter found. Activate the venv with 'source %s/bin/activate' or install Python 3.\n" " $VENV " >& 2
exit 127
MM
2025-12-24 03:09:59 -08:00
# Inject absolute repo path into the installed script so global launcher prefers the project venv
2025-12-24 04:48:04 -08:00
# Replace __REPO__ placeholder robustly using Python (preferred) else fallback to sed
if command -v python >/dev/null 2>& 1; then
python - <<PY "$USER_BIN/mm" "$REP O"
import sys
fn = sys.argv[ 1] ; repo = sys.argv[ 2]
with open( fn,'r' , encoding = 'utf-8' ) as f:
s = f.read( )
s = s.replace( '__REPO__' , repo)
with open( fn,'w' , encoding = 'utf-8' ) as f:
f.write( s)
PY
else
escaped_repo = $( printf '%s' " $REPO " | sed -e 's/[\/&]/\\&/g' )
sed -i " s|__REPO__| $escaped_repo |g " " $USER_BIN /mm " || true
fi
2025-12-23 16:36:39 -08:00
chmod +x " $USER_BIN /mm "
2025-12-24 04:48:04 -08:00
# Verify injection succeeded
if grep -Fq " $REPO " " $USER_BIN /mm " ; then
echo " Installed global 'mm' launcher with REPO= $REPO "
else
echo " ERROR: failed to inject repository path into $USER_BIN /mm; inspect file: $USER_BIN /mm " >& 2
fi
2025-12-23 16:36:39 -08:00
# Quick verification of the global launcher; helps catch packaging issues early.
if " $USER_BIN /mm " --help >/dev/null 2>& 1; then
echo " Global 'mm' launcher verified: $USER_BIN /mm runs correctly. "
else
2025-12-24 02:58:58 -08:00
# Capture error output to detect permission issues
err = $( { " $USER_BIN /mm " --help >/dev/null 2>& 1 || true; } 2>& 1 || true )
echo " Warning: Global 'mm' launcher failed to run in this shell. Error: ${ err :- unknown } " >& 2
# If we detected a permission denied (e.g., filesystem mounted noexec), fall back to user-local bin
if echo " ${ err } " | grep -qi "Permission denied" || [ [ ${ err :- } = = *"Permission denied" * ] ] ; then
FALLBACK_BIN = " $HOME /.local/bin "
mkdir -p " $FALLBACK_BIN "
# Backup the problematic file and copy to fallback (use a single timestamp variable)
backup = " $USER_BIN /mm.broken. $( date +%s) "
if mv " $USER_BIN /mm " " $backup " 2>/dev/null; then
echo " Moved non-executable launcher to backup: $backup " >& 2
cp " $backup " " $FALLBACK_BIN /mm " 2>/dev/null || true
chmod +x " $FALLBACK_BIN /mm " 2>/dev/null || true
USER_BIN = " $FALLBACK_BIN "
echo " Installed launcher to fallback location: $USER_BIN /mm " >& 2
echo " Ensure ' $USER_BIN ' is on your PATH (e.g. add 'export PATH=\" $USER_BIN :\$PATH\"' to your shell rc). " >& 2
else
echo " Failed to move the launcher for fallback installation; manual inspection recommended: $USER_BIN /mm " >& 2
fi
fi
2025-12-23 16:36:39 -08:00
fi
# Ensure the user's bin is on PATH for future sessions by adding to ~/.profile if needed
if ! echo " : $PATH : " | grep -q " : $USER_BIN : " ; then
PROFILE = " $HOME /.profile "
if [ ! -f " $PROFILE " ] ; then
if [ -f " $HOME /.bash_profile " ] ; then
PROFILE = " $HOME /.bash_profile "
elif [ -f " $HOME /.bashrc " ] ; then
PROFILE = " $HOME /.bashrc "
elif [ -f " $HOME /.zshrc " ] ; then
PROFILE = " $HOME /.zshrc "
else
PROFILE = " $HOME /.profile "
fi
fi
if ! grep -q "ensure user local bin is on PATH" " $PROFILE " 2>/dev/null; then
cat >> " $PROFILE " <<PROFILE_SNIP PET
# Added by Medeia-Macina setup: ensure user local bin is on PATH
if [ -d " $HOME /.local/bin " ] && [ [ " : $PATH : " != *" : $HOME /.local/bin: " * ] ] ; then
PATH = " $HOME /.local/bin: $PATH "
fi
PROFILE_SNIPPET
echo " Added $USER_BIN export to $PROFILE ; restart your shell or source $PROFILE to use 'mm' from anywhere "
fi
fi
cat <<EOF
Bootstrap complete.
To activate the virtualenv:
source $VENV_PATH /bin/activate
To run the app:
$VENV_PATH /bin/mm # if installed as a console script
$VENV_PY -m medeia_macina.cli_entry # alternative
Global launcher installed: $USER_BIN /mm
2025-12-24 03:50:10 -08:00
If the global 'mm' launcher fails to run, collect diagnostics with MM_DEBUG = 1:
MM_DEBUG = 1 mm
2025-12-23 16:36:39 -08:00
EOF