> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Iru/Kandji Deployment

> Deploy automatic configuration provisioning to macOS devices managed by Iru/Kandji

export const MdmScriptGenerator = ({mode = "watch", scriptType = "macos"}) => {
  const macosWatchScript = `
#!/bin/bash
# MDM Script: Run AI Watch (Wrapper Binary Version)
# Exit codes: 0=success, 1=general failure, 2=network, 3=installation

set -e

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

RUNLAYER_HOST="{{RUNLAYER_HOST}}"
ORG_API_KEY="{{ORG_API_KEY}}"
DEVICE_NAME_OVERRIDE="{{DEVICE_NAME}}"
RUNLAYER_VERSION="{{RUNLAYER_VERSION}}"

# Embedded universal macOS binary (arm64 + x86_64) that wraps uvx runlayer scan.
# Signed with identifier "com.runlayer.scan" for PPPC targeting.

EMBEDDED_BINARY_BASE64='
__BINARY_PLACEHOLDER__
'

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

LOG_FILE="/var/log/runlayer/ai_watch_detect.log"
UV_INSTALL_SCRIPT="https://astral.sh/uv/install.sh"

WRAPPER_BINARY_PATH="/usr/local/bin/runlayer-scan"
BUNDLE_ID="com.runlayer.scan"

LAUNCHAGENT_LABEL="com.runlayer.scan"
SCAN_STDOUT_LOG="/tmp/runlayer-scan.stdout.log"
SCAN_STDERR_LOG="/tmp/runlayer-scan.stderr.log"
SCAN_TIMEOUT=120

log() {
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}

mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/runlayer-ai_watch_detect.log"

log "=========================================="
log "AI Watch (macOS - Wrapper Binary)"
log "Host: \${RUNLAYER_HOST:-NOT SET}"
log "=========================================="

if [[ -z "$RUNLAYER_HOST" ]]; then
    log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your AI Watch URL."
    exit 1
fi

if [[ -z "$ORG_API_KEY" ]]; then
    log "ERROR: ORG_API_KEY is not set. Edit the script to add your organization API key."
    exit 1
fi

get_console_user() {
    stat -f '%Su' /dev/console 2>/dev/null
}

get_user_uid() {
    local user="$1"
    id -u "$user" 2>/dev/null
}

CONSOLE_USER=$(get_console_user)
if [[ -z "$CONSOLE_USER" || "$CONSOLE_USER" == "root" || "$CONSOLE_USER" == "loginwindow" ]]; then
    log "ERROR: No user logged in"
    exit 1
fi

USER_UID=$(get_user_uid "$CONSOLE_USER")
USER_HOME=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')

UV_PATH="/usr/local/bin/uv"
UVX_PATH="/usr/local/bin/uvx"

if [[ -n "$DEVICE_NAME_OVERRIDE" ]]; then
    DEVICE_NAME="$DEVICE_NAME_OVERRIDE"
else
    DEVICE_NAME=$(scutil --get ComputerName 2>/dev/null || hostname -s)
fi

log "Console user: $CONSOLE_USER (UID: $USER_UID)"
log "Home directory: $USER_HOME"
log "Device name: $DEVICE_NAME"

ALREADY_CONFIGURED=false
ORG_KEY_LABEL="ai_watch_mdm"

log "Checking network..."
if ! curl -s --connect-timeout 10 https://pypi.org > /dev/null 2>&1; then
    log "ERROR: No network connectivity"
    exit 2
fi
log "Network OK"

log "Using organization API key (label: $ORG_KEY_LABEL)"

if [[ ! -x "$UV_PATH" ]]; then
    log "Installing UV to /usr/local/bin ..."
    
    mkdir -p /usr/local/bin
    
    INSTALLER_SCRIPT=$(mktemp)
    if ! curl -LsSf "$UV_INSTALL_SCRIPT" -o "$INSTALLER_SCRIPT"; then
        log "ERROR: Failed to download UV installer"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    chmod +x "$INSTALLER_SCRIPT"
    
    if ! HOME="\${HOME:-/var/root}" INSTALLER_NO_MODIFY_PATH=1 UV_INSTALL_DIR="/usr/local/bin" /bin/bash "$INSTALLER_SCRIPT"; then
        log "ERROR: UV installation failed"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    rm -f "$INSTALLER_SCRIPT"
    
    # No re-signing needed — wrapper binary receives FDA, not uvx
    log "UV installed (no re-signing needed - wrapper receives FDA)"
fi

if [[ -x "$UV_PATH" ]]; then
    UV_VERSION=$("$UV_PATH" --version 2>/dev/null || echo 'unknown')
    log "UV installed: $UV_VERSION"
else
    log "ERROR: UV not found at $UV_PATH after installation"
    exit 3
fi

TARGET_VERSION="$RUNLAYER_VERSION"
if [[ -z "$TARGET_VERSION" ]]; then
    log "Checking latest version on PyPI..."
    TARGET_VERSION=$(curl -s "https://pypi.org/pypi/runlayer/json" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
    if [[ -n "$TARGET_VERSION" ]]; then
        log "Latest version on PyPI: $TARGET_VERSION"
    else
        log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
    fi
fi

USER_CACHE_DIR="$USER_HOME/.cache"
UV_CACHE_DIR="$USER_CACHE_DIR/uv"
if [[ -d "$USER_CACHE_DIR" ]]; then
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR" 2>/dev/null || true
fi

CURRENT_VERSION=""
CURRENT_VERSION_NUMBER=""
if [[ -x "$UVX_PATH" ]]; then
    CURRENT_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" UV_CACHE_DIR="$UV_CACHE_DIR" "$UVX_PATH" runlayer --version 2>/dev/null || echo "")
    CURRENT_VERSION_NUMBER=$(echo "$CURRENT_VERSION" | awk '{print $3}')
fi

NEEDS_INSTALL=false
if [[ -z "$CURRENT_VERSION" ]]; then
    log "runlayer CLI not cached yet"
    NEEDS_INSTALL=true
elif [[ -n "$TARGET_VERSION" && "$CURRENT_VERSION_NUMBER" != "$TARGET_VERSION" ]]; then
    log "runlayer CLI version mismatch: have '$CURRENT_VERSION_NUMBER', want '$TARGET_VERSION'"
    NEEDS_INSTALL=true
else
    log "runlayer CLI available: $CURRENT_VERSION (up to date)"
fi

if [[ "$NEEDS_INSTALL" == "true" ]]; then
    log "Pre-caching runlayer CLI for user..."
    PACKAGE="runlayer"
    [[ -n "$RUNLAYER_VERSION" ]] && PACKAGE="runlayer==$RUNLAYER_VERSION"
    
    mkdir -p "$UV_CACHE_DIR"
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR"
    
    UV_WRAPPER=$(mktemp)
    cat > "$UV_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UV_PATH" tool install "$PACKAGE" --force
WRAPPER
    chmod +x "$UV_WRAPPER"
    
    if ! /bin/launchctl asuser "$USER_UID" "$UV_WRAPPER"; then
        log "ERROR: runlayer CLI installation failed"
        rm -f "$UV_WRAPPER"
        exit 3
    fi
    rm -f "$UV_WRAPPER"
    
    chown -R "$CONSOLE_USER" "$UV_CACHE_DIR" 2>/dev/null || true
    
    INSTALLED_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" "$UVX_PATH" runlayer --version 2>/dev/null || echo 'unknown')
    log "runlayer CLI installed: $INSTALLED_VERSION"
fi

log "Checking if org key is already stored..."
CHECK_WRAPPER=$(mktemp)
cat > "$CHECK_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer credentials check --host "$RUNLAYER_HOST" --skip-user-check --org-api-key "$ORG_KEY_LABEL"
WRAPPER
chmod +x "$CHECK_WRAPPER"

if /bin/launchctl asuser "$USER_UID" "$CHECK_WRAPPER" 2>/dev/null; then
    ALREADY_CONFIGURED=true
    log "Org key '$ORG_KEY_LABEL' already stored for $RUNLAYER_HOST"
else
    log "Org key not stored, will add now"
fi
rm -f "$CHECK_WRAPPER"

if [[ "$ALREADY_CONFIGURED" != "true" ]]; then
    log "Storing org API key as '$ORG_KEY_LABEL'..."

    ADD_WRAPPER=$(mktemp)
    cat > "$ADD_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
export RUNLAYER_ORG_API_KEY="$ORG_API_KEY"
"$UVX_PATH" runlayer credentials add org "$ORG_KEY_LABEL" --host "$RUNLAYER_HOST" --secret "\\$RUNLAYER_ORG_API_KEY"
WRAPPER
    chmod +x "$ADD_WRAPPER"

    if ! /bin/launchctl asuser "$USER_UID" "$ADD_WRAPPER"; then
        log "ERROR: Failed to store org API key"
        rm -f "$ADD_WRAPPER"
        exit 1
    fi
    rm -f "$ADD_WRAPPER"

    log "Org API key stored as '$ORG_KEY_LABEL'"
fi

chown -R "$CONSOLE_USER" "$USER_HOME/.runlayer" 2>/dev/null || true

log "Installing wrapper binary to $WRAPPER_BINARY_PATH..."

TEMP_BINARY=$(mktemp)
echo "$EMBEDDED_BINARY_BASE64" | base64 -d > "$TEMP_BINARY"

if [[ ! -s "$TEMP_BINARY" ]]; then
    log "ERROR: Failed to decode embedded binary"
    rm -f "$TEMP_BINARY"
    exit 3
fi

mkdir -p "$(dirname "$WRAPPER_BINARY_PATH")"
mv "$TEMP_BINARY" "$WRAPPER_BINARY_PATH"
chmod 755 "$WRAPPER_BINARY_PATH"

# Sign with stable identifier for PPPC matching
log "Signing wrapper binary with identifier: $BUNDLE_ID"
codesign --force --sign - --identifier "$BUNDLE_ID" "$WRAPPER_BINARY_PATH" 2>/dev/null || {
    log "WARNING: Failed to sign wrapper binary"
}

WRAPPER_ID=$(codesign -dv "$WRAPPER_BINARY_PATH" 2>&1 | grep "Identifier=" | cut -d= -f2)
log "Wrapper binary installed: $WRAPPER_BINARY_PATH (identifier: $WRAPPER_ID)"

log "Setting up LaunchAgent for scan execution..."

LAUNCHAGENT_DIR="$USER_HOME/Library/LaunchAgents"
LAUNCHAGENT_PLIST="$LAUNCHAGENT_DIR/$LAUNCHAGENT_LABEL.plist"

log "Installing LaunchAgent plist to $LAUNCHAGENT_PLIST..."
mkdir -p "$LAUNCHAGENT_DIR"
cat > "$LAUNCHAGENT_PLIST" << 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>$LAUNCHAGENT_LABEL</string>
    <key>ProgramArguments</key>
    <array>
        <string>$WRAPPER_BINARY_PATH</string>
        <string>--host</string>
        <string>$RUNLAYER_HOST</string>
        <string>--username</string>
        <string>$CONSOLE_USER</string>
        <string>--org-api-key</string>
        <string>$ORG_KEY_LABEL</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HOME</key>
        <string>$USER_HOME</string>
        <key>UV_CACHE_DIR</key>
        <string>$UV_CACHE_DIR</string>
    </dict>
    <key>RunAtLoad</key>
    <false/>
    <key>StandardOutPath</key>
    <string>$SCAN_STDOUT_LOG</string>
    <key>StandardErrorPath</key>
    <string>$SCAN_STDERR_LOG</string>
</dict>
</plist>
PLIST
chown "$CONSOLE_USER" "$LAUNCHAGENT_PLIST"
chmod 644 "$LAUNCHAGENT_PLIST"
log "LaunchAgent plist installed"

rm -f "$SCAN_STDOUT_LOG" "$SCAN_STDERR_LOG"

DOMAIN="gui/$USER_UID"

log "Loading LaunchAgent..."
/bin/launchctl bootout "$DOMAIN/$LAUNCHAGENT_LABEL" 2>/dev/null || true
/bin/launchctl bootstrap "$DOMAIN" "$LAUNCHAGENT_PLIST"
log "LaunchAgent loaded"

log "Triggering scan via LaunchAgent..."
/bin/launchctl kickstart -kp "$DOMAIN/$LAUNCHAGENT_LABEL"

sleep 1

PROCESS_STARTED=false
for i in {1..5}; do
    if /bin/launchctl print "$DOMAIN/$LAUNCHAGENT_LABEL" 2>/dev/null | grep -q "state = running"; then
        PROCESS_STARTED=true
        break
    fi
    if [[ -f "$SCAN_STDOUT_LOG" ]] || [[ -f "$SCAN_STDERR_LOG" ]]; then
        PROCESS_STARTED=true
        break
    fi
    sleep 0.5
done

if [[ "$PROCESS_STARTED" == "false" ]]; then
    log "WARNING: Scan process may not have started properly"
fi

log "Waiting for scan to complete (timeout: \${SCAN_TIMEOUT}s)..."
SCAN_START=$(date +%s)
SCAN_COMPLETED=false

while true; do
    if ! /bin/launchctl print "$DOMAIN/$LAUNCHAGENT_LABEL" 2>/dev/null | grep -q "state = running"; then
        SCAN_COMPLETED=true
        break
    fi
    
    ELAPSED=$(($(date +%s) - SCAN_START))
    if [[ $ELAPSED -ge $SCAN_TIMEOUT ]]; then
        log "WARNING: Scan timed out after \${SCAN_TIMEOUT}s"
        break
    fi
    sleep 2
done

if [[ -f "$SCAN_STDERR_LOG" ]]; then
    SCAN_ERRORS=$(cat "$SCAN_STDERR_LOG" 2>/dev/null)
    if [[ -n "$SCAN_ERRORS" ]]; then
        log "Scan stderr output:"
        echo "$SCAN_ERRORS" | while read -r line; do
            log "  $line"
        done
    fi
fi

if [[ -f "$SCAN_STDOUT_LOG" ]]; then
    SCAN_OUTPUT=$(cat "$SCAN_STDOUT_LOG" 2>/dev/null)
    if [[ -n "$SCAN_OUTPUT" ]]; then
        log "Scan stdout output:"
        echo "$SCAN_OUTPUT" | while read -r line; do
            log "  $line"
        done
    fi
fi

if [[ "$SCAN_COMPLETED" == "true" ]]; then
    log "Scan completed"
else
    log "WARNING: Scan may not have completed successfully"
fi

log "=========================================="
log "SUCCESS: AI Watch execution complete"
log "=========================================="

exit 0
`;
  const windowsWatchScript = `
#Requires -Version 5.1
# MDM Script: Run AI Watch for Windows
# Exit codes: 0=success, 1=general failure, 2=network, 3=installation

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

$RUNLAYER_HOST = "{{RUNLAYER_HOST}}"
$ORG_API_KEY = "{{ORG_API_KEY}}"
$DEVICE_NAME_OVERRIDE = "{{DEVICE_NAME}}"
$RUNLAYER_VERSION = "{{RUNLAYER_VERSION}}"
$SCAN_INTERVAL_MINUTES = "{{SCAN_INTERVAL_MINUTES}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\ai_watch_detect.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

function Test-NetworkConnectivity {
    try {
        $response = Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

Write-Log "=========================================="
Write-Log "AI Watch (Windows)"
Write-Log "Host: $(if ($RUNLAYER_HOST) { $RUNLAYER_HOST } else { 'NOT SET' })"
Write-Log "=========================================="

if (-not $RUNLAYER_HOST) {
    Write-Log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your AI Watch URL."
    exit 1
}

if (-not $ORG_API_KEY) {
    Write-Log "ERROR: ORG_API_KEY is not set. Edit the script to add your organization API key."
    exit 1
}

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userProfile = $env:USERPROFILE
$deviceName = if ($DEVICE_NAME_OVERRIDE) { $DEVICE_NAME_OVERRIDE } else { $env:COMPUTERNAME }

Write-Log "Current user: $currentUser"
Write-Log "User profile: $userProfile"
Write-Log "Device name: $deviceName"

$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

$alreadyConfigured = $false
$orgKeyLabel = "ai_watch_mdm"

Write-Log "Checking network connectivity..."
if (-not (Test-NetworkConnectivity)) {
    Write-Log "ERROR: No network connectivity"
    exit 2
}
Write-Log "Network OK"

Write-Log "Using organization API key (label: $orgKeyLabel)"

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV to $localBinPath ..."
    
    try {
        $installerUrl = "https://astral.sh/uv/install.ps1"
        Invoke-RestMethod $installerUrl | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 3
    }
}

if (Test-Path $uvPath) {
    $uvVersion = & $uvPath --version 2>$null
    Write-Log "UV installed: $uvVersion"
} else {
    Write-Log "ERROR: UV not found at $uvPath after installation"
    exit 3
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$targetVersion = $RUNLAYER_VERSION
if (-not $targetVersion) {
    Write-Log "Checking latest version on PyPI..."
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
        Write-Log "Latest version on PyPI: $targetVersion"
    } catch {
        Write-Log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
        $targetVersion = ""
    }
}

$currentVersion = ""
$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $currentVersion = & $uvxPath runlayer --version 2>$null
        if ($currentVersion -match '\\d+\\.\\d+\\.\\d+') {
            $currentVersionNumber = $Matches[0]
        }
    } catch {
        $currentVersion = ""
    }
}

$needsInstall = $false
if (-not $currentVersion) {
    Write-Log "runlayer CLI not installed"
    $needsInstall = $true
} elseif ($targetVersion -and $currentVersionNumber -ne $targetVersion) {
    Write-Log "runlayer CLI version mismatch: have '$currentVersionNumber', want '$targetVersion'"
    $needsInstall = $true
} else {
    Write-Log "runlayer CLI already installed: $currentVersion (up to date)"
}

if ($needsInstall) {
    Write-Log "Installing runlayer CLI..."
    
    $package = "runlayer"
    if ($RUNLAYER_VERSION) {
        $package = "runlayer==$RUNLAYER_VERSION"
    }
    
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($installExitCode -ne 0) {
        Write-Log "ERROR: runlayer CLI installation failed (exit code: $installExitCode)"
        exit 3
    }
    
    $installedVersion = & $uvxPath runlayer --version 2>$null
    Write-Log "runlayer CLI installed: $installedVersion"
}

Write-Log "Checking if org key is already stored..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer credentials check --host $RUNLAYER_HOST --skip-user-check --org-api-key $orgKeyLabel 2>$null
    $checkExitCode = $LASTEXITCODE
} catch {
    $checkExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

if ($checkExitCode -eq 0) {
    $alreadyConfigured = $true
    Write-Log "Org key '$orgKeyLabel' already stored for $RUNLAYER_HOST"
} else {
    Write-Log "Org key not stored, will add now"
}

if (-not $alreadyConfigured) {
    Write-Log "Storing org API key as '$orgKeyLabel'..."

    try {
        $ErrorActionPreference = "Continue"
        & $uvxPath runlayer credentials add org $orgKeyLabel --host $RUNLAYER_HOST --secret $ORG_API_KEY 2>$null
        $addExitCode = $LASTEXITCODE
    } catch {
        $addExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($addExitCode -ne 0) {
        Write-Log "ERROR: Failed to store org API key (exit code: $addExitCode)"
        exit 1
    }

    Write-Log "Org API key stored as '$orgKeyLabel'"
}

Write-Log "Running AI Watch scan..."

try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer scan --host $RUNLAYER_HOST --org-api-key $orgKeyLabel 2>$null
    $scanExitCode = $LASTEXITCODE
} catch {
    Write-Log "WARNING: Scan failed: $_"
    $scanExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}
if ($scanExitCode -eq 0) {
    Write-Log "Scan completed successfully"
} else {
    Write-Log "WARNING: Scan failed (exit code: $scanExitCode)"
}

try {
    Write-Log "Setting up scheduled task for recurring scans..."

    $scriptDir = "$env:ProgramData\\Runlayer\\Scripts"
    $recurringScript = "$scriptDir\\runlayer-ai-watch.ps1"
    New-Item -Path $scriptDir -ItemType Directory -Force | Out-Null

    $recurringScriptContent = @'
$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\ai_watch_detect.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

Write-Log "=========================================="
Write-Log "AI Watch Scheduled Run"
Write-Log "=========================================="

$userProfile = $env:USERPROFILE
$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

try {
    Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop | Out-Null
} catch {
    Write-Log "WARNING: No network connectivity, skipping"
    exit 0
}

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV..."
    try {
        Invoke-RestMethod "https://astral.sh/uv/install.ps1" | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 3
    }
}

if (-not (Test-Path $uvPath)) {
    Write-Log "ERROR: UV not found at $uvPath"
    exit 3
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$pinnedVersion = "__RUNLAYER_VERSION__"
$targetVersion = $pinnedVersion
if (-not $targetVersion) {
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
    } catch {
        $targetVersion = ""
    }
}

$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $vOut = & $uvxPath runlayer --version 2>$null
        if ($vOut -match '\\d+\\.\\d+\\.\\d+') { $currentVersionNumber = $Matches[0] }
    } catch {}
}

if (-not $currentVersionNumber -or ($targetVersion -and $currentVersionNumber -ne $targetVersion)) {
    Write-Log "Updating runlayer CLI..."
    $package = "runlayer"
    if ($pinnedVersion) { $package = "runlayer==$pinnedVersion" }
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }
    if ($installExitCode -ne 0) {
        Write-Log "WARNING: CLI update failed (exit code: $installExitCode)"
    } else {
        $installedVersion = & $uvxPath runlayer --version 2>$null
        Write-Log "runlayer CLI updated: $installedVersion"
    }
}

Write-Log "Running AI Watch scan..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer scan --host __RUNLAYER_HOST__ --org-api-key __ORG_KEY_LABEL__ 2>$null
    $scanExitCode = $LASTEXITCODE
} catch {
    Write-Log "WARNING: Scan failed: $_"
    $scanExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}
if ($scanExitCode -eq 0) {
    Write-Log "Scan completed successfully"
} else {
    Write-Log "WARNING: Scan failed (exit code: $scanExitCode)"
}

Write-Log "=========================================="
Write-Log "Scheduled run complete"
Write-Log "=========================================="
'@

    $recurringScriptContent = $recurringScriptContent.Replace('__RUNLAYER_VERSION__', $RUNLAYER_VERSION)
    $recurringScriptContent = $recurringScriptContent.Replace('__RUNLAYER_HOST__', $RUNLAYER_HOST)
    $recurringScriptContent = $recurringScriptContent.Replace('__ORG_KEY_LABEL__', $orgKeyLabel)
    Set-Content -Path $recurringScript -Value $recurringScriptContent -Force
    Write-Log "Recurring script saved to $recurringScript"

    $intervalMinutes = if ($SCAN_INTERVAL_MINUTES) { [int]$SCAN_INTERVAL_MINUTES } else { 60 }

    $action = New-ScheduledTaskAction -Execute "conhost.exe" \`
        -Argument "--headless powershell.exe -NoProfile -NonInteractive -File \`"$recurringScript\`""

    $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) \`
        -RepetitionInterval (New-TimeSpan -Minutes $intervalMinutes) \`
        -RepetitionDuration (New-TimeSpan -Days 9999)

    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries \`
        -DontStopIfGoingOnBatteries -StartWhenAvailable -Hidden

    Register-ScheduledTask -TaskName "RunlayerAIWatch" \`
        -Action $action \`
        -Trigger $trigger \`
        -Settings $settings \`
        -User $currentUser \`
        -RunLevel Limited \`
        -Force

    Write-Log "Scheduled task 'RunlayerAIWatch' registered (every $intervalMinutes minutes)"
} catch {
    Write-Log "WARNING: Could not set up scheduled task: $_"
}

Write-Log "=========================================="
Write-Log "SUCCESS: AI Watch execution complete"
Write-Log "=========================================="

exit 0
`;
  const windowsWatchRemediationScript = `
#Requires -Version 5.1
# MDM Script: Run AI Watch for Windows (Intune Remediation Detection)
# Deployed as a detection script in Intune Remediations.
# Intune handles scheduling; no scheduled task is created.
# Exit codes: 0=compliant (scan succeeded), 1=non-compliant (scan or prerequisite failed)

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

$RUNLAYER_HOST = "{{RUNLAYER_HOST}}"
$ORG_API_KEY = "{{ORG_API_KEY}}"
$DEVICE_NAME_OVERRIDE = "{{DEVICE_NAME}}"
$RUNLAYER_VERSION = "{{RUNLAYER_VERSION}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\ai_watch_detect.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

function Test-NetworkConnectivity {
    try {
        $response = Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

Write-Log "=========================================="
Write-Log "AI Watch Remediation Detection (Windows)"
Write-Log "Host: $(if ($RUNLAYER_HOST) { $RUNLAYER_HOST } else { 'NOT SET' })"
Write-Log "=========================================="

if (-not $RUNLAYER_HOST) {
    Write-Log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your AI Watch URL."
    exit 1
}

if (-not $ORG_API_KEY) {
    Write-Log "ERROR: ORG_API_KEY is not set. Edit the script to add your organization API key."
    exit 1
}

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userProfile = $env:USERPROFILE
$deviceName = if ($DEVICE_NAME_OVERRIDE) { $DEVICE_NAME_OVERRIDE } else { $env:COMPUTERNAME }

Write-Log "Current user: $currentUser"
Write-Log "User profile: $userProfile"
Write-Log "Device name: $deviceName"

$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

$alreadyConfigured = $false
$orgKeyLabel = "ai_watch_mdm"

Write-Log "Checking network connectivity..."
if (-not (Test-NetworkConnectivity)) {
    Write-Log "ERROR: No network connectivity"
    exit 1
}
Write-Log "Network OK"

Write-Log "Using organization API key (label: $orgKeyLabel)"

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV to $localBinPath ..."
    
    try {
        $installerUrl = "https://astral.sh/uv/install.ps1"
        Invoke-RestMethod $installerUrl | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 1
    }
}

if (Test-Path $uvPath) {
    $uvVersion = & $uvPath --version 2>$null
    Write-Log "UV installed: $uvVersion"
} else {
    Write-Log "ERROR: UV not found at $uvPath after installation"
    exit 1
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$targetVersion = $RUNLAYER_VERSION
if (-not $targetVersion) {
    Write-Log "Checking latest version on PyPI..."
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
        Write-Log "Latest version on PyPI: $targetVersion"
    } catch {
        Write-Log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
        $targetVersion = ""
    }
}

$currentVersion = ""
$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $currentVersion = & $uvxPath runlayer --version 2>$null
        if ($currentVersion -match '\\d+\\.\\d+\\.\\d+') {
            $currentVersionNumber = $Matches[0]
        }
    } catch {
        $currentVersion = ""
    }
}

$needsInstall = $false
if (-not $currentVersion) {
    Write-Log "runlayer CLI not installed"
    $needsInstall = $true
} elseif ($targetVersion -and $currentVersionNumber -ne $targetVersion) {
    Write-Log "runlayer CLI version mismatch: have '$currentVersionNumber', want '$targetVersion'"
    $needsInstall = $true
} else {
    Write-Log "runlayer CLI already installed: $currentVersion (up to date)"
}

if ($needsInstall) {
    Write-Log "Installing runlayer CLI..."
    
    $package = "runlayer"
    if ($RUNLAYER_VERSION) {
        $package = "runlayer==$RUNLAYER_VERSION"
    }
    
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($installExitCode -ne 0) {
        Write-Log "ERROR: runlayer CLI installation failed (exit code: $installExitCode)"
        exit 1
    }
    
    $installedVersion = & $uvxPath runlayer --version 2>$null
    Write-Log "runlayer CLI installed: $installedVersion"
}

Write-Log "Checking if org key is already stored..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer credentials check --host $RUNLAYER_HOST --skip-user-check --org-api-key $orgKeyLabel 2>$null
    $checkExitCode = $LASTEXITCODE
} catch {
    $checkExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

if ($checkExitCode -eq 0) {
    $alreadyConfigured = $true
    Write-Log "Org key '$orgKeyLabel' already stored for $RUNLAYER_HOST"
} else {
    Write-Log "Org key not stored, will add now"
}

if (-not $alreadyConfigured) {
    Write-Log "Storing org API key as '$orgKeyLabel'..."

    try {
        $ErrorActionPreference = "Continue"
        & $uvxPath runlayer credentials add org $orgKeyLabel --host $RUNLAYER_HOST --secret $ORG_API_KEY 2>$null
        $addExitCode = $LASTEXITCODE
    } catch {
        $addExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($addExitCode -ne 0) {
        Write-Log "ERROR: Failed to store org API key (exit code: $addExitCode)"
        exit 1
    }

    Write-Log "Org API key stored as '$orgKeyLabel'"
}

Write-Log "Running AI Watch scan..."

try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer scan --host $RUNLAYER_HOST --org-api-key $orgKeyLabel 2>$null
    $scanExitCode = $LASTEXITCODE
} catch {
    Write-Log "ERROR: Scan failed: $_"
    $scanExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

$exitCode = 0
if ($scanExitCode -eq 0) {
    Write-Log "Scan completed successfully"
} else {
    Write-Log "ERROR: Scan failed (exit code: $scanExitCode)"
    $exitCode = 1
}

Write-Log "=========================================="
Write-Log "AI Watch remediation detection complete (exit: $exitCode)"
Write-Log "=========================================="

exit $exitCode
`;
  const windowsSyncRemediationScript = `
#Requires -Version 5.1
# MDM Script: Run Config Sync for Windows (Intune Remediation Detection)
# Deployed as a detection script in Intune Remediations.
# Intune handles scheduling; no scheduled task is created.
# Exit codes: 0=compliant (sync succeeded), 1=non-compliant (sync or prerequisite failed)

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

$RUNLAYER_HOST = "{{RUNLAYER_HOST}}"
$ENROLLMENT_API_KEY = "{{ENROLLMENT_API_KEY}}"
$ENROLLMENT_USERNAME = "{{ENROLLMENT_USERNAME}}"
$ENROLLMENT_DEVICE_NAME = "{{ENROLLMENT_DEVICE_NAME}}"
$RUNLAYER_VERSION = "{{RUNLAYER_VERSION}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\auto_provisioning.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

function Test-NetworkConnectivity {
    try {
        $response = Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

Write-Log "=========================================="
Write-Log "Config Sync Remediation Detection (Windows)"
Write-Log "Host: $(if ($RUNLAYER_HOST) { $RUNLAYER_HOST } else { 'NOT SET' })"
Write-Log "=========================================="

if (-not $RUNLAYER_HOST) {
    Write-Log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your Runlayer URL."
    exit 1
}

if (-not $ENROLLMENT_API_KEY) {
    Write-Log "ERROR: ENROLLMENT_API_KEY is not set. Edit the script to add your enrollment key."
    exit 1
}

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userProfile = $env:USERPROFILE
$deviceName = if ($ENROLLMENT_DEVICE_NAME) { $ENROLLMENT_DEVICE_NAME } else { $env:COMPUTERNAME }

Write-Log "Current user: $currentUser"
Write-Log "User profile: $userProfile"
Write-Log "Device name: $deviceName"

$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

$alreadyConfigured = $false

Write-Log "Checking network connectivity..."
if (-not (Test-NetworkConnectivity)) {
    Write-Log "ERROR: No network connectivity"
    exit 1
}
Write-Log "Network OK"

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV to $localBinPath ..."
    
    try {
        $installerUrl = "https://astral.sh/uv/install.ps1"
        Invoke-RestMethod $installerUrl | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 1
    }
}

if (Test-Path $uvPath) {
    $uvVersion = & $uvPath --version 2>$null
    Write-Log "UV installed: $uvVersion"
} else {
    Write-Log "ERROR: UV not found at $uvPath after installation"
    exit 1
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$targetVersion = $RUNLAYER_VERSION
if (-not $targetVersion) {
    Write-Log "Checking latest version on PyPI..."
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
        Write-Log "Latest version on PyPI: $targetVersion"
    } catch {
        Write-Log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
        $targetVersion = ""
    }
}

$currentVersion = ""
$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $currentVersion = & $uvxPath runlayer --version 2>$null
        if ($currentVersion -match '\\d+\\.\\d+\\.\\d+') {
            $currentVersionNumber = $Matches[0]
        }
    } catch {
        $currentVersion = ""
    }
}

$needsInstall = $false
if (-not $currentVersion) {
    Write-Log "runlayer CLI not installed"
    $needsInstall = $true
} elseif ($targetVersion -and $currentVersionNumber -ne $targetVersion) {
    Write-Log "runlayer CLI version mismatch: have '$currentVersionNumber', want '$targetVersion'"
    $needsInstall = $true
} else {
    Write-Log "runlayer CLI already installed: $currentVersion (up to date)"
}

if ($needsInstall) {
    Write-Log "Installing runlayer CLI..."
    
    $package = "runlayer"
    if ($RUNLAYER_VERSION) {
        $package = "runlayer==$RUNLAYER_VERSION"
    }
    
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($installExitCode -ne 0) {
        Write-Log "ERROR: runlayer CLI installation failed (exit code: $installExitCode)"
        exit 1
    }
    
    $installedVersion = & $uvxPath runlayer --version 2>$null
    Write-Log "runlayer CLI installed: $installedVersion"
}

Write-Log "Checking if user is already enrolled..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer credentials check --host $RUNLAYER_HOST --skip-org-check 2>$null
    $checkExitCode = $LASTEXITCODE
} catch {
    $checkExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

if ($checkExitCode -eq 0) {
    $alreadyConfigured = $true
    Write-Log "User already enrolled for $RUNLAYER_HOST"
} else {
    Write-Log "User not enrolled, will enroll now"
}

if (-not $alreadyConfigured) {
    $deviceUsername = if ($ENROLLMENT_USERNAME) { $ENROLLMENT_USERNAME } else { $env:USERNAME }
    Write-Log "Enrolling device for user: $deviceUsername"

    try {
        $ErrorActionPreference = "Continue"
        & $uvxPath runlayer credentials enroll $ENROLLMENT_API_KEY --host $RUNLAYER_HOST --username $deviceUsername --device-name $deviceName 2>$null
        $enrollExitCode = $LASTEXITCODE
    } catch {
        $enrollExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($enrollExitCode -ne 0) {
        Write-Log "ERROR: Enrollment failed (exit code: $enrollExitCode)"
        exit 1
    }

    Write-Log "Enrollment successful"
}

Write-Log "Running setup sync..."

try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer setup sync --yes 2>$null
    $syncExitCode = $LASTEXITCODE
} catch {
    Write-Log "ERROR: Setup sync failed: $_"
    $syncExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

$exitCode = 0
if ($syncExitCode -eq 0) {
    Write-Log "Setup sync completed successfully"
} else {
    Write-Log "ERROR: Setup sync failed (exit code: $syncExitCode)"
    $exitCode = 1
}

Write-Log "=========================================="
Write-Log "Config sync remediation detection complete (exit: $exitCode)"
Write-Log "=========================================="

exit $exitCode
`;
  const macosHooksScript = `
#!/bin/bash
# MDM Script: Install Runlayer Hooks
# Exit codes: 0=success, 1=general failure, 2=network, 3=installation

set -e

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

RUNLAYER_HOST="{{RUNLAYER_HOST}}"
ENROLLMENT_API_KEY="{{ENROLLMENT_API_KEY}}"
ENROLLMENT_USERNAME="{{ENROLLMENT_USERNAME}}"
ENROLLMENT_DEVICE_NAME="{{ENROLLMENT_DEVICE_NAME}}"
RUNLAYER_VERSION="{{RUNLAYER_VERSION}}"
HOOKS_CLIENT="{{HOOKS_CLIENT}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

LOG_FILE="/var/log/runlayer/ai_watch_enforce.log"
UV_INSTALL_SCRIPT="https://astral.sh/uv/install.sh"

log() {
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}

mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/runlayer-ai_watch_enforce.log"

log "=========================================="
log "Runlayer Hooks Installation (macOS)"
log "Host: \${RUNLAYER_HOST:-NOT SET}"
log "Client: \${HOOKS_CLIENT:-all}"
log "=========================================="

if [[ -z "$RUNLAYER_HOST" ]]; then
    log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your Runlayer URL."
    exit 1
fi

if [[ -z "$ENROLLMENT_API_KEY" ]]; then
    log "ERROR: ENROLLMENT_API_KEY is not set. Edit the script to add your enrollment key."
    exit 1
fi

get_console_user() {
    stat -f '%Su' /dev/console 2>/dev/null
}

get_user_uid() {
    local user="$1"
    id -u "$user" 2>/dev/null
}

CONSOLE_USER=$(get_console_user)
if [[ -z "$CONSOLE_USER" || "$CONSOLE_USER" == "root" || "$CONSOLE_USER" == "loginwindow" ]]; then
    log "ERROR: No user logged in"
    exit 1
fi

USER_UID=$(get_user_uid "$CONSOLE_USER")
USER_HOME=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')

UV_PATH="/usr/local/bin/uv"
UVX_PATH="/usr/local/bin/uvx"

if [[ -n "$ENROLLMENT_DEVICE_NAME" ]]; then
    DEVICE_NAME="$ENROLLMENT_DEVICE_NAME"
else
    DEVICE_NAME=$(scutil --get ComputerName 2>/dev/null || hostname -s)
fi

log "Console user: $CONSOLE_USER (UID: $USER_UID)"
log "Home directory: $USER_HOME"
log "Device name: $DEVICE_NAME"

ALREADY_CONFIGURED=false

log "Checking network..."
if ! curl -s --connect-timeout 10 https://pypi.org > /dev/null 2>&1; then
    log "ERROR: No network connectivity"
    exit 2
fi
log "Network OK"

if [[ "$ALREADY_CONFIGURED" != "true" ]]; then
    log "Will enroll after CLI is installed"
fi

if [[ ! -x "$UV_PATH" ]]; then
    log "Installing UV to /usr/local/bin ..."
    
    mkdir -p /usr/local/bin
    
    INSTALLER_SCRIPT=$(mktemp)
    if ! curl -LsSf "$UV_INSTALL_SCRIPT" -o "$INSTALLER_SCRIPT"; then
        log "ERROR: Failed to download UV installer"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    chmod +x "$INSTALLER_SCRIPT"
    
    if ! HOME="\${HOME:-/var/root}" INSTALLER_NO_MODIFY_PATH=1 UV_INSTALL_DIR="/usr/local/bin" /bin/bash "$INSTALLER_SCRIPT"; then
        log "ERROR: UV installation failed"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    rm -f "$INSTALLER_SCRIPT"
    
    # Re-sign uv/uvx with stable identifiers for PPPC targeting
    log "Re-signing uv/uvx with stable identifiers..."
    if [[ -x "$UV_PATH" ]]; then
        codesign --force --sign - --identifier "com.runlayer.uv" "$UV_PATH" 2>/dev/null && \\
            log "  Signed: $UV_PATH as com.runlayer.uv" || \\
            log "  WARNING: Failed to sign $UV_PATH"
    fi
    if [[ -x "$UVX_PATH" ]]; then
        codesign --force --sign - --identifier "com.runlayer.uvx" "$UVX_PATH" 2>/dev/null && \\
            log "  Signed: $UVX_PATH as com.runlayer.uvx" || \\
            log "  WARNING: Failed to sign $UVX_PATH"
    fi
fi

if [[ -x "$UV_PATH" ]]; then
    UV_VERSION=$("$UV_PATH" --version 2>/dev/null || echo 'unknown')
    log "UV installed: $UV_VERSION"
else
    log "ERROR: UV not found at $UV_PATH after installation"
    exit 3
fi

TARGET_VERSION="$RUNLAYER_VERSION"
if [[ -z "$TARGET_VERSION" ]]; then
    log "Checking latest version on PyPI..."
    TARGET_VERSION=$(curl -s "https://pypi.org/pypi/runlayer/json" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
    if [[ -n "$TARGET_VERSION" ]]; then
        log "Latest version on PyPI: $TARGET_VERSION"
    else
        log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
    fi
fi

USER_CACHE_DIR="$USER_HOME/.cache"
UV_CACHE_DIR="$USER_CACHE_DIR/uv"
if [[ -d "$USER_CACHE_DIR" ]]; then
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR" 2>/dev/null || true
fi

CURRENT_VERSION=""
CURRENT_VERSION_NUMBER=""
if [[ -x "$UVX_PATH" ]]; then
    CURRENT_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" UV_CACHE_DIR="$UV_CACHE_DIR" "$UVX_PATH" runlayer --version 2>/dev/null || echo "")
    CURRENT_VERSION_NUMBER=$(echo "$CURRENT_VERSION" | awk '{print $3}')
fi

NEEDS_INSTALL=false
if [[ -z "$CURRENT_VERSION" ]]; then
    log "runlayer CLI not cached yet"
    NEEDS_INSTALL=true
elif [[ -n "$TARGET_VERSION" && "$CURRENT_VERSION_NUMBER" != "$TARGET_VERSION" ]]; then
    log "runlayer CLI version mismatch: have '$CURRENT_VERSION_NUMBER', want '$TARGET_VERSION'"
    NEEDS_INSTALL=true
else
    log "runlayer CLI available: $CURRENT_VERSION (up to date)"
fi

if [[ "$NEEDS_INSTALL" == "true" ]]; then
    log "Pre-caching runlayer CLI for user..."
    PACKAGE="runlayer"
    [[ -n "$RUNLAYER_VERSION" ]] && PACKAGE="runlayer==$RUNLAYER_VERSION"
    
    mkdir -p "$UV_CACHE_DIR"
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR"
    
    UV_WRAPPER=$(mktemp)
    cat > "$UV_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UV_PATH" tool install "$PACKAGE" --force
WRAPPER
    chmod +x "$UV_WRAPPER"
    
    if ! /bin/launchctl asuser "$USER_UID" "$UV_WRAPPER"; then
        log "ERROR: runlayer CLI installation failed"
        rm -f "$UV_WRAPPER"
        exit 3
    fi
    rm -f "$UV_WRAPPER"
    
    chown -R "$CONSOLE_USER" "$UV_CACHE_DIR" 2>/dev/null || true
    
    INSTALLED_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" "$UVX_PATH" runlayer --version 2>/dev/null || echo 'unknown')
    log "runlayer CLI installed: $INSTALLED_VERSION"
fi

log "Checking if user credentials already exist..."
CHECK_WRAPPER=$(mktemp)
cat > "$CHECK_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer credentials check --host "$RUNLAYER_HOST" --skip-org-check
WRAPPER
chmod +x "$CHECK_WRAPPER"

if /bin/launchctl asuser "$USER_UID" "$CHECK_WRAPPER" 2>/dev/null; then
    ALREADY_CONFIGURED=true
    log "User credentials already stored for $RUNLAYER_HOST"
else
    log "User credentials not stored, will enroll"
fi
rm -f "$CHECK_WRAPPER"

if [[ "$ALREADY_CONFIGURED" != "true" ]]; then
    DEVICE_USERNAME="\${ENROLLMENT_USERNAME:-$CONSOLE_USER}"
    log "Enrolling device for user: $DEVICE_USERNAME"

    ENROLL_WRAPPER=$(mktemp)
    cat > "$ENROLL_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer credentials enroll "$ENROLLMENT_API_KEY" --host "$RUNLAYER_HOST" --username "$DEVICE_USERNAME" --device-name "$DEVICE_NAME"
WRAPPER
    chmod +x "$ENROLL_WRAPPER"

    if ! /bin/launchctl asuser "$USER_UID" "$ENROLL_WRAPPER"; then
        log "ERROR: Enrollment failed"
        rm -f "$ENROLL_WRAPPER"
        exit 1
    fi
    rm -f "$ENROLL_WRAPPER"

    log "Enrollment successful"
fi

chown -R "$CONSOLE_USER" "$USER_HOME/.runlayer" 2>/dev/null || true

log "Installing Runlayer hooks..."

# --mdm writes to enterprise location (e.g. /Library/Application Support/Cursor/)
# and skips config.yaml check since root doesn't have one.
# The hook reads the user's config at runtime (Cursor runs as the logged-in user).
HOOKS_CMD="setup hooks --install --mdm --yes"
if [[ -n "$HOOKS_CLIENT" && "$HOOKS_CLIENT" != "all" ]]; then
    HOOKS_CMD="setup hooks --client $HOOKS_CLIENT --install --mdm --yes"
fi

log "Running: uvx runlayer $HOOKS_CMD"
if ! "$UVX_PATH" runlayer $HOOKS_CMD; then
    log "ERROR: Hooks installation failed"
    exit 3
fi

log "Installing .cursorignore for user..."

CURSORIGNORE_PATH="$USER_HOME/.cursorignore"
MARKER_START="# >>> Runlayer managed - do not edit >>>"
MARKER_END="# <<< Runlayer managed <<<"
MANAGED_BLOCK="$MARKER_START
.env
.env.*
.cursor/mcp.json
mcp.json
mcp_config.json
.mcp.json
$MARKER_END"

if [[ -f "$CURSORIGNORE_PATH" ]]; then
    if grep -q "$MARKER_START" "$CURSORIGNORE_PATH"; then
        log "Runlayer patterns already in .cursorignore, updating..."
        awk -v block="$MANAGED_BLOCK" '
            /^# >>> Runlayer managed - do not edit >>>$/ { print block; skip=1; next }
            /^# <<< Runlayer managed <<<$/ { skip=0; next }
            !skip
        ' "$CURSORIGNORE_PATH" > "\${CURSORIGNORE_PATH}.tmp" && mv "\${CURSORIGNORE_PATH}.tmp" "$CURSORIGNORE_PATH"
    else
        log "Appending Runlayer patterns to existing .cursorignore"
        printf '\\n%s\\n' "$MANAGED_BLOCK" >> "$CURSORIGNORE_PATH"
    fi
else
    log "Creating .cursorignore with Runlayer patterns"
    echo "$MANAGED_BLOCK" > "$CURSORIGNORE_PATH"
fi
chown "$CONSOLE_USER" "$CURSORIGNORE_PATH"
log "Updated $CURSORIGNORE_PATH"

log "=========================================="
log "SUCCESS: Runlayer hooks installation complete"
log "=========================================="

exit 0
`;
  const macosSyncScript = `
#!/bin/bash
# MDM Script: Automatic Configuration Provisioning (macOS)
# Exit codes: 0=success, 1=general failure, 2=network, 3=installation

set -e

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

RUNLAYER_HOST="{{RUNLAYER_HOST}}"
ENROLLMENT_API_KEY="{{ENROLLMENT_API_KEY}}"
ENROLLMENT_USERNAME="{{ENROLLMENT_USERNAME}}"
ENROLLMENT_DEVICE_NAME="{{ENROLLMENT_DEVICE_NAME}}"
RUNLAYER_VERSION="{{RUNLAYER_VERSION}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

LOG_FILE="/var/log/runlayer/auto_provisioning.log"
UV_INSTALL_SCRIPT="https://astral.sh/uv/install.sh"

log() {
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}

mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
touch "$LOG_FILE" 2>/dev/null || LOG_FILE="/tmp/runlayer-auto_provisioning.log"

log "=========================================="
log "Runlayer Config Sync (macOS)"
log "Host: \${RUNLAYER_HOST:-NOT SET}"
log "=========================================="

if [[ -z "$RUNLAYER_HOST" ]]; then
    log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your Runlayer URL."
    exit 1
fi

if [[ -z "$ENROLLMENT_API_KEY" ]]; then
    log "ERROR: ENROLLMENT_API_KEY is not set. Edit the script to add your enrollment key."
    exit 1
fi

get_console_user() {
    stat -f '%Su' /dev/console 2>/dev/null
}

get_user_uid() {
    local user="$1"
    id -u "$user" 2>/dev/null
}

CONSOLE_USER=$(get_console_user)
if [[ -z "$CONSOLE_USER" || "$CONSOLE_USER" == "root" || "$CONSOLE_USER" == "loginwindow" ]]; then
    log "ERROR: No user logged in"
    exit 1
fi

USER_UID=$(get_user_uid "$CONSOLE_USER")
USER_HOME=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')

UV_PATH="/usr/local/bin/uv"
UVX_PATH="/usr/local/bin/uvx"

if [[ -n "$ENROLLMENT_DEVICE_NAME" ]]; then
    DEVICE_NAME="$ENROLLMENT_DEVICE_NAME"
else
    DEVICE_NAME=$(scutil --get ComputerName 2>/dev/null || hostname -s)
fi

log "Console user: $CONSOLE_USER (UID: $USER_UID)"
log "Home directory: $USER_HOME"
log "Device name: $DEVICE_NAME"

ALREADY_CONFIGURED=false

log "Checking network..."
if ! curl -s --connect-timeout 10 https://pypi.org > /dev/null 2>&1; then
    log "ERROR: No network connectivity"
    exit 2
fi
log "Network OK"

if [[ "$ALREADY_CONFIGURED" != "true" ]]; then
    log "Will enroll after CLI is installed"
fi

if [[ ! -x "$UV_PATH" ]]; then
    log "Installing UV to /usr/local/bin ..."
    
    mkdir -p /usr/local/bin
    
    INSTALLER_SCRIPT=$(mktemp)
    if ! curl -LsSf "$UV_INSTALL_SCRIPT" -o "$INSTALLER_SCRIPT"; then
        log "ERROR: Failed to download UV installer"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    chmod +x "$INSTALLER_SCRIPT"
    
    if ! HOME="\${HOME:-/var/root}" INSTALLER_NO_MODIFY_PATH=1 UV_INSTALL_DIR="/usr/local/bin" /bin/bash "$INSTALLER_SCRIPT"; then
        log "ERROR: UV installation failed"
        rm -f "$INSTALLER_SCRIPT"
        exit 3
    fi
    rm -f "$INSTALLER_SCRIPT"
    
    log "UV installed"
fi

if [[ -x "$UV_PATH" ]]; then
    UV_VERSION=$("$UV_PATH" --version 2>/dev/null || echo 'unknown')
    log "UV installed: $UV_VERSION"
else
    log "ERROR: UV not found at $UV_PATH after installation"
    exit 3
fi

TARGET_VERSION="$RUNLAYER_VERSION"
if [[ -z "$TARGET_VERSION" ]]; then
    log "Checking latest version on PyPI..."
    TARGET_VERSION=$(curl -s "https://pypi.org/pypi/runlayer/json" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)
    if [[ -n "$TARGET_VERSION" ]]; then
        log "Latest version on PyPI: $TARGET_VERSION"
    else
        log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
    fi
fi

USER_CACHE_DIR="$USER_HOME/.cache"
UV_CACHE_DIR="$USER_CACHE_DIR/uv"
if [[ -d "$USER_CACHE_DIR" ]]; then
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR" 2>/dev/null || true
fi

CURRENT_VERSION=""
CURRENT_VERSION_NUMBER=""
if [[ -x "$UVX_PATH" ]]; then
    CURRENT_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" UV_CACHE_DIR="$UV_CACHE_DIR" "$UVX_PATH" runlayer --version 2>/dev/null || echo "")
    CURRENT_VERSION_NUMBER=$(echo "$CURRENT_VERSION" | awk '{print $3}')
fi

NEEDS_INSTALL=false
if [[ -z "$CURRENT_VERSION" ]]; then
    log "runlayer CLI not cached yet"
    NEEDS_INSTALL=true
elif [[ -n "$TARGET_VERSION" && "$CURRENT_VERSION_NUMBER" != "$TARGET_VERSION" ]]; then
    log "runlayer CLI version mismatch: have '$CURRENT_VERSION_NUMBER', want '$TARGET_VERSION'"
    NEEDS_INSTALL=true
else
    log "runlayer CLI available: $CURRENT_VERSION (up to date)"
fi

if [[ "$NEEDS_INSTALL" == "true" ]]; then
    log "Pre-caching runlayer CLI for user..."
    PACKAGE="runlayer"
    [[ -n "$RUNLAYER_VERSION" ]] && PACKAGE="runlayer==$RUNLAYER_VERSION"
    
    mkdir -p "$UV_CACHE_DIR"
    chown -R "$CONSOLE_USER" "$USER_CACHE_DIR"
    
    UV_WRAPPER=$(mktemp)
    cat > "$UV_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UV_PATH" tool install "$PACKAGE" --force
WRAPPER
    chmod +x "$UV_WRAPPER"
    
    if ! /bin/launchctl asuser "$USER_UID" "$UV_WRAPPER"; then
        log "ERROR: runlayer CLI installation failed"
        rm -f "$UV_WRAPPER"
        exit 3
    fi
    rm -f "$UV_WRAPPER"
    
    chown -R "$CONSOLE_USER" "$UV_CACHE_DIR" 2>/dev/null || true
    
    INSTALLED_VERSION=$(/bin/launchctl asuser "$USER_UID" /usr/bin/env HOME="$USER_HOME" "$UVX_PATH" runlayer --version 2>/dev/null || echo 'unknown')
    log "runlayer CLI installed: $INSTALLED_VERSION"
fi

log "Checking if user credentials already exist..."
CHECK_WRAPPER=$(mktemp)
cat > "$CHECK_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer credentials check --host "$RUNLAYER_HOST" --skip-org-check
WRAPPER
chmod +x "$CHECK_WRAPPER"

if /bin/launchctl asuser "$USER_UID" "$CHECK_WRAPPER" 2>/dev/null; then
    ALREADY_CONFIGURED=true
    log "User credentials already stored for $RUNLAYER_HOST"
else
    log "User credentials not stored, will enroll"
fi
rm -f "$CHECK_WRAPPER"

if [[ "$ALREADY_CONFIGURED" != "true" ]]; then
    DEVICE_USERNAME="\${ENROLLMENT_USERNAME:-$CONSOLE_USER}"
    log "Enrolling device for user: $DEVICE_USERNAME"

    ENROLL_WRAPPER=$(mktemp)
    cat > "$ENROLL_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer credentials enroll "$ENROLLMENT_API_KEY" --host "$RUNLAYER_HOST" --username "$DEVICE_USERNAME" --device-name "$DEVICE_NAME"
WRAPPER
    chmod +x "$ENROLL_WRAPPER"

    if ! /bin/launchctl asuser "$USER_UID" "$ENROLL_WRAPPER"; then
        log "ERROR: Enrollment failed"
        rm -f "$ENROLL_WRAPPER"
        exit 1
    fi
    rm -f "$ENROLL_WRAPPER"

    log "Enrollment successful"
fi

log "Running setup sync..."

SYNC_WRAPPER=$(mktemp)
cat > "$SYNC_WRAPPER" << WRAPPER
#!/bin/bash
export HOME="$USER_HOME"
export UV_CACHE_DIR="$UV_CACHE_DIR"
"$UVX_PATH" runlayer setup sync --yes
WRAPPER
chmod +x "$SYNC_WRAPPER"

if /bin/launchctl asuser "$USER_UID" "$SYNC_WRAPPER"; then
    log "Setup sync completed successfully"
else
    log "WARNING: Setup sync failed (exit code: $?)"
fi
rm -f "$SYNC_WRAPPER"

chown -R "$CONSOLE_USER" "$USER_HOME/.runlayer" 2>/dev/null || true

log "=========================================="
log "SUCCESS: Config sync execution complete"
log "=========================================="

exit 0
`;
  const windowsSyncScript = `
#Requires -Version 5.1
# MDM Script: Automatic Configuration Provisioning for Windows
# Exit codes: 0=success, 1=general failure, 2=network, 3=installation

# =============================================================================
# CONFIGURATION
# =============================================================================
# Template markers are replaced by the script generator. If deploying manually,
# replace {{VAR}} placeholders with your actual values.

$RUNLAYER_HOST = "{{RUNLAYER_HOST}}"
$ENROLLMENT_API_KEY = "{{ENROLLMENT_API_KEY}}"
$ENROLLMENT_USERNAME = "{{ENROLLMENT_USERNAME}}"
$ENROLLMENT_DEVICE_NAME = "{{ENROLLMENT_DEVICE_NAME}}"
$RUNLAYER_VERSION = "{{RUNLAYER_VERSION}}"
$SCAN_INTERVAL_MINUTES = "{{SCAN_INTERVAL_MINUTES}}"

# =============================================================================
# SCRIPT - No changes needed below
# =============================================================================

$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\auto_provisioning.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

function Test-NetworkConnectivity {
    try {
        $response = Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

Write-Log "=========================================="
Write-Log "Runlayer Config Sync (Windows)"
Write-Log "Host: $(if ($RUNLAYER_HOST) { $RUNLAYER_HOST } else { 'NOT SET' })"
Write-Log "=========================================="

if (-not $RUNLAYER_HOST) {
    Write-Log "ERROR: RUNLAYER_HOST is not set. Edit the script to add your Runlayer URL."
    exit 1
}

if (-not $ENROLLMENT_API_KEY) {
    Write-Log "ERROR: ENROLLMENT_API_KEY is not set. Edit the script to add your enrollment key."
    exit 1
}

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$userProfile = $env:USERPROFILE
$deviceName = if ($ENROLLMENT_DEVICE_NAME) { $ENROLLMENT_DEVICE_NAME } else { $env:COMPUTERNAME }

Write-Log "Current user: $currentUser"
Write-Log "User profile: $userProfile"
Write-Log "Device name: $deviceName"

$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

$alreadyConfigured = $false

Write-Log "Checking network connectivity..."
if (-not (Test-NetworkConnectivity)) {
    Write-Log "ERROR: No network connectivity"
    exit 2
}
Write-Log "Network OK"

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV to $localBinPath ..."
    
    try {
        $installerUrl = "https://astral.sh/uv/install.ps1"
        Invoke-RestMethod $installerUrl | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 3
    }
}

if (Test-Path $uvPath) {
    $uvVersion = & $uvPath --version 2>$null
    Write-Log "UV installed: $uvVersion"
} else {
    Write-Log "ERROR: UV not found at $uvPath after installation"
    exit 3
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$targetVersion = $RUNLAYER_VERSION
if (-not $targetVersion) {
    Write-Log "Checking latest version on PyPI..."
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
        Write-Log "Latest version on PyPI: $targetVersion"
    } catch {
        Write-Log "WARNING: Could not fetch latest version from PyPI, will install/upgrade anyway"
        $targetVersion = ""
    }
}

$currentVersion = ""
$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $currentVersion = & $uvxPath runlayer --version 2>$null
        if ($currentVersion -match '\\d+\\.\\d+\\.\\d+') {
            $currentVersionNumber = $Matches[0]
        }
    } catch {
        $currentVersion = ""
    }
}

$needsInstall = $false
if (-not $currentVersion) {
    Write-Log "runlayer CLI not installed"
    $needsInstall = $true
} elseif ($targetVersion -and $currentVersionNumber -ne $targetVersion) {
    Write-Log "runlayer CLI version mismatch: have '$currentVersionNumber', want '$targetVersion'"
    $needsInstall = $true
} else {
    Write-Log "runlayer CLI already installed: $currentVersion (up to date)"
}

if ($needsInstall) {
    Write-Log "Installing runlayer CLI..."
    
    $package = "runlayer"
    if ($RUNLAYER_VERSION) {
        $package = "runlayer==$RUNLAYER_VERSION"
    }
    
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($installExitCode -ne 0) {
        Write-Log "ERROR: runlayer CLI installation failed (exit code: $installExitCode)"
        exit 3
    }
    
    $installedVersion = & $uvxPath runlayer --version 2>$null
    Write-Log "runlayer CLI installed: $installedVersion"
}

Write-Log "Checking if user credentials already exist..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer credentials check --host $RUNLAYER_HOST --skip-org-check 2>$null
    $checkExitCode = $LASTEXITCODE
} catch {
    $checkExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}

if ($checkExitCode -eq 0) {
    $alreadyConfigured = $true
    Write-Log "User credentials already stored for $RUNLAYER_HOST"
} else {
    Write-Log "User credentials not stored, will enroll"
}

if (-not $alreadyConfigured) {
    $deviceUsername = if ($ENROLLMENT_USERNAME) { $ENROLLMENT_USERNAME } else { $env:USERNAME }
    Write-Log "Enrolling device for user: $deviceUsername"

    try {
        $ErrorActionPreference = "Continue"
        & $uvxPath runlayer credentials enroll $ENROLLMENT_API_KEY --host $RUNLAYER_HOST --username $deviceUsername --device-name $deviceName 2>$null
        $enrollExitCode = $LASTEXITCODE
    } catch {
        $enrollExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }

    if ($enrollExitCode -ne 0) {
        Write-Log "ERROR: Enrollment failed (exit code: $enrollExitCode)"
        exit 1
    }

    Write-Log "Enrollment successful"
}

Write-Log "Running setup sync..."

try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer setup sync --yes 2>$null
    $syncExitCode = $LASTEXITCODE
} catch {
    Write-Log "WARNING: Setup sync failed: $_"
    $syncExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}
if ($syncExitCode -eq 0) {
    Write-Log "Setup sync completed successfully"
} else {
    Write-Log "WARNING: Setup sync failed (exit code: $syncExitCode)"
}

try {
    Write-Log "Setting up scheduled task for recurring sync..."

    $scriptDir = "$env:ProgramData\\Runlayer\\Scripts"
    $recurringScript = "$scriptDir\\runlayer-config-sync.ps1"
    New-Item -Path $scriptDir -ItemType Directory -Force | Out-Null

    $recurringScriptContent = @'
$ErrorActionPreference = "Stop"
$LogFile = "$env:ProgramData\\Runlayer\\auto_provisioning.log"

function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $Message"
    Write-Host $logMessage
    $logDir = Split-Path $LogFile -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    Add-Content -Path $LogFile -Value $logMessage
}

Write-Log "=========================================="
Write-Log "Config Sync Scheduled Run"
Write-Log "=========================================="

$userProfile = $env:USERPROFILE
$uvPath = "$userProfile\\.local\\bin\\uv.exe"
$uvxPath = "$userProfile\\.local\\bin\\uvx.exe"
$localBinPath = "$userProfile\\.local\\bin"

try {
    Invoke-WebRequest -Uri "https://pypi.org" -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop | Out-Null
} catch {
    Write-Log "WARNING: No network connectivity, skipping"
    exit 0
}

if (-not (Test-Path $uvPath)) {
    Write-Log "Installing UV..."
    try {
        Invoke-RestMethod "https://astral.sh/uv/install.ps1" | Invoke-Expression
    } catch {
        Write-Log "ERROR: UV installation failed: $_"
        exit 3
    }
}

if (-not (Test-Path $uvPath)) {
    Write-Log "ERROR: UV not found at $uvPath"
    exit 3
}

if ($env:PATH -notlike "*$localBinPath*") {
    $env:PATH = "$localBinPath;$env:PATH"
}

$pinnedVersion = "__RUNLAYER_VERSION__"
$targetVersion = $pinnedVersion
if (-not $targetVersion) {
    try {
        $pypiResponse = Invoke-RestMethod -Uri "https://pypi.org/pypi/runlayer/json" -TimeoutSec 10
        $targetVersion = $pypiResponse.info.version
    } catch {
        $targetVersion = ""
    }
}

$currentVersionNumber = ""
if (Test-Path $uvxPath) {
    try {
        $vOut = & $uvxPath runlayer --version 2>$null
        if ($vOut -match '\\d+\\.\\d+\\.\\d+') { $currentVersionNumber = $Matches[0] }
    } catch {}
}

if (-not $currentVersionNumber -or ($targetVersion -and $currentVersionNumber -ne $targetVersion)) {
    Write-Log "Updating runlayer CLI..."
    $package = "runlayer"
    if ($pinnedVersion) { $package = "runlayer==$pinnedVersion" }
    try {
        $ErrorActionPreference = "Continue"
        & $uvPath tool install $package --force 2>$null
        $installExitCode = $LASTEXITCODE
    } catch {
        $installExitCode = 1
    } finally {
        $ErrorActionPreference = "Stop"
    }
    if ($installExitCode -ne 0) {
        Write-Log "WARNING: CLI update failed (exit code: $installExitCode)"
    } else {
        $installedVersion = & $uvxPath runlayer --version 2>$null
        Write-Log "runlayer CLI updated: $installedVersion"
    }
}

Write-Log "Running setup sync..."
try {
    $ErrorActionPreference = "Continue"
    & $uvxPath runlayer setup sync --yes 2>$null
    $syncExitCode = $LASTEXITCODE
} catch {
    Write-Log "WARNING: Setup sync failed: $_"
    $syncExitCode = 1
} finally {
    $ErrorActionPreference = "Stop"
}
if ($syncExitCode -eq 0) {
    Write-Log "Setup sync completed successfully"
} else {
    Write-Log "WARNING: Setup sync failed (exit code: $syncExitCode)"
}

Write-Log "=========================================="
Write-Log "Scheduled run complete"
Write-Log "=========================================="
'@

    $recurringScriptContent = $recurringScriptContent.Replace('__RUNLAYER_VERSION__', $RUNLAYER_VERSION)
    Set-Content -Path $recurringScript -Value $recurringScriptContent -Force
    Write-Log "Recurring script saved to $recurringScript"

    $intervalMinutes = if ($SCAN_INTERVAL_MINUTES) { [int]$SCAN_INTERVAL_MINUTES } else { 60 }

    $action = New-ScheduledTaskAction -Execute "conhost.exe" \`
        -Argument "--headless powershell.exe -NoProfile -NonInteractive -File \`"$recurringScript\`""

    $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) \`
        -RepetitionInterval (New-TimeSpan -Minutes $intervalMinutes) \`
        -RepetitionDuration (New-TimeSpan -Days 9999)

    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries \`
        -DontStopIfGoingOnBatteries -StartWhenAvailable -Hidden

    Register-ScheduledTask -TaskName "RunlayerConfigSync" \`
        -Action $action \`
        -Trigger $trigger \`
        -Settings $settings \`
        -User $currentUser \`
        -RunLevel Limited \`
        -Force

    Write-Log "Scheduled task 'RunlayerConfigSync' registered (every $intervalMinutes minutes)"
} catch {
    Write-Log "WARNING: Could not set up scheduled task: $_"
}

Write-Log "=========================================="
Write-Log "SUCCESS: Config sync execution complete"
Write-Log "=========================================="

exit 0
`;
  const isEnrollmentMode = mode === "hooks" || mode === "sync";
  const isHooksMode = mode === "hooks";
  const isWindows = scriptType === "windows";
  const [config, setConfig] = useState({
    runlayerHost: "https://your-instance.runlayer.com",
    apiKey: "",
    enrollmentUsername: "",
    deviceName: "",
    runlayerVersion: "",
    hooksClient: "cursor",
    scanInterval: "",
    intuneDeployMethod: "platform"
  });
  const [copied, setCopied] = useState(false);
  const [showScript, setShowScript] = useState(false);
  const handleChange = field => e => {
    setConfig(prev => ({
      ...prev,
      [field]: e.target.value
    }));
    setCopied(false);
  };
  const isValid = config.runlayerHost && config.apiKey;
  const generateScript = () => {
    let template = "";
    if (mode === "hooks") {
      if (scriptType === "macos") {
        template = macosHooksScript;
      }
    } else if (mode === "sync") {
      if (scriptType === "macos") {
        template = macosSyncScript;
      } else if (scriptType === "windows") {
        template = config.intuneDeployMethod === "remediation" ? windowsSyncRemediationScript : windowsSyncScript;
      }
    } else {
      if (scriptType === "macos") {
        template = macosWatchScript;
      } else if (scriptType === "windows") {
        template = config.intuneDeployMethod === "remediation" ? windowsWatchRemediationScript : windowsWatchScript;
      }
    }
    let script = template.replace(/\{\{RUNLAYER_HOST\}\}/g, config.runlayerHost).replace(/\{\{RUNLAYER_VERSION\}\}/g, config.runlayerVersion).replace(/\{\{SCAN_INTERVAL_MINUTES\}\}/g, config.scanInterval);
    if (isEnrollmentMode) {
      script = script.replace(/\{\{ENROLLMENT_API_KEY\}\}/g, config.apiKey).replace(/\{\{ENROLLMENT_USERNAME\}\}/g, config.enrollmentUsername).replace(/\{\{ENROLLMENT_DEVICE_NAME\}\}/g, config.deviceName);
      if (isHooksMode) {
        script = script.replace(/\{\{HOOKS_CLIENT\}\}/g, config.hooksClient);
      }
    } else {
      script = script.replace(/\{\{ORG_API_KEY\}\}/g, config.apiKey).replace(/\{\{DEVICE_NAME\}\}/g, config.deviceName);
    }
    return script;
  };
  const copyToClipboard = () => {
    navigator.clipboard.writeText(generateScript()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const inputClasses = "w-full px-3 py-2 text-sm rounded-lg border border-zinc-300 dark:border-zinc-700 bg-transparent focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent";
  const selectClasses = "w-full px-3 py-2 text-sm rounded-lg border border-zinc-300 dark:border-zinc-700 bg-transparent focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent";
  const inputStyle = {
    color: "inherit"
  };
  const labelClasses = "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1";
  const helpTextClasses = "text-xs text-zinc-500 dark:text-zinc-400 mt-1";
  const title = mode === "hooks" ? "Hooks Script Configuration" : mode === "sync" ? "Config Sync Script Configuration" : "AI Watch Script Configuration";
  const description = mode === "hooks" ? "Fill in your settings to generate a customized hooks installation script" : mode === "sync" ? "Fill in your settings to generate a customized configuration sync script" : "Fill in your settings to generate a customized AI Watch script";
  return <div className="not-prose border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden">
      {}
      <div className="bg-zinc-50 dark:bg-zinc-900 px-4 py-3 border-b border-zinc-200 dark:border-zinc-800">
        <h4 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
          {title}
        </h4>
        <p className="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
          {description}
        </p>
      </div>

      {}
      <div className="p-4 space-y-4">
        {}
        <div className="grid gap-4 sm:grid-cols-2">
          <div>
            <label className={labelClasses}>
              Runlayer Host URL <span className="text-red-500">*</span>
            </label>
            <input type="url" value={config.runlayerHost} onChange={handleChange("runlayerHost")} placeholder="https://your-instance.runlayer.com" className={inputClasses} style={inputStyle} />
            <p className={helpTextClasses}>Your Runlayer instance URL</p>
          </div>

          <div>
            <label className={labelClasses}>
              {isEnrollmentMode ? "Enrollment API Key" : "Organization API Key"} <span className="text-red-500">*</span>
            </label>
            <input type="text" value={config.apiKey} onChange={handleChange("apiKey")} placeholder={isEnrollmentMode ? "rl_enroll_..." : "rl_org_..."} className={inputClasses} style={inputStyle} />
            <p className={helpTextClasses}>
              {isEnrollmentMode ? "Created in Settings → Enrollment Keys" : "Created in Settings → Organization API keys (AI Watch Scan role)"}
            </p>
          </div>
        </div>

        {}
        {mode === "hooks" && <div>
            <label className={labelClasses}>Target Client</label>
            <select value={config.hooksClient} onChange={handleChange("hooksClient")} className={selectClasses} style={inputStyle}>
              <option value="cursor">Cursor</option>
              <option value="all">All Clients</option>
            </select>
            <p className={helpTextClasses}>
              Which client to install hooks for
            </p>
          </div>}

        {}
        {isEnrollmentMode && <div>
            <label className={labelClasses}>Enrollment Username</label>
            <input type="text" value={config.enrollmentUsername} onChange={handleChange("enrollmentUsername")} placeholder="Leave empty for device username" className={inputClasses} style={inputStyle} />
            <p className={helpTextClasses}>
              MDM variable like $EMAIL or $USER_PRINCIPAL_NAME
            </p>
          </div>}

        {}
        <div>
          <label className={labelClasses}>Device Name</label>
          <input type="text" value={config.deviceName} onChange={handleChange("deviceName")} placeholder="Leave empty for computer name" className={inputClasses} style={inputStyle} />
          <p className={helpTextClasses}>
            MDM variable like $DEVICE_NAME or $SERIAL_NUMBER
          </p>
        </div>

        {}
        {isWindows && mode === "watch" && <div>
            <label className={labelClasses}>Deployment Method</label>
            <select value={config.intuneDeployMethod} onChange={handleChange("intuneDeployMethod")} className={selectClasses} style={inputStyle}>
              <option value="platform">Platform Script (creates scheduled task)</option>
              <option value="remediation">Remediation (Intune handles scheduling)</option>
            </select>
            <p className={helpTextClasses}>
              Remediation requires Intune P2 license but avoids creating a visible scheduled task
            </p>
          </div>}

        {}
        {isWindows && config.intuneDeployMethod === "platform" && <div>
            <label className={labelClasses}>Scan Interval (minutes)</label>
            <input type="number" value={config.scanInterval} onChange={handleChange("scanInterval")} placeholder="60" min="5" className={inputClasses} style={inputStyle} />
            <p className={helpTextClasses}>
              How often the scheduled task runs (default: 60 minutes)
            </p>
          </div>}

        {}
        <details className="group">
          <summary className="cursor-pointer text-sm font-medium text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-200">
            <span className="ml-1">Optional Settings</span>
          </summary>
          <div className="mt-3 grid gap-4 sm:grid-cols-2">
            <div>
              <label className={labelClasses}>Pin Version</label>
              <input type="text" value={config.runlayerVersion} onChange={handleChange("runlayerVersion")} placeholder="Leave empty for latest" className={inputClasses} style={inputStyle} />
              <p className={helpTextClasses}>
                e.g., 0.9.0 — leave empty for latest
              </p>
            </div>
          </div>
        </details>
      </div>

      {}
      <div className="bg-zinc-50 dark:bg-zinc-900 px-4 py-3 border-t border-zinc-200 dark:border-zinc-800 flex items-center justify-between">
        <div className="text-xs text-zinc-500 dark:text-zinc-400">
          {isValid ? <span className="text-green-600 dark:text-green-400">
              ✓ Ready to generate
            </span> : <span className="text-amber-600 dark:text-amber-400">
              Fill in required fields
            </span>}
        </div>
        <div className="flex gap-2">
          <button onClick={() => setShowScript(!showScript)} disabled={!isValid} className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${isValid ? "bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-zinc-100 hover:bg-zinc-300 dark:hover:bg-zinc-600" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-400 dark:text-zinc-600 cursor-not-allowed"}`}>
            {showScript ? "Hide Script" : "Preview Script"}
          </button>
          <button onClick={copyToClipboard} disabled={!isValid} className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${isValid ? "bg-purple-600 text-white hover:bg-purple-700" : "bg-purple-300 dark:bg-purple-900 text-purple-100 dark:text-purple-700 cursor-not-allowed"}`}>
            {copied ? "✓ Copied!" : "Copy Script"}
          </button>
        </div>
      </div>

      {}
      {showScript && isValid && <div className="border-t border-zinc-200 dark:border-zinc-800">
          <pre className="p-4 text-xs overflow-x-auto bg-zinc-950 text-zinc-300 max-h-96">
            <code>{generateScript()}</code>
          </pre>
        </div>}
    </div>;
};

## Prerequisites

* Iru/Kandji admin access
* Enrollment key from Runlayer
* At least one Blueprint configured with enrolled devices

<Accordion title="Creating an Enrollment Key">
  Enrollment keys allow devices to automatically register with Runlayer and obtain API credentials.

  <img className="block" src="https://mintcdn.com/anysource/6y8VzDSWjckfUDpB/images/enrollment-keys-list.png?fit=max&auto=format&n=6y8VzDSWjckfUDpB&q=85&s=e6e9bf9618213fc75b9e4db86c44f28b" alt="Enrollment Keys List" width="1524" height="898" data-path="images/enrollment-keys-list.png" />

  <Steps>
    <Step title="Navigate to Enrollment Keys">
      Go to **Settings** in the Runlayer dashboard and select the **Enrollment Keys** tab
    </Step>

    <Step title="Create a New Key">
      Click **+ Create Enrollment Key**

      <img className="block" src="https://mintcdn.com/anysource/6y8VzDSWjckfUDpB/images/enrollment-key-create.png?fit=max&auto=format&n=6y8VzDSWjckfUDpB&q=85&s=18f53ec6ae775870c77a40f3a2b21cc3" alt="Create Enrollment Key" width="1022" height="636" data-path="images/enrollment-key-create.png" />
    </Step>

    <Step title="Configure the Key">
      * **Name** (required): Enter a descriptive name (e.g., "Production MDM")
      * **Description** (optional): Add context about the key's purpose
    </Step>

    <Step title="Copy the Key">
      Copy the generated key (starts with `rl_enroll_`) and store it securely

      <img className="block" src="https://mintcdn.com/anysource/6y8VzDSWjckfUDpB/images/enrollment-key-created.png?fit=max&auto=format&n=6y8VzDSWjckfUDpB&q=85&s=3bb1fd346da4bd7717d1dbef8be9a56d" alt="Enrollment Key Created" width="1110" height="544" data-path="images/enrollment-key-created.png" />
    </Step>
  </Steps>

  <Warning>
    Enrollment keys are shown only once. Store them securely and treat them like passwords.
  </Warning>
</Accordion>

## Deployment Steps

<Steps>
  <Step title="Generate the Script">
    Fill in your settings below to generate a deployment script.

    **Iru/Kandji-specific configuration tips:**

    * `ENROLLMENT_USERNAME`: Use an Iru/Kandji variable for the user's identity if available. Leave empty to use the device username.
    * `ENROLLMENT_DEVICE_NAME`: Use an Iru/Kandji variable for the device name. Leave empty to use the computer name.

    <MdmScriptGenerator mode="sync" scriptType="macos" />
  </Step>

  <Step title="Add a Custom Script Library Item">
    1. Navigate to the **Library** section in Iru/Kandji
    2. Click **Add New** → **Custom Script** → **Add & Configure**
    3. Provide a **Name** (e.g., "Runlayer Config Sync")
    4. Assign to your target **Blueprint(s)**
    5. Set **Execution Frequency** to **Run daily** (recommended) or **Run every 15 minutes** for more frequent syncs
    6. Paste the generated script into the **Audit Script** field
    7. Click **Save**
  </Step>
</Steps>

## Verification

Open a client application (e.g., Cursor) on a target device and confirm the synced MCP servers appear. Check the Custom Script's **Status** tab in Iru/Kandji for execution results. If something went wrong, check `/var/log/runlayer/auto_provisioning.log` on the device.

You can force an immediate check-in on a test Mac by running `sudo kandji checkin` in Terminal.
