Chapter 4: Implementation - Step-by-Step Build

Build the automation pipeline in 30 minutes with detailed instructions

Let's build the automation pipeline. I'll show you exactly what I do, with every command explained.

Step 1: Create the Utility Functions (10 minutes)

First, we create a library of reusable functions.

Create wake-utils.sh:

cd ~/automation-projects/claude-pipeline/scripts
cat > wake-utils.sh << 'EOF'
#!/bin/bash
# wake-utils.sh - Utility functions for wake automation

# Logging function
log_message() {
    local message="$1"
    local log_file="${LOG_FILE:-$HOME/automation-projects/claude-pipeline/logs/automation.log}"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $message" | tee -a "$log_file"
}

# Calculate next occurrence of target hour
calculate_next_wake() {
    local target_hour="$1"  # e.g., 18 for 6pm
    local current_hour=$(date +%H)
    local current_date=$(date +%Y-%m-%d)

    # If target hour hasn't passed today, use today
    if [ "$current_hour" -lt "$target_hour" ]; then
        echo "${current_date} ${target_hour}:00:00"
    else
        # Otherwise use tomorrow
        echo "$(date -v+1d +%Y-%m-%d) ${target_hour}:00:00"
    fi
}

# Check current pmset schedule
get_current_schedule() {
    pmset -g sched 2>/dev/null || echo "No schedules found"
}

# Cancel all pmset schedules
cancel_all_schedules() {
    log_message "Canceling all pmset schedules..."
    sudo pmset schedule cancelall 2>/dev/null
    log_message "All schedules canceled"
}

# Get running caffeinate processes
get_caffeinate_processes() {
    ps aux | grep caffeinate | grep -v grep
}

# Stop all caffeinate processes
stop_all_caffeinate() {
    log_message "Stopping all caffeinate processes..."
    pkill -f caffeinate
    log_message "Caffeinate processes stopped"
}

# Show system status
show_system_status() {
    echo "=== System Status ==="
    echo ""
    echo "Current Time: $(date)"
    echo ""
    echo "Scheduled Wakes:"
    get_current_schedule
    echo ""
    echo "Running Caffeinate:"
    get_caffeinate_processes || echo "None"
    echo ""
    echo "LaunchAgent Status:"
    launchctl list | grep claude-automation || echo "Not loaded"
    echo ""
}

# Export functions for use in other scripts
export -f log_message
export -f calculate_next_wake
export -f get_current_schedule
export -f cancel_all_schedules
export -f get_caffeinate_processes
export -f stop_all_caffeinate
export -f show_system_status
EOF

chmod +x wake-utils.sh

What this does: Creates reusable functions for logging, scheduling, and system control. Exports functions so other scripts can use them. Provides debugging utilities (show_system_status).

Test it:

source wake-utils.sh
show_system_status

You should see current system status with scheduled wakes, running processes, and LaunchAgent status.

Step 2: Build the Wake Handler (10 minutes)

This script runs when the system wakes and orchestrates the automation.

Create automation-handler.sh:

cd ~/automation-projects/claude-pipeline/scripts
cat > automation-handler.sh << 'EOF'
#!/bin/bash
# automation-handler.sh - Handles wake events and runs automation tasks

# Setup
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export LOG_FILE="$HOME/automation-projects/claude-pipeline/logs/automation.log"

# Source utilities
source "$SCRIPT_DIR/wake-utils.sh"

# Detect current time and determine action
detect_wake_time() {
    local hour=$(date +%H)
    local minute=$(date +%M)

    # 6pm wake (18:00)
    if [ "$hour" -eq 18 ] && [ "$minute" -lt 10 ]; then
        echo "6pm"
    # 7pm wake (19:00)
    elif [ "$hour" -eq 19 ] && [ "$minute" -lt 10 ]; then
        echo "7pm"
    else
        echo "unknown"
    fi
}

# Calculate caffeinate duration
calculate_caffeinate_duration() {
    local wake_time="$1"

    case "$wake_time" in
        "6pm")
            echo 1800  # 30 minutes
            ;;
        "7pm")
            echo 300   # 5 minutes
            ;;
        *)
            echo 60    # 1 minute default
            ;;
    esac
}

# Run caffeinate with logging
run_caffeinate() {
    local duration="$1"
    local wake_time="$2"

    log_message "[$wake_time wake] Starting caffeinate for $duration seconds"

    # Run caffeinate in background
    caffeinate -t "$duration" &
    local caffeinate_pid=$!

    log_message "[$wake_time wake] Caffeinate running (PID: $caffeinate_pid)"

    # Wait for caffeinate to complete
    wait $caffeinate_pid

    log_message "[$wake_time wake] Caffeinate completed"
}

# Schedule next wake
schedule_next_wake() {
    local next_hour="$1"
    local next_time=$(calculate_next_wake "$next_hour")

    log_message "Scheduling next wake: $next_time"
    sudo pmset schedule wake "$next_time" 2>&1 | tee -a "$LOG_FILE"
}

# Main execution flow
main() {
    log_message "=== Automation Handler Started ==="

    # Detect wake time
    local wake_time=$(detect_wake_time)
    log_message "Detected wake time: $wake_time"

    case "$wake_time" in
        "6pm")
            # Schedule 7pm wake immediately
            schedule_next_wake 19

            # Run 30-minute caffeinate
            local duration=$(calculate_caffeinate_duration "6pm")
            run_caffeinate "$duration" "6pm"

            # Add your 6pm automation tasks here
            log_message "[6pm wake] Running automation tasks..."
            # Example: Run a backup script
            # /path/to/backup-script.sh

            log_message "[6pm wake] Automation complete"
            ;;

        "7pm")
            # Run 5-minute caffeinate
            local duration=$(calculate_caffeinate_duration "7pm")
            run_caffeinate "$duration" "7pm"

            # Add your 7pm automation tasks here
            log_message "[7pm wake] Running automation tasks..."
            # Example: Run a data processing script
            # /path/to/process-data.sh

            log_message "[7pm wake] Automation complete"
            ;;

        *)
            log_message "Unknown wake time - no action taken"
            ;;
    esac

    log_message "=== Automation Handler Finished ==="
}

# Handle command line arguments
case "${1:-}" in
    test-6pm)
        log_message "=== TESTING 6PM SEQUENCE ==="
        schedule_next_wake 19
        run_caffeinate 10 "6pm-test"  # 10 seconds for testing
        ;;
    test-7pm)
        log_message "=== TESTING 7PM SEQUENCE ==="
        run_caffeinate 10 "7pm-test"  # 10 seconds for testing
        ;;
    *)
        main
        ;;
esac
EOF

chmod +x automation-handler.sh

What this does: Detects whether it's a 6pm or 7pm wake, schedules the next wake in the sequence, runs caffeinate to keep system awake, provides hooks for your automation tasks, and includes test modes for debugging.

Test it:

# Test 6pm sequence (won't actually schedule, just logs)
./automation-handler.sh test-6pm

# Check the log
tail -20 ~/automation-projects/claude-pipeline/logs/automation.log

Step 3: Create the Main Setup Script (5 minutes)

This script sets up the entire system.

Create setup-automation.sh:

cd ~/automation-projects/claude-pipeline/scripts
cat > setup-automation.sh << 'EOF'
#!/bin/bash
# setup-automation.sh - Setup and control the automation pipeline

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLIST_DIR="$HOME/automation-projects/claude-pipeline/launchagents"
PLIST_FILE="$PLIST_DIR/com.user.claude-automation.plist"
export LOG_FILE="$HOME/automation-projects/claude-pipeline/logs/automation.log"

source "$SCRIPT_DIR/wake-utils.sh"

# Create LaunchAgent plist
create_launch_agent() {
    log_message "Creating LaunchAgent plist..."

    cat > "$PLIST_FILE" << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.user.claude-automation</string>

    <key>ProgramArguments</key>
    <array>
        <string>$SCRIPT_DIR/automation-handler.sh</string>
    </array>

    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>18</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>19</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </array>

    <key>StandardOutPath</key>
    <string>/tmp/claude-automation.out</string>

    <key>StandardErrorPath</key>
    <string>/tmp/claude-automation.err</string>

    <key>RunAtLoad</key>
    <false/>
</dict>
</plist>
PLIST

    log_message "LaunchAgent plist created: $PLIST_FILE"
}

# Install LaunchAgent
install_launch_agent() {
    log_message "Installing LaunchAgent..."

    # Copy to system LaunchAgents directory
    cp "$PLIST_FILE" ~/Library/LaunchAgents/

    # Load the agent
    launchctl unload ~/Library/LaunchAgents/com.user.claude-automation.plist 2>/dev/null
    launchctl load ~/Library/LaunchAgents/com.user.claude-automation.plist

    log_message "LaunchAgent installed and loaded"
}

# Setup initial wake schedule
setup_initial_wake() {
    log_message "Setting up initial wake schedule..."

    local next_6pm=$(calculate_next_wake 18)
    log_message "Scheduling first wake: $next_6pm"

    sudo pmset schedule wake "$next_6pm" 2>&1 | tee -a "$LOG_FILE"

    log_message "Initial wake scheduled"
}

# Main setup
setup() {
    log_message "=== Starting Claude Automation Setup ==="

    # Create plist
    create_launch_agent

    # Install LaunchAgent
    install_launch_agent

    # Schedule initial wake
    setup_initial_wake

    log_message "=== Setup Complete ==="
    log_message ""
    log_message "Next steps:"
    log_message "1. System will wake at 6pm"
    log_message "2. Caffeinate will run for 30 minutes"
    log_message "3. System will wake again at 7pm"
    log_message ""
    log_message "To check status: $0 status"
    log_message "To cancel all: $0 cancel"
}

# Show status
status() {
    show_system_status

    echo "Recent logs:"
    tail -15 "$LOG_FILE"
}

# Cancel everything
cancel() {
    log_message "=== Canceling all automation ==="

    # Unload LaunchAgent
    launchctl unload ~/Library/LaunchAgents/com.user.claude-automation.plist 2>/dev/null

    # Cancel schedules
    cancel_all_schedules

    # Stop caffeinate
    stop_all_caffeinate

    log_message "=== All automation canceled ==="
}

# Command line interface
case "${1:-}" in
    status)
        status
        ;;
    cancel)
        cancel
        ;;
    *)
        setup
        ;;
esac
EOF

chmod +x setup-automation.sh

What this does: Creates and installs the LaunchAgent, schedules the initial 6pm wake, provides status and cancel commands, and ties everything together.

Step 4: Run the Setup (5 minutes)

Now we bring it all together.

Execute setup:

cd ~/automation-projects/claude-pipeline/scripts
./setup-automation.sh

You'll be prompted for your password (for sudo pmset). Enter it.

Expected output:

[2025-10-03 14:45:23] === Starting Claude Automation Setup ===
[2025-10-03 14:45:23] Creating LaunchAgent plist...
[2025-10-03 14:45:23] LaunchAgent plist created: /Users/you/automation-projects/claude-pipeline/launchagents/com.user.claude-automation.plist
[2025-10-03 14:45:23] Installing LaunchAgent...
[2025-10-03 14:45:24] LaunchAgent installed and loaded
[2025-10-03 14:45:24] Setting up initial wake schedule...
[2025-10-03 14:45:24] Scheduling first wake: 2025-10-03 18:00:00
[2025-10-03 14:45:25] Initial wake scheduled
[2025-10-03 14:45:25] === Setup Complete ===

Verify installation:

./setup-automation.sh status

You should see:

  • Scheduled wake at 6pm today (or tomorrow if it's after 6pm)
  • LaunchAgent loaded
  • Recent log entries

Test the automation (without waiting for 6pm):

./automation-handler.sh test-6pm

Check the logs:

tail -30 ~/automation-projects/claude-pipeline/logs/automation.log

You should see:

  • Handler started
  • 7pm wake scheduled
  • Caffeinate running for 10 seconds (test mode)
  • Caffeinate completed
  • Handler finished

Congratulations! You've built a complete automation pipeline in 30 minutes.