Files
passepartout/literate/setup.org

13 KiB

Setup & Onboarding (setup.org)

Overview: The Zero-to-One Experience

The Setup & Onboarding process ensures that users can boot the org-agent Lisp Machine with zero friction. We follow the Appliance Paradigm for standard users (Docker-first) and provide a Power User Path (Baremetal) for those wanting deep native integration.

This file is a Literate Devops document. Tangling it generates the Docker configuration and the unified entrypoint script (org-agent.sh).

1. The Appliance Paradigm (Docker First)

The easiest way to run the agent is via Docker. This prevents the user from having to manually manage SBCL, Quicklisp, Python virtual environments, Playwright binaries, and Java (for Signal).

The Dockerfile

The container wraps all messy OS dependencies and pre-caches the Lisp environment for rapid booting.

# ORG-AGENT v1.0 Production Environment
FROM debian:bookworm-slim

# Prevent interactive prompts during build
ENV DEBIAN_FRONTEND=noninteractive

# 1. Install System Dependencies
# - sbcl: The Lisp Runtime
# - curl/git/unzip: Standard tools for Quicklisp and binaries
# - default-jre: Required by signal-cli
# - python3/pip: Required for Playwright bridge
# - socat: Required for stateful CLI interaction
RUN apt-get update && apt-get install -y \
    sbcl \
    curl \
    git \
    unzip \
    default-jre \
    libsqlite3-0 \
    python3 \
    python3-pip \
    python3-venv \
    emacs-nox \
    socat \
    && rm -rf /var/lib/apt/lists/*

# 2. Setup Playwright (High-Fidelity Browsing)
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install playwright \
    && playwright install --with-deps chromium

# 3. Install signal-cli (v0.14.0)
ENV SIGNAL_CLI_VERSION=0.14.0
RUN curl -L https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux.tar.gz | tar xz -C /opt \
    && ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/local/bin/signal-cli

# 4. Install Quicklisp & Pin Distribution
# Pinned to 2026-04-01 for bit-rot resistance.
WORKDIR /root
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
    && sbcl --non-interactive \
        --load quicklisp.lisp \
        --eval '(quicklisp-quickstart:install)' \
        --eval '(ql-dist:install-dist "http://beta.quicklisp.org/dist/quicklisp/2026-04-01/distinfo.txt" :prompt nil :replace t)'

# 5. Configure SBCL to load Quicklisp on startup
RUN echo '(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init)))' > /root/.sbclrc

# 6. Setup Application Directory
WORKDIR /app
COPY . /app/projects/org-agent

# 7. Pre-cache Lisp Dependencies
RUN sbcl --non-interactive \
    --eval '(push #p"/app/projects/org-agent/" asdf:*central-registry*)' \
    --eval '(ql:quickload :org-agent)'

# 8. Environment & Volumes
# The host's memex root should be mounted to /memex
ENV MEMEX_DIR=/memex
VOLUME ["/memex"]

# Default Ports
EXPOSE 9105 8080

# Entrypoint
CMD ["sbcl", "--non-interactive", \
     "--eval", "(push #p\"/app/projects/org-agent/\" asdf:*central-registry*)", \
     "--eval", "(ql:quickload :org-agent)", \
     "--eval", "(org-agent:main)"]

Docker Compose

services:
  org-agent:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: org-agent
    env_file: .env
    volumes:
      # Mount the entire memex directory (2 levels up from projects/org-agent)
      - ../..:/memex
      # Ensure signal-cli state is preserved
      - signal-state:/root/.local/share/signal-cli
    ports:
      - "${ORG_AGENT_DAEMON_PORT:-9105}:9105"
      - "${ORG_AGENT_WEB_PORT:-8080}:8080"
    restart: unless-stopped

volumes:
  signal-state:

2. The Unified Entrypoint (org-agent.sh)

We combine the installation script, the daemon launcher, and the CLI chat client into a single, elegant bash script.

If the agent is running, it connects to the chat. If it's installed but offline, it boots the daemon. If it's not installed at all, it walks the user through the onboarding wizard.

set -e

PORT=9105
HOST=${1:-localhost}

RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
NC='\033[0m'

command_exists() { command -v "$1" >/dev/null 2>&1; }

# 1. Try to drop straight into the CLI chat
if command_exists socat && socat - TCP:$HOST:$PORT,connect-timeout=1 2>/dev/null; then
    echo -e "${BLUE}Connected to autonomous brain at $HOST:$PORT...${NC}"
    # Use socat with READLINE for history and arrow-key support.
    # It establishes a persistent bidirectional connection.
    socat READLINE,history=$HOME/.org_agent_history TCP:$HOST:$PORT
    exit 0
elif command_exists nc && nc -z $HOST $PORT 2>/dev/null; then
    echo -e "${YELLOW}socat not found. Falling back to nc (no line-editing).${NC}"
    echo -e "${BLUE}Connected to autonomous brain at $HOST:$PORT...${NC}"
    while true; do
        read -p "User: " MESSAGE
        if [ -z "$MESSAGE" ]; then continue; fi
        echo "$MESSAGE" | nc -N $HOST $PORT
    done
    exit 0
fi

# 2. Check if we have an existing installation we can boot
if [ -f "$HOME/.org-agent-path" ]; then
    INSTALL_DIR=$(cat "$HOME/.org-agent-path")
    if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/docker-compose.yml" ]; then
        echo -e "${YELLOW}Daemon is offline. Booting from $INSTALL_DIR...${NC}"
        cd "$INSTALL_DIR"
        docker-compose up -d
        echo "Waiting for brain to initialize..."
        sleep 5
        # Re-run to enter chat
        exec "$0" "$@"
    fi
fi

# 3. If we are running this inside a cloned repo, configure and boot
if [ -f "docker-compose.yml" ] && [ -d "literate" ]; then
    echo -e "${YELLOW}Local repository detected. Ensuring configuration...${NC}"
    INSTALL_DIR=$(pwd)
    echo "$INSTALL_DIR" > "$HOME/.org-agent-path"
    
    if [ ! -f .env ]; then
        cp .env.example .env
        read -p "What is your name? (default: User): " USER_NAME
        USER_NAME=${USER_NAME:-User}
        sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env

        read -p "What shall we name your Assistant? (default: Agent): " AGENT_NAME
        AGENT_NAME=${AGENT_NAME:-Agent}
        sed -i "s/MEMEX_ASSISTANT=.*/MEMEX_ASSISTANT=\"$AGENT_NAME\"/g" .env

        echo -e "\nSelect your primary neural provider:"
        echo "1) Google Gemini (Free Tier / Official)"
        echo "2) OpenRouter (Unified / Paid)"
        echo "3) Anthropic (Claude / API Key)"
        echo "4) OpenAI (GPT / API Key)"
        read -p "Choice [1-4]: " LLM_CHOICE

        case $LLM_CHOICE in
            2) read -p "Enter OpenRouter API Key: " INPUT; sed -i "s/OPENROUTER_API_KEY=.*/OPENROUTER_API_KEY=\"$INPUT\"/g" .env ;;
            3) read -p "Enter Anthropic API Key: " INPUT; sed -i "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=\"$INPUT\"/g" .env ;;
            4) read -p "Enter OpenAI API Key: " INPUT; sed -i "s/OPENAI_API_KEY=.*/OPENAI_API_KEY=\"$INPUT\"/g" .env ;;
            *) read -p "Enter Gemini API Key: " INPUT; sed -i "s/GEMINI_API_KEY=.*/GEMINI_API_KEY=\"$INPUT\"/g" .env ;;
        esac

        # Seed Core Skills
        echo -e "\n${BLUE}Seeding Skills...${NC}"
        MEMEX_TARGET=$(dirname $(dirname "$INSTALL_DIR"))
        SKILLS_DIR="$MEMEX_TARGET/notes"
        mkdir -p "$SKILLS_DIR"
        cp -n skills/*.org "$SKILLS_DIR/" 2>/dev/null || true
        echo -e "${GREEN}✓ Core skills seeded to $SKILLS_DIR.${NC}"
    fi
    
    docker-compose up -d --build
    echo "Waiting for brain to initialize..."
    sleep 5
    exec "$0" "$@"
fi

# 4. Zero-to-One Onboarding (No installation found)
echo -e "${BLUE}==================================================${NC}"
echo -e "${BLUE} org-agent: Autonomous Intelligence Onboarding     ${NC}"
echo -e "${BLUE}==================================================${NC}"

# --- OS & Docker Detection ---
echo -e "\n${BLUE}[1/2] Verifying Environment...${NC}"

install_docker() {
    echo -e "${YELLOW}Docker is required to run org-agent natively without messy dependencies.${NC}"
    read -p "Would you like me to attempt to install Docker? [Y/n]: " install_choice
    install_choice=${install_choice:-Y}
    if [[ "$install_choice" =~ ^[Yy]$ ]]; then
        if [[ "$OSTYPE" == "linux-gnu"* ]]; then
            if command_exists apt-get; then
                echo "Installing Docker via apt..."
                sudo apt-get update
                sudo apt-get install -y docker.io docker-compose
            elif command_exists dnf; then
                echo "Installing Docker via dnf..."
                sudo dnf install -y docker docker-compose
                sudo systemctl start docker
                sudo systemctl enable docker
            else
                echo -e "${RED}Unsupported package manager. Please install Docker manually.${NC}"
                exit 1
            fi
        elif [[ "$OSTYPE" == "darwin"* ]]; then
            if command_exists brew; then
                echo "Installing Docker Desktop via Homebrew..."
                brew install --cask docker
                echo -e "${YELLOW}Please start Docker Desktop from your Applications folder, then re-run this script.${NC}"
                exit 0
            else
                echo -e "${RED}Homebrew not found. Please install Docker Desktop for Mac manually.${NC}"
                exit 1
            fi
        else
            echo -e "${RED}Unsupported OS for automated Docker installation. Please install manually.${NC}"
            exit 1
        fi
    else
        echo -e "${RED}Docker is required. Aborting.${NC}"
        exit 1
    fi
}

if ! command_exists docker || ! command_exists docker-compose; then
    install_docker
else
    echo -e "${GREEN}✓ Docker and docker-compose detected.${NC}"
fi

# --- Repository Setup ---
echo -e "\n${BLUE}[2/2] Downloading Kernel...${NC}"
MEMEX_DEFAULT="$HOME/memex"
read -p "Where is your Memex located? (default: $MEMEX_DEFAULT): " MEMEX_TARGET
MEMEX_TARGET=${MEMEX_TARGET:-$MEMEX_DEFAULT}

mkdir -p "$MEMEX_TARGET/projects"
cd "$MEMEX_TARGET/projects"

if [ ! -d "org-agent" ]; then
    echo "Cloning org-agent..."
    git clone https://github.com/gharbeia/org-agent.git
    cd org-agent
else
    echo -e "${GREEN}✓ Repository already exists.${NC}"
    cd org-agent
    git pull origin main
fi

mkdir -p "$HOME/.local/bin"
ln -sf "$(pwd)/org-agent.sh" "$HOME/.local/bin/org-agent"
echo -e "${GREEN}✓ Installed 'org-agent' command to ~/.local/bin${NC}"

# Ensure proper ownership if sudo was used for apt
if [ -n "$SUDO_USER" ]; then
    chown -R "$SUDO_USER" "$MEMEX_TARGET/projects/org-agent"
fi

# Execute the newly cloned script to run configuration (Step 3)
exec ./org-agent.sh

3. The Power-User Path (Baremetal Onboarding)

For users who want to run the Lisp Machine natively on their host OS (typically Emacs users who want the agent to directly manipulate their local .emacs.d), we provide the baremetal setup script. This script verifies the host has SBCL and Quicklisp installed, and configures the paths natively.

set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; NC='\033[0m'

echo -e "${BLUE}=== org-agent: Baremetal Power-User Setup ===${NC}"

if ! command -v sbcl >/dev/null 2>&1; then
    echo -e "${RED}✗ SBCL not found. Please install it first.${NC}"
    exit 1
fi

if [ ! -d "$HOME/quicklisp" ] && [ ! -d "$HOME/.quicklisp" ]; then
    echo -e "${RED}✗ Quicklisp not found. Please install Quicklisp.${NC}"
    exit 1
fi

if [ ! -f .env ]; then cp .env.example .env; fi

read -p "What is your name? (default: User): " USER_NAME
USER_NAME=${USER_NAME:-User}
sed -i "s/MEMEX_USER=.*/MEMEX_USER=\"$USER_NAME\"/g" .env

read -p "What shall we name your Assistant? (default: Agent): " AGENT_NAME
AGENT_NAME=${AGENT_NAME:-Agent}
sed -i "s/MEMEX_ASSISTANT=.*/MEMEX_ASSISTANT=\"$AGENT_NAME\"/g" .env

echo "Select primary neural provider:"
echo "1) Gemini"; echo "2) OpenRouter"; echo "3) Anthropic"; echo "4) OpenAI"
read -p "Choice [1-4]: " LLM_CHOICE
case $LLM_CHOICE in
    2) read -p "Enter OpenRouter Key: " INPUT; sed -i "s/OPENROUTER_API_KEY=.*/OPENROUTER_API_KEY=\"$INPUT\"/g" .env ;;
    3) read -p "Enter Anthropic Key: " INPUT; sed -i "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=\"$INPUT\"/g" .env ;;
    4) read -p "Enter OpenAI Key: " INPUT; sed -i "s/OPENAI_API_KEY=.*/OPENAI_API_KEY=\"$INPUT\"/g" .env ;;
    *) read -p "Enter Gemini Key: " INPUT; sed -i "s/GEMINI_API_KEY=.*/GEMINI_API_KEY=\"$INPUT\"/g" .env ;;
esac

# Update baremetal paths based on current directory structure
PROJECT_ROOT=$(pwd)
PARENT_DIR=$(dirname "$PROJECT_ROOT")
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$PARENT_DIR\"|g" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$PARENT_DIR/notes\"|g" .env
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$PARENT_DIR/notes\"|g" .env

mkdir -p "$PARENT_DIR/notes"
cp -n skills/*.org "$PARENT_DIR/notes/" 2>/dev/null || true

echo -e "${GREEN}Baremetal setup complete. Run 'make run' to start.${NC}"