# Strict shell for all recipes
set shell := ["bash", "-euo", "pipefail", "-c"]
# Default help
[private]
help:
just --list
# Ensure pipx is available (user-local, no sudo)
[private]
install-pipx:
#!/usr/bin/env bash
set -euo pipefail
if command -v pipx >/dev/null 2>&1; then
exit 0
fi
python3 -m pip install --user pipx
python3 -m pipx ensurepath 2>/dev/null || pipx ensurepath || true
# Install a tool via pipx if not present
[private]
install-pipx-dependency name command_name="{{name}}": install-pipx
#!/usr/bin/env bash
set -euo pipefail
if command -v {{ command_name }} >/dev/null 2>&1; then
exit 0
fi
pipx install {{ name }}
# Install Poetry if missing (prefers pipx, falls back to user pip in Docker)
install-poetry:
#!/usr/bin/env bash
set -euo pipefail
if command -v poetry >/dev/null 2>&1; then
exit 0
fi
if [ -f /.dockerenv ] && [ "$(whoami)" != "root" ]; then
python3 -m pip install --user poetry
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
exit 0
fi
just install-pipx-dependency "poetry"
# Install project dependencies inside an in-project venv
[private]
install-deps: install-poetry
#!/usr/bin/env bash
set -euo pipefail
# Load project configuration if present (contains PYTHON_PATH and USE_SPACK)
# This file is generated by apply-the-template with machine-specific settings
PYTHON_PATH=""
USE_SPACK=""
if [ -f ".project-config" ]; then
# shellcheck disable=SC1091
source ./.project-config
fi
# If USE_SPACK is set or Spack is available, and spack.yaml exists, ensure env and activate it
# Note: spack-ensure.sh will install Spack if it's not present, so we check USE_SPACK first
if [ -f "spack.yaml" ]; then
if [ "$USE_SPACK" = "true" ] || command -v spack >/dev/null 2>&1; then
bash ./scripts/spack-ensure.sh
if [ -f ".spack-activate.sh" ]; then
# shellcheck disable=SC1091
source ./.spack-activate.sh
fi
fi
fi
# Deactivate any foreign venv to avoid confusion with Poetry's .venv
if [ -n "${VIRTUAL_ENV-}" ]; then
deactivate || true
fi
# Tell Poetry which Python to use for the virtualenv
# Priority: PYTHON_PATH from config > Spack view Python > auto-detect
if [ -n "$PYTHON_PATH" ] && [ -x "$PYTHON_PATH" ]; then
echo "Configuring Poetry to use specified Python: $PYTHON_PATH"
poetry env use "$PYTHON_PATH"
elif [ -x ".spack-env/view/bin/python3" ]; then
echo "Configuring Poetry to use Spack view Python: .spack-env/view/bin/python3"
poetry env use .spack-env/view/bin/python3
fi
poetry install --with dev,test
# Install pre-commit hooks into .git/hooks (ensures hooks are fetched)
install-hooks: install-deps
#!/usr/bin/env bash
set -euo pipefail
poetry run pre-commit install --install-hooks
# Setup development environment: poetry deps + pre-commit hooks
setup: install-deps install-hooks
#!/usr/bin/env bash
set -euo pipefail
echo "Setup complete."
# Run unit tests with coverage report
test:
#!/usr/bin/env bash
set -euo pipefail
poetry run coverage run -m pytest -q
poetry run coverage report -m
# Static type checking
typecheck:
#!/usr/bin/env bash
set -euo pipefail
poetry run pyright
# Run formatting hooks (fixers that modify files)
# Shows a line before each formatter and reports only if files were modified.
format: install-deps
#!/usr/bin/env bash
set -euo pipefail
# Helper function to run a formatter with clean output
run_fmt() {
local hook_id="$1"
local extra_args="${2:-}"
echo "Running $hook_id..."
local output
output=$(poetry run pre-commit run "$hook_id" --all-files $extra_args 2>&1) || true
# Check if hook exists
if echo "$output" | grep -q "No hook with id"; then
return 0
fi
# Show files that were modified
if echo "$output" | grep -qE "(Fixing|Fixed|files were modified)"; then
echo "$output" | grep -E "^Fixing" | head -20 || true
fi
}
echo "==> Running formatting hooks"
run_fmt end-of-file-fixer
run_fmt trailing-whitespace
run_fmt mixed-line-ending
run_fmt ruff
run_fmt ruff-format
run_fmt yamlfix
run_fmt beautysh
echo "==> Formatting complete"
# Run all pre-commit hooks on all files (runs format first)
# Fails with non-zero exit code if any hook fails.
validate: format
#!/usr/bin/env bash
set -euo pipefail
echo "==> Running all pre-commit hooks"
poetry run pre-commit run --all-files
# Update pre-commit hooks to the latest versions
update-pre-commit:
#!/usr/bin/env bash
set -euo pipefail
# Use --freeze to pin to commit hashes and avoid mutable tag warnings
poetry run pre-commit autoupdate --freeze
# Build wheel package into dist/
package:
#!/usr/bin/env bash
set -euo pipefail
poetry check
poetry build -f wheel
# Clean build artifacts and temporary files
clean:
#!/usr/bin/env bash
set -euo pipefail
rm -rf build/ dist/ .pytest_cache/ .coverage* htmlcov/ .ruff_cache/ .mypy_cache/
find . -name "__pycache__" -type d -prune -exec rm -rf {} +
# Recreate virtual environment with a specific Python version
# Usage: just recreate-venv [python_version]
# Example: just recreate-venv 3.12
recreate-venv python_version="3.12":
#!/usr/bin/env bash
set -euo pipefail
echo "==> Installing Python {{ python_version }} via mise if needed..."
mise install python@{{ python_version }} || true
mise use python@{{ python_version }}
# Get the full path to the Python executable
PYTHON_PATH="$HOME/.local/share/mise/installs/python/{{ python_version }}.12/bin/python"
if [ ! -x "$PYTHON_PATH" ]; then
# Try to find the exact version
PYTHON_PATH=$(find "$HOME/.local/share/mise/installs/python" -name "python" -path "*{{ python_version }}*" -type f 2>/dev/null | head -1)
fi
if [ ! -x "$PYTHON_PATH" ]; then
echo "ERROR: Could not find Python {{ python_version }} executable"
exit 1
fi
echo "==> Using Python: $PYTHON_PATH"
$PYTHON_PATH --version
echo "==> Removing existing virtual environment..."
rm -rf .venv
rm -f poetry.lock
echo "==> Configuring Poetry to use Python {{ python_version }}..."
poetry env use "$PYTHON_PATH"
echo "==> Regenerating lock file..."
poetry lock
echo "==> Installing dependencies..."
poetry install --with dev,test
echo "==> Virtual environment recreated with Python {{ python_version }}"
# Build wheel, install into a clean venv, and smoke-test import & version
# Fails if any step fails. See ./scripts/test-package.sh for details.
test-package:
#!/usr/bin/env bash
set -euo pipefail
bash ./scripts/test-package.sh
# =============================================================================
# Shared HTTP Server Mode (for reduced CPU usage with multiple VS Code windows)
# =============================================================================
# Run MCP server with Streamable HTTP transport (shared mode)
# This allows multiple VS Code windows to connect to a single server instance,
# reducing idle CPU usage from ~10% (7 instances) to ~1.3% (1 instance).
serve-http host="127.0.0.1" port="8082":
#!/usr/bin/env bash
set -euo pipefail
echo "Starting imagen-mcp server in shared HTTP mode..."
echo "Configure VS Code to connect to: http://{{ host }}:{{ port }}/mcp"
poetry run imagen-mcp serve --transport streamable-http --host "{{ host }}" --port "{{ port }}"
# Check if MCP HTTP server is running
mcp-status port="8082":
#!/usr/bin/env bash
set -euo pipefail
if curl -s --max-time 2 "http://127.0.0.1:{{ port }}/mcp" >/dev/null 2>&1; then
echo "MCP HTTP server is running on port {{ port }}"
else
echo "MCP HTTP server is NOT running on port {{ port }}"
exit 1
fi
# Install and enable systemd user service for MCP HTTP server
# This creates a service that starts the MCP server with HTTP transport on login.
install-systemd-service:
#!/usr/bin/env bash
set -euo pipefail
# Determine the path to imagen-mcp executable
MCP_PATH=""
if command -v imagen-mcp >/dev/null 2>&1; then
MCP_PATH=$(command -v imagen-mcp)
elif [ -x "$HOME/.local/bin/imagen-mcp" ]; then
MCP_PATH="$HOME/.local/bin/imagen-mcp"
elif [ -f "$(pwd)/.venv/bin/imagen-mcp" ]; then
MCP_PATH="$(pwd)/.venv/bin/imagen-mcp"
else
echo "Error: imagen-mcp not found. Install it first with: pipx install ."
exit 1
fi
WORKING_DIR="$(pwd)"
PORT="8082"
echo "Creating systemd user service..."
echo " Working directory: $WORKING_DIR"
echo " MCP path: $MCP_PATH"
echo " Port: $PORT"
# Create service file from template
mkdir -p ~/.config/systemd/user/
sed -e "s|__WORKING_DIR__|$WORKING_DIR|g" \
-e "s|__MCP_PATH__|$MCP_PATH|g" \
-e "s|__PORT__|$PORT|g" \
scripts/imagen-mcp.service.template > ~/.config/systemd/user/imagen-mcp.service
# Reload systemd
systemctl --user daemon-reload
# Enable the service (starts on login)
systemctl --user enable imagen-mcp
# Start the service now
systemctl --user start imagen-mcp
echo ""
echo "✓ MCP systemd service installed and started!"
echo ""
echo "The server is now running at: http://127.0.0.1:$PORT/mcp"
echo ""
echo "Useful commands:"
echo " systemctl --user status imagen-mcp # Check status"
echo " systemctl --user stop imagen-mcp # Stop the server"
echo " systemctl --user restart imagen-mcp # Restart the server"
echo " journalctl --user -u imagen-mcp -f # View logs"
echo ""
echo "To use in VS Code, update your MCP configuration:"
echo ' "imagen": {'
echo ' "type": "streamable-http",'
echo " \"url\": \"http://127.0.0.1:$PORT/mcp\""
echo ' }'
# Uninstall systemd user service for MCP HTTP server
uninstall-systemd-service:
#!/usr/bin/env bash
set -euo pipefail
echo "Stopping and disabling MCP systemd service..."
# Stop the service if running
systemctl --user stop imagen-mcp 2>/dev/null || true
# Disable the service
systemctl --user disable imagen-mcp 2>/dev/null || true
# Remove the service file
rm -f ~/.config/systemd/user/imagen-mcp.service
# Reload systemd
systemctl --user daemon-reload
echo "✓ MCP systemd service uninstalled."