- Add ROOH credit (www.rooh.red) to banner, summary, and README - Step 1: Offer to install python3 and curl instead of hard-failing - Step 3: Detect missing Claude CLI and offer to install via npm - Step 3: Detect not-signed-in CLI and offer interactive OAuth sign-in - Step 3: Provide clear instructions and exit paths at every decision point - Update README with correct git clone URL and wizard capabilities - All install steps now require user confirmation before proceeding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1070 lines
39 KiB
Bash
Executable File
1070 lines
39 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# OAuth Fix for OpenClaw — Interactive Setup Wizard
|
|
# ============================================================================
|
|
# Configures automatic Anthropic OAuth token refresh for OpenClaw.
|
|
# Detects paths, installs the sync service, and configures the Anthropic model.
|
|
#
|
|
# Usage:
|
|
# ./setup.sh # Interactive mode
|
|
# ./setup.sh --uninstall # Remove everything
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
VERSION="1.0.0"
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# --- Colors ---
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
header() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${NC}\n"; }
|
|
|
|
ask() {
|
|
local prompt="$1"
|
|
local default="${2:-}"
|
|
local result
|
|
if [ -n "$default" ]; then
|
|
read -rp "$(echo -e "${BOLD}$prompt${NC} [$default]: ")" result
|
|
echo "${result:-$default}"
|
|
else
|
|
read -rp "$(echo -e "${BOLD}$prompt${NC}: ")" result
|
|
echo "$result"
|
|
fi
|
|
}
|
|
|
|
confirm() {
|
|
local prompt="$1"
|
|
local default="${2:-Y}"
|
|
local result
|
|
read -rp "$(echo -e "${BOLD}$prompt${NC} [${default}]: ")" result
|
|
result="${result:-$default}"
|
|
[[ "$result" =~ ^[Yy] ]]
|
|
}
|
|
|
|
# --- Uninstall ---
|
|
if [ "${1:-}" = "--uninstall" ]; then
|
|
bash "$SCRIPT_DIR/scripts/uninstall.sh"
|
|
exit $?
|
|
fi
|
|
|
|
# ============================================================================
|
|
# BANNER
|
|
# ============================================================================
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}"
|
|
echo " ╔══════════════════════════════════════════════════════╗"
|
|
echo " ║ OAuth Fix for OpenClaw + Claude Max ║"
|
|
echo " ║ Automatic Anthropic Token Refresh ║"
|
|
echo " ║ v${VERSION} ║"
|
|
echo " ║ ║"
|
|
echo " ║ Created by ROOH — www.rooh.red ║"
|
|
echo " ╚══════════════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
echo -e "${DIM} Keeps your Anthropic OAuth tokens fresh by syncing"
|
|
echo -e " Claude Code CLI's auto-refreshed credentials to OpenClaw.${NC}"
|
|
echo ""
|
|
|
|
# ============================================================================
|
|
# STEP 1: Prerequisites
|
|
# ============================================================================
|
|
header "Step 1: Checking Prerequisites"
|
|
|
|
MISSING=0
|
|
|
|
# --- Required (cannot be installed by wizard) ---
|
|
for cmd in systemctl docker; do
|
|
if command -v "$cmd" &>/dev/null; then
|
|
success "$cmd found"
|
|
else
|
|
error "$cmd not found — required, cannot be installed by this wizard"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
done
|
|
|
|
# Check docker compose (v2)
|
|
if docker compose version &>/dev/null; then
|
|
success "docker compose found ($(docker compose version --short 2>/dev/null || echo 'v2'))"
|
|
else
|
|
error "docker compose v2 not found — required"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
|
|
# --- python3 (offer to install) ---
|
|
if command -v python3 &>/dev/null; then
|
|
success "python3 found"
|
|
else
|
|
warn "python3 not found (required for JSON processing)"
|
|
if confirm " Install python3 now?" "Y"; then
|
|
apt-get install -y python3 2>&1 | tail -3
|
|
if command -v python3 &>/dev/null; then
|
|
success "python3 installed"
|
|
else
|
|
error "python3 installation failed"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
else
|
|
error "python3 is required — install it manually and re-run"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
fi
|
|
|
|
# --- curl (offer to install) ---
|
|
if command -v curl &>/dev/null; then
|
|
success "curl found"
|
|
else
|
|
warn "curl not found"
|
|
if confirm " Install curl now?" "Y"; then
|
|
apt-get install -y curl 2>&1 | tail -3
|
|
if command -v curl &>/dev/null; then
|
|
success "curl installed"
|
|
else
|
|
error "curl installation failed"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
else
|
|
error "curl is required — install it manually and re-run"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
fi
|
|
|
|
# --- inotifywait (offer to install, optional) ---
|
|
if command -v inotifywait &>/dev/null; then
|
|
success "inotifywait found"
|
|
USE_INOTIFY=true
|
|
else
|
|
warn "inotifywait not found (inotify-tools package)"
|
|
echo -e " ${DIM}Provides real-time credential sync. Without it, a 6-hour timer is used instead.${NC}"
|
|
if confirm " Install inotify-tools now?" "Y"; then
|
|
apt-get install -y inotify-tools 2>&1 | tail -3
|
|
if command -v inotifywait &>/dev/null; then
|
|
success "inotifywait installed"
|
|
USE_INOTIFY=true
|
|
else
|
|
warn "Installation failed. Will use timer-based fallback."
|
|
USE_INOTIFY=false
|
|
fi
|
|
else
|
|
warn "Will use timer-based fallback (6-hour refresh cycle)"
|
|
USE_INOTIFY=false
|
|
fi
|
|
fi
|
|
|
|
if [ "$MISSING" -gt 0 ]; then
|
|
error "$MISSING prerequisite(s) missing. Please install them and re-run."
|
|
exit 1
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 2: Detect OpenClaw Installation
|
|
# ============================================================================
|
|
header "Step 2: Detecting OpenClaw Installation"
|
|
|
|
# Find openclaw.json
|
|
OPENCLAW_CONFIG_DIR=""
|
|
for path in \
|
|
"${OPENCLAW_CONFIG_DIR:-}" \
|
|
/root/.openclaw \
|
|
/home/*/.openclaw; do
|
|
if [ -f "$path/openclaw.json" ] 2>/dev/null; then
|
|
OPENCLAW_CONFIG_DIR="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$OPENCLAW_CONFIG_DIR" ]; then
|
|
# Try docker inspect
|
|
GATEWAY=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i 'openclaw.*gateway' | head -1)
|
|
if [ -n "$GATEWAY" ]; then
|
|
OPENCLAW_CONFIG_DIR=$(docker inspect "$GATEWAY" --format '{{range .Mounts}}{{if eq .Destination "/home/node/.openclaw"}}{{.Source}}{{end}}{{end}}' 2>/dev/null)
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$OPENCLAW_CONFIG_DIR" ] || [ ! -f "$OPENCLAW_CONFIG_DIR/openclaw.json" ]; then
|
|
error "Cannot find OpenClaw installation"
|
|
OPENCLAW_CONFIG_DIR=$(ask "Enter OpenClaw config directory (contains openclaw.json)")
|
|
if [ ! -f "$OPENCLAW_CONFIG_DIR/openclaw.json" ]; then
|
|
error "openclaw.json not found in $OPENCLAW_CONFIG_DIR"
|
|
exit 1
|
|
fi
|
|
fi
|
|
success "Config: $OPENCLAW_CONFIG_DIR/openclaw.json"
|
|
|
|
# Find docker-compose directory
|
|
COMPOSE_DIR=""
|
|
for path in \
|
|
/root/openclaw \
|
|
"$(dirname "$OPENCLAW_CONFIG_DIR")/openclaw" \
|
|
/opt/openclaw; do
|
|
if [ -f "$path/docker-compose.yml" ] || [ -f "$path/docker-compose.yaml" ] || [ -f "$path/compose.yml" ]; then
|
|
COMPOSE_DIR="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$COMPOSE_DIR" ]; then
|
|
COMPOSE_DIR=$(ask "Enter OpenClaw docker-compose directory" "/root/openclaw")
|
|
fi
|
|
success "Compose: $COMPOSE_DIR"
|
|
|
|
# Find .env file
|
|
ENV_FILE="$COMPOSE_DIR/.env"
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
warn ".env not found at $ENV_FILE"
|
|
ENV_FILE=$(ask "Enter .env file path" "$COMPOSE_DIR/.env")
|
|
fi
|
|
success "Env: $ENV_FILE"
|
|
|
|
# Find gateway container
|
|
GATEWAY_CONTAINER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i 'openclaw.*gateway' | head -1)
|
|
if [ -z "$GATEWAY_CONTAINER" ]; then
|
|
GATEWAY_CONTAINER=$(ask "Enter gateway container name" "openclaw-openclaw-gateway-1")
|
|
fi
|
|
success "Gateway: $GATEWAY_CONTAINER"
|
|
|
|
# Derive oauth.json path
|
|
OPENCLAW_OAUTH_FILE="$OPENCLAW_CONFIG_DIR/credentials/oauth.json"
|
|
success "OAuth file: $OPENCLAW_OAUTH_FILE"
|
|
|
|
# ============================================================================
|
|
# STEP 3: Claude CLI & Credentials
|
|
# ============================================================================
|
|
header "Step 3: Claude CLI & Credentials"
|
|
|
|
CLAUDE_CREDS_FILE=""
|
|
EARLY_CLI_MODE=""
|
|
EARLY_CLI_CONTAINER=""
|
|
CLI_INSTALLED=false
|
|
|
|
# --- Phase 1: Search for existing credentials ---
|
|
info "Searching for Claude CLI credentials..."
|
|
|
|
# Strategy 1: Find claude container and check mounts
|
|
CLAUDE_CONTAINER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i 'claude' | head -1)
|
|
if [ -n "$CLAUDE_CONTAINER" ]; then
|
|
info "Found Claude container: $CLAUDE_CONTAINER"
|
|
MOUNT_SOURCE=$(docker inspect "$CLAUDE_CONTAINER" --format '{{range .Mounts}}{{if or (eq .Destination "/root") (eq .Destination "/root/.claude") (eq .Destination "/home/node/.claude")}}{{.Source}}{{end}}{{end}}' 2>/dev/null)
|
|
if [ -n "$MOUNT_SOURCE" ]; then
|
|
for suffix in "/.claude/.credentials.json" "/.credentials.json"; do
|
|
if [ -f "${MOUNT_SOURCE}${suffix}" ]; then
|
|
CLAUDE_CREDS_FILE="${MOUNT_SOURCE}${suffix}"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# Strategy 2: Search workspace directories
|
|
if [ -z "$CLAUDE_CREDS_FILE" ]; then
|
|
for path in "$OPENCLAW_CONFIG_DIR"/workspaces/*/config/.claude/.credentials.json; do
|
|
if [ -f "$path" ]; then
|
|
CLAUDE_CREDS_FILE="$path"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Strategy 3: Direct paths
|
|
if [ -z "$CLAUDE_CREDS_FILE" ]; then
|
|
for path in \
|
|
/root/.claude/.credentials.json \
|
|
"$HOME/.claude/.credentials.json"; do
|
|
if [ -f "$path" ]; then
|
|
CLAUDE_CREDS_FILE="$path"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# --- Phase 2: If no credentials found, detect CLI and help user ---
|
|
if [ -z "$CLAUDE_CREDS_FILE" ] || [ ! -f "$CLAUDE_CREDS_FILE" ]; then
|
|
warn "No Claude CLI credentials found"
|
|
echo ""
|
|
|
|
# Detect if Claude CLI is installed
|
|
if [ -n "$CLAUDE_CONTAINER" ] && docker exec "$CLAUDE_CONTAINER" which claude &>/dev/null; then
|
|
CLI_INSTALLED=true
|
|
EARLY_CLI_MODE="container"
|
|
EARLY_CLI_CONTAINER="$CLAUDE_CONTAINER"
|
|
info "Claude Code CLI found in container: $CLAUDE_CONTAINER"
|
|
elif command -v claude &>/dev/null; then
|
|
CLI_INSTALLED=true
|
|
EARLY_CLI_MODE="host"
|
|
info "Claude Code CLI found on host: $(which claude)"
|
|
fi
|
|
|
|
# --- CLI not installed: offer to install ---
|
|
if ! $CLI_INSTALLED; then
|
|
echo -e " ${BOLD}Claude Code CLI is not installed.${NC}"
|
|
echo " It's required for automatic OAuth token refresh."
|
|
echo ""
|
|
echo " Options:"
|
|
echo " 1) Install Claude Code CLI now (requires npm/Node.js)"
|
|
echo " 2) Show me the install instructions (exit wizard)"
|
|
echo " 3) Skip — I'll provide the credentials path manually"
|
|
CHOICE=$(ask "Select" "1")
|
|
case "$CHOICE" in
|
|
1)
|
|
if command -v npm &>/dev/null; then
|
|
info "Installing Claude Code CLI via npm..."
|
|
npm install -g @anthropic-ai/claude-code 2>&1 | tail -5
|
|
if command -v claude &>/dev/null; then
|
|
success "Claude Code CLI installed successfully"
|
|
CLI_INSTALLED=true
|
|
EARLY_CLI_MODE="host"
|
|
else
|
|
error "Installation failed. Check npm/Node.js setup."
|
|
fi
|
|
else
|
|
warn "npm not found. Node.js is required to install Claude Code CLI."
|
|
echo ""
|
|
echo " Install Node.js first:"
|
|
echo " curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -"
|
|
echo " apt install -y nodejs"
|
|
echo ""
|
|
if confirm " Install Node.js + Claude Code CLI now?" "N"; then
|
|
info "Installing Node.js LTS..."
|
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - 2>&1 | tail -3
|
|
apt-get install -y nodejs 2>&1 | tail -3
|
|
if command -v npm &>/dev/null; then
|
|
info "Installing Claude Code CLI..."
|
|
npm install -g @anthropic-ai/claude-code 2>&1 | tail -5
|
|
if command -v claude &>/dev/null; then
|
|
success "Claude Code CLI installed successfully"
|
|
CLI_INSTALLED=true
|
|
EARLY_CLI_MODE="host"
|
|
else
|
|
error "Claude CLI installation failed"
|
|
fi
|
|
else
|
|
error "Node.js installation failed"
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
2)
|
|
echo ""
|
|
info "Install Claude Code CLI:"
|
|
echo ""
|
|
echo " # Option A: npm (recommended)"
|
|
echo " npm install -g @anthropic-ai/claude-code"
|
|
echo ""
|
|
echo " # Option B: Run directly with npx"
|
|
echo " npx @anthropic-ai/claude-code"
|
|
echo ""
|
|
echo " # More info:"
|
|
echo " https://docs.anthropic.com/en/docs/claude-code"
|
|
echo ""
|
|
info "After installing and signing in, re-run: sudo ./setup.sh"
|
|
exit 0
|
|
;;
|
|
3)
|
|
info "Continuing without Claude CLI..."
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# --- CLI installed but no credentials: offer sign-in ---
|
|
if $CLI_INSTALLED && ([ -z "$CLAUDE_CREDS_FILE" ] || [ ! -f "$CLAUDE_CREDS_FILE" ]); then
|
|
echo ""
|
|
warn "Claude Code CLI is installed but not signed in (no OAuth credentials)."
|
|
info "You need to authenticate with your Claude Max subscription."
|
|
info "This will open an OAuth flow — you'll get a URL to visit in your browser."
|
|
echo ""
|
|
if confirm " Launch Claude CLI now to sign in?" "Y"; then
|
|
echo ""
|
|
echo -e " ${BOLD}Complete the OAuth sign-in in your browser.${NC}"
|
|
echo -e " ${BOLD}After signing in, press Ctrl+C or type /exit to return here.${NC}"
|
|
echo ""
|
|
if [ "$EARLY_CLI_MODE" = "container" ]; then
|
|
docker exec -it "$EARLY_CLI_CONTAINER" claude || true
|
|
else
|
|
claude || true
|
|
fi
|
|
echo ""
|
|
info "Checking for credentials after sign-in..."
|
|
|
|
# Re-search for credentials
|
|
for path in \
|
|
/root/.claude/.credentials.json \
|
|
"$HOME/.claude/.credentials.json"; do
|
|
if [ -f "$path" ]; then
|
|
CLAUDE_CREDS_FILE="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Also check container mounts again
|
|
if [ -z "$CLAUDE_CREDS_FILE" ] && [ -n "$CLAUDE_CONTAINER" ]; then
|
|
MOUNT_SOURCE=$(docker inspect "$CLAUDE_CONTAINER" --format '{{range .Mounts}}{{if or (eq .Destination "/root") (eq .Destination "/root/.claude") (eq .Destination "/home/node/.claude")}}{{.Source}}{{end}}{{end}}' 2>/dev/null)
|
|
if [ -n "$MOUNT_SOURCE" ]; then
|
|
for suffix in "/.claude/.credentials.json" "/.credentials.json"; do
|
|
if [ -f "${MOUNT_SOURCE}${suffix}" ]; then
|
|
CLAUDE_CREDS_FILE="${MOUNT_SOURCE}${suffix}"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- Last resort: ask for manual path ---
|
|
if [ -z "$CLAUDE_CREDS_FILE" ] || [ ! -f "$CLAUDE_CREDS_FILE" ]; then
|
|
echo ""
|
|
warn "Could not find credentials automatically."
|
|
CLAUDE_CREDS_FILE=$(ask "Enter path to .credentials.json (or 'quit' to exit)")
|
|
if [ "$CLAUDE_CREDS_FILE" = "quit" ] || [ "$CLAUDE_CREDS_FILE" = "q" ]; then
|
|
echo ""
|
|
info "To set up credentials:"
|
|
info " 1. Install Claude Code CLI: npm install -g @anthropic-ai/claude-code"
|
|
info " 2. Sign in: claude"
|
|
info " 3. Re-run this wizard: sudo ./setup.sh"
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$CLAUDE_CREDS_FILE" ]; then
|
|
error "File not found: $CLAUDE_CREDS_FILE"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# --- Phase 3: Validate credentials ---
|
|
HAS_OAUTH=$(python3 -c "
|
|
import json
|
|
with open('$CLAUDE_CREDS_FILE') as f:
|
|
d = json.load(f)
|
|
oauth = d.get('claudeAiOauth', {})
|
|
print('yes' if oauth.get('accessToken') else 'no')
|
|
" 2>/dev/null || echo "no")
|
|
|
|
if [ "$HAS_OAUTH" != "yes" ]; then
|
|
error "Credentials file exists but contains no OAuth tokens."
|
|
info "Claude CLI may not be signed in with a Claude Max subscription."
|
|
echo ""
|
|
if $CLI_INSTALLED; then
|
|
info "Sign in to Claude CLI and re-run this wizard:"
|
|
info " claude # sign in with OAuth"
|
|
info " sudo ./setup.sh # re-run wizard"
|
|
else
|
|
info "Install Claude Code CLI, sign in, and re-run:"
|
|
info " npm install -g @anthropic-ai/claude-code"
|
|
info " claude # sign in with OAuth"
|
|
info " sudo ./setup.sh # re-run wizard"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
TOKEN_INFO=$(python3 -c "
|
|
import json, time
|
|
with open('$CLAUDE_CREDS_FILE') as f:
|
|
d = json.load(f)
|
|
oauth = d.get('claudeAiOauth', {})
|
|
access = oauth.get('accessToken', '')
|
|
refresh = oauth.get('refreshToken', '')
|
|
expires = oauth.get('expiresAt', 0)
|
|
remaining = (expires / 1000 - time.time()) / 3600
|
|
status = 'VALID' if remaining > 0 else 'EXPIRED'
|
|
print(f'access={access[:20]}... refresh={\"yes\" if refresh else \"no\"} remaining={remaining:.1f}h status={status}')
|
|
" 2>/dev/null || echo "error")
|
|
|
|
if echo "$TOKEN_INFO" | grep -q "error"; then
|
|
error "Cannot parse credentials file. Is it a valid Claude CLI .credentials.json?"
|
|
exit 1
|
|
fi
|
|
|
|
success "Credentials: $CLAUDE_CREDS_FILE"
|
|
info " $TOKEN_INFO"
|
|
|
|
CURRENT_TOKEN=$(python3 -c "
|
|
import json
|
|
with open('$CLAUDE_CREDS_FILE') as f:
|
|
d = json.load(f)
|
|
print(d['claudeAiOauth']['accessToken'])
|
|
" 2>/dev/null)
|
|
|
|
# ============================================================================
|
|
# STEP 4: Confirm Configuration
|
|
# ============================================================================
|
|
header "Step 4: Configuration Summary"
|
|
|
|
echo -e " ${BOLD}OpenClaw config:${NC} $OPENCLAW_CONFIG_DIR"
|
|
echo -e " ${BOLD}Docker compose:${NC} $COMPOSE_DIR"
|
|
echo -e " ${BOLD}Environment file:${NC} $ENV_FILE"
|
|
echo -e " ${BOLD}Gateway container:${NC} $GATEWAY_CONTAINER"
|
|
echo -e " ${BOLD}OAuth file:${NC} $OPENCLAW_OAUTH_FILE"
|
|
echo -e " ${BOLD}Claude CLI creds:${NC} $CLAUDE_CREDS_FILE"
|
|
echo -e " ${BOLD}Sync method:${NC} $([ "$USE_INOTIFY" = true ] && echo 'inotifywait (real-time)' || echo 'systemd timer (every 6h)')"
|
|
echo ""
|
|
|
|
if ! confirm "Proceed with these settings?" "Y"; then
|
|
echo "Aborted."
|
|
exit 0
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 5: Configure Anthropic Model in OpenClaw
|
|
# ============================================================================
|
|
header "Step 5: Configuring Anthropic Model"
|
|
|
|
# Check if anthropic model is already configured
|
|
HAS_ANTHROPIC=$(python3 -c "
|
|
import json
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json') as f:
|
|
d = json.load(f)
|
|
primary = d.get('agents', {}).get('defaults', {}).get('model', {}).get('primary', '')
|
|
print('yes' if 'anthropic/' in primary else 'no')
|
|
" 2>/dev/null || echo "no")
|
|
|
|
if [ "$HAS_ANTHROPIC" = "yes" ]; then
|
|
success "Anthropic model already configured as primary"
|
|
else
|
|
info "Anthropic model not yet configured"
|
|
if confirm " Set anthropic/claude-opus-4-6 as primary model?" "Y"; then
|
|
python3 -c "
|
|
import json
|
|
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json') as f:
|
|
d = json.load(f)
|
|
|
|
# Set primary model
|
|
if 'agents' not in d:
|
|
d['agents'] = {}
|
|
if 'defaults' not in d['agents']:
|
|
d['agents']['defaults'] = {}
|
|
if 'model' not in d['agents']['defaults']:
|
|
d['agents']['defaults']['model'] = {}
|
|
d['agents']['defaults']['model']['primary'] = 'anthropic/claude-opus-4-6'
|
|
|
|
# Add fallback if not present
|
|
fallbacks = d['agents']['defaults']['model'].get('fallbacks', [])
|
|
if 'anthropic/claude-sonnet-4-6' not in fallbacks:
|
|
fallbacks.insert(0, 'anthropic/claude-sonnet-4-6')
|
|
d['agents']['defaults']['model']['fallbacks'] = fallbacks
|
|
|
|
# Add model aliases
|
|
if 'models' not in d['agents']['defaults']:
|
|
d['agents']['defaults']['models'] = {}
|
|
d['agents']['defaults']['models']['anthropic/claude-opus-4-6'] = {'alias': 'Claude Opus 4.6 (Max)'}
|
|
d['agents']['defaults']['models']['anthropic/claude-sonnet-4-6'] = {'alias': 'Claude Sonnet 4.6 (Max)'}
|
|
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json', 'w') as f:
|
|
json.dump(d, f, indent=2)
|
|
"
|
|
success "Set anthropic/claude-opus-4-6 as primary model"
|
|
success "Added anthropic/claude-sonnet-4-6 to fallbacks"
|
|
success "Added model aliases"
|
|
fi
|
|
fi
|
|
|
|
# Check for broken custom anthropic provider (common mistake)
|
|
HAS_CUSTOM_PROVIDER=$(python3 -c "
|
|
import json
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json') as f:
|
|
d = json.load(f)
|
|
providers = d.get('models', {}).get('providers', {})
|
|
print('yes' if 'anthropic' in providers else 'no')
|
|
" 2>/dev/null || echo "no")
|
|
|
|
if [ "$HAS_CUSTOM_PROVIDER" = "yes" ]; then
|
|
warn "Found custom 'anthropic' in models.providers — this causes 404 errors!"
|
|
warn "The built-in provider handles Anthropic. Custom entry causes double /v1 in URLs."
|
|
if confirm " Remove the custom anthropic provider entry?" "Y"; then
|
|
python3 -c "
|
|
import json
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json') as f:
|
|
d = json.load(f)
|
|
del d['models']['providers']['anthropic']
|
|
with open('$OPENCLAW_CONFIG_DIR/openclaw.json', 'w') as f:
|
|
json.dump(d, f, indent=2)
|
|
"
|
|
success "Removed custom anthropic provider"
|
|
fi
|
|
fi
|
|
|
|
# Add ANTHROPIC_OAUTH_TOKEN to .env if missing
|
|
if grep -q 'ANTHROPIC_OAUTH_TOKEN=' "$ENV_FILE" 2>/dev/null; then
|
|
success "ANTHROPIC_OAUTH_TOKEN already in .env"
|
|
else
|
|
echo "ANTHROPIC_OAUTH_TOKEN=\"$CURRENT_TOKEN\"" >> "$ENV_FILE"
|
|
success "Added ANTHROPIC_OAUTH_TOKEN to .env"
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 6: Fix/Create Auth Profiles
|
|
# ============================================================================
|
|
header "Step 6: Configuring Auth Profiles"
|
|
|
|
AGENTS_DIR="$OPENCLAW_CONFIG_DIR/agents"
|
|
if [ -d "$AGENTS_DIR" ]; then
|
|
for agent_dir in "$AGENTS_DIR"/*/agent; do
|
|
[ -d "$agent_dir" ] || continue
|
|
agent=$(basename "$(dirname "$agent_dir")")
|
|
f="$agent_dir/auth-profiles.json"
|
|
|
|
if [ ! -f "$f" ]; then
|
|
# Create new auth-profiles.json
|
|
python3 -c "
|
|
import json
|
|
data = {
|
|
'version': 1,
|
|
'profiles': {
|
|
'anthropic:default': {
|
|
'type': 'oauth',
|
|
'provider': 'anthropic',
|
|
'access': '$CURRENT_TOKEN'
|
|
}
|
|
},
|
|
'lastGood': {
|
|
'anthropic': 'anthropic:default'
|
|
},
|
|
'usageStats': {}
|
|
}
|
|
with open('$f', 'w') as fh:
|
|
json.dump(data, fh, indent=2)
|
|
"
|
|
success "$agent: created auth-profiles.json"
|
|
else
|
|
# Fix existing profile
|
|
python3 -c "
|
|
import json
|
|
with open('$f') as fh:
|
|
data = json.load(fh)
|
|
changed = False
|
|
if 'profiles' not in data:
|
|
data['profiles'] = {}
|
|
p = data['profiles'].get('anthropic:default', {})
|
|
if not p:
|
|
data['profiles']['anthropic:default'] = {
|
|
'type': 'oauth',
|
|
'provider': 'anthropic',
|
|
'access': '$CURRENT_TOKEN'
|
|
}
|
|
changed = True
|
|
else:
|
|
if p.get('type') != 'oauth':
|
|
p['type'] = 'oauth'
|
|
changed = True
|
|
if 'key' in p and 'access' not in p:
|
|
p['access'] = p.pop('key')
|
|
changed = True
|
|
elif 'key' in p:
|
|
del p['key']
|
|
changed = True
|
|
if p.get('access') != '$CURRENT_TOKEN':
|
|
p['access'] = '$CURRENT_TOKEN'
|
|
changed = True
|
|
|
|
# Clear cooldown
|
|
stats = data.get('usageStats', {}).get('anthropic:default', {})
|
|
for k in ['cooldownUntil', 'errorCount', 'failureCounts', 'lastFailureAt']:
|
|
if k in stats:
|
|
del stats[k]
|
|
changed = True
|
|
|
|
if 'lastGood' not in data:
|
|
data['lastGood'] = {}
|
|
data['lastGood']['anthropic'] = 'anthropic:default'
|
|
|
|
if changed:
|
|
with open('$f', 'w') as fh:
|
|
json.dump(data, fh, indent=2)
|
|
print('fixed')
|
|
else:
|
|
print('ok')
|
|
" 2>/dev/null
|
|
RESULT=$(python3 -c "
|
|
import json
|
|
with open('$f') as fh:
|
|
data = json.load(fh)
|
|
p = data.get('profiles', {}).get('anthropic:default', {})
|
|
has_access = 'access' in p
|
|
print(f'type={p.get(\"type\",\"none\")} access={\"yes\" if has_access else \"no\"}')
|
|
" 2>/dev/null)
|
|
success "$agent: $RESULT"
|
|
fi
|
|
done
|
|
else
|
|
warn "No agents directory found at $AGENTS_DIR"
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 7: Install Sync Script
|
|
# ============================================================================
|
|
header "Step 7: Installing Sync Service"
|
|
|
|
INSTALL_DIR="/usr/local/bin"
|
|
|
|
if [ "$USE_INOTIFY" = true ]; then
|
|
# Install inotify-based watcher
|
|
SYNC_SCRIPT="$INSTALL_DIR/sync-oauth-token.sh"
|
|
sed \
|
|
-e "s|@@CLAUDE_CREDS_FILE@@|$CLAUDE_CREDS_FILE|g" \
|
|
-e "s|@@OPENCLAW_OAUTH_FILE@@|$OPENCLAW_OAUTH_FILE|g" \
|
|
-e "s|@@OPENCLAW_ENV_FILE@@|$ENV_FILE|g" \
|
|
-e "s|@@COMPOSE_DIR@@|$COMPOSE_DIR|g" \
|
|
"$SCRIPT_DIR/scripts/sync-oauth-token.sh" > "$SYNC_SCRIPT"
|
|
chmod +x "$SYNC_SCRIPT"
|
|
success "Installed $SYNC_SCRIPT"
|
|
|
|
# Install systemd service
|
|
sed "s|@@SYNC_SCRIPT_PATH@@|$SYNC_SCRIPT|g" \
|
|
"$SCRIPT_DIR/templates/sync-oauth-token.service" > /etc/systemd/system/sync-oauth-token.service
|
|
success "Installed systemd service"
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable sync-oauth-token.service
|
|
systemctl start sync-oauth-token.service
|
|
success "Service started and enabled"
|
|
else
|
|
# Install timer-based refresh
|
|
REFRESH_SCRIPT="$INSTALL_DIR/refresh-claude-token.sh"
|
|
sed \
|
|
-e "s|@@CLAUDE_CREDS_FILE@@|$CLAUDE_CREDS_FILE|g" \
|
|
-e "s|@@OPENCLAW_OAUTH_FILE@@|$OPENCLAW_OAUTH_FILE|g" \
|
|
-e "s|@@OPENCLAW_ENV_FILE@@|$ENV_FILE|g" \
|
|
-e "s|@@COMPOSE_DIR@@|$COMPOSE_DIR|g" \
|
|
"$SCRIPT_DIR/scripts/refresh-claude-token.sh" > "$REFRESH_SCRIPT"
|
|
chmod +x "$REFRESH_SCRIPT"
|
|
success "Installed $REFRESH_SCRIPT"
|
|
|
|
# Install systemd timer
|
|
sed "s|@@REFRESH_SCRIPT_PATH@@|$REFRESH_SCRIPT|g" \
|
|
"$SCRIPT_DIR/templates/refresh-claude-token.service" > /etc/systemd/system/refresh-claude-token.service
|
|
cp "$SCRIPT_DIR/templates/refresh-claude-token.timer" /etc/systemd/system/
|
|
success "Installed systemd timer"
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable refresh-claude-token.timer
|
|
systemctl start refresh-claude-token.timer
|
|
success "Timer started and enabled (runs every 6 hours)"
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 8: Detect Claude CLI & Install Auto-Refresh Trigger
|
|
# ============================================================================
|
|
header "Step 8: Detecting Claude CLI & Installing Auto-Refresh Trigger"
|
|
|
|
info "The sync service watches for credential changes, but something must"
|
|
info "trigger Claude CLI to actually refresh the token before it expires."
|
|
info "This step installs a timer that triggers the refresh automatically."
|
|
echo ""
|
|
|
|
# Detect Claude CLI — container or host?
|
|
CLI_MODE=""
|
|
CLI_CONTAINER=""
|
|
CLI_BASE_URL_OVERRIDE="false"
|
|
|
|
# Strategy 1: Check for Claude container
|
|
CLAUDE_CONTAINER_FOUND=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i 'claude' | head -1)
|
|
if [ -n "$CLAUDE_CONTAINER_FOUND" ]; then
|
|
# Verify claude binary exists inside
|
|
if docker exec "$CLAUDE_CONTAINER_FOUND" which claude &>/dev/null; then
|
|
CLI_MODE="container"
|
|
CLI_CONTAINER="$CLAUDE_CONTAINER_FOUND"
|
|
info "Found Claude CLI in container: $CLI_CONTAINER"
|
|
|
|
# Check if ANTHROPIC_BASE_URL needs override
|
|
CTR_BASE_URL=$(docker inspect "$CLI_CONTAINER" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | grep '^ANTHROPIC_BASE_URL=' | cut -d= -f2-)
|
|
if [ -n "$CTR_BASE_URL" ] && [ "$CTR_BASE_URL" != "https://api.anthropic.com" ]; then
|
|
CLI_BASE_URL_OVERRIDE="true"
|
|
info "Container has ANTHROPIC_BASE_URL=$CTR_BASE_URL"
|
|
info "Will use temporary override for CLI invocations (does not affect container)"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Strategy 2: Check host
|
|
if [ -z "$CLI_MODE" ]; then
|
|
if command -v claude &>/dev/null; then
|
|
CLI_MODE="host"
|
|
info "Found Claude CLI on host: $(which claude)"
|
|
fi
|
|
fi
|
|
|
|
# Strategy 3: Ask user
|
|
if [ -z "$CLI_MODE" ]; then
|
|
warn "Could not auto-detect Claude CLI"
|
|
echo ""
|
|
echo " Where is Claude CLI installed?"
|
|
echo " 1) In a Docker container"
|
|
echo " 2) Directly on this system"
|
|
echo " 3) Skip (no auto-refresh trigger)"
|
|
CHOICE=$(ask "Select" "3")
|
|
case "$CHOICE" in
|
|
1)
|
|
CLI_MODE="container"
|
|
CLI_CONTAINER=$(ask "Enter container name" "claude-proxy")
|
|
if ! docker exec "$CLI_CONTAINER" which claude &>/dev/null; then
|
|
error "Claude CLI not found in container $CLI_CONTAINER"
|
|
CLI_MODE=""
|
|
else
|
|
# Check base URL
|
|
CTR_BASE_URL=$(docker inspect "$CLI_CONTAINER" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | grep '^ANTHROPIC_BASE_URL=' | cut -d= -f2-)
|
|
if [ -n "$CTR_BASE_URL" ] && [ "$CTR_BASE_URL" != "https://api.anthropic.com" ]; then
|
|
CLI_BASE_URL_OVERRIDE="true"
|
|
fi
|
|
fi
|
|
;;
|
|
2)
|
|
CLI_MODE="host"
|
|
if ! command -v claude &>/dev/null; then
|
|
error "Claude CLI not found on system"
|
|
CLI_MODE=""
|
|
fi
|
|
;;
|
|
*)
|
|
warn "Skipping auto-refresh trigger installation"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
TRIGGER_INSTALLED=false
|
|
|
|
if [ -n "$CLI_MODE" ]; then
|
|
# Test Claude CLI invocation
|
|
info "Testing Claude CLI invocation..."
|
|
if [ "$CLI_MODE" = "container" ]; then
|
|
if [ "$CLI_BASE_URL_OVERRIDE" = "true" ]; then
|
|
TEST_CMD="docker exec -e ANTHROPIC_BASE_URL=https://api.anthropic.com $CLI_CONTAINER claude -p 'say ok' --no-session-persistence"
|
|
TEST_OUTPUT=$(timeout 60 docker exec -e ANTHROPIC_BASE_URL=https://api.anthropic.com "$CLI_CONTAINER" claude -p "say ok" --no-session-persistence 2>&1)
|
|
else
|
|
TEST_CMD="docker exec $CLI_CONTAINER claude -p 'say ok' --no-session-persistence"
|
|
TEST_OUTPUT=$(timeout 60 docker exec "$CLI_CONTAINER" claude -p "say ok" --no-session-persistence 2>&1)
|
|
fi
|
|
else
|
|
TEST_CMD="claude -p 'say ok' --no-session-persistence"
|
|
TEST_OUTPUT=$(timeout 60 claude -p "say ok" --no-session-persistence 2>&1)
|
|
fi
|
|
TEST_EXIT=$?
|
|
|
|
if [ "$TEST_EXIT" -eq 0 ] && [ -n "$TEST_OUTPUT" ]; then
|
|
success "CLI test passed: $TEST_OUTPUT"
|
|
|
|
# Verify proxy/container env unchanged (if container mode)
|
|
if [ "$CLI_MODE" = "container" ] && [ "$CLI_BASE_URL_OVERRIDE" = "true" ]; then
|
|
VERIFY_URL=$(docker inspect "$CLI_CONTAINER" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | grep '^ANTHROPIC_BASE_URL=' | cut -d= -f2-)
|
|
if [ "$VERIFY_URL" = "$CTR_BASE_URL" ]; then
|
|
success "Container env unchanged (temporary override confirmed)"
|
|
else
|
|
warn "Container env may have changed — check manually"
|
|
fi
|
|
fi
|
|
|
|
# Install trigger script
|
|
TRIGGER_SCRIPT="$INSTALL_DIR/trigger-claude-refresh.sh"
|
|
REAUTH_FLAG="$COMPOSE_DIR/REAUTH_NEEDED"
|
|
sed \
|
|
-e "s|@@CREDS_FILE@@|$CLAUDE_CREDS_FILE|g" \
|
|
-e "s|@@REAUTH_FLAG@@|$REAUTH_FLAG|g" \
|
|
-e "s|@@CLI_MODE@@|$CLI_MODE|g" \
|
|
-e "s|@@CLI_CONTAINER@@|$CLI_CONTAINER|g" \
|
|
-e "s|@@CLI_BASE_URL_OVERRIDE@@|$CLI_BASE_URL_OVERRIDE|g" \
|
|
"$SCRIPT_DIR/scripts/trigger-claude-refresh.sh" > "$TRIGGER_SCRIPT"
|
|
chmod +x "$TRIGGER_SCRIPT"
|
|
success "Installed $TRIGGER_SCRIPT"
|
|
|
|
# Install systemd service and timer
|
|
sed "s|@@TRIGGER_SCRIPT_PATH@@|$TRIGGER_SCRIPT|g" \
|
|
"$SCRIPT_DIR/templates/trigger-claude-refresh.service" > /etc/systemd/system/trigger-claude-refresh.service
|
|
cp "$SCRIPT_DIR/templates/trigger-claude-refresh.timer" /etc/systemd/system/
|
|
success "Installed systemd timer"
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable trigger-claude-refresh.timer
|
|
systemctl start trigger-claude-refresh.timer
|
|
success "Auto-refresh trigger timer started (runs every 30 minutes)"
|
|
TRIGGER_INSTALLED=true
|
|
|
|
# Run the trigger script once to verify
|
|
info "Running trigger script for initial verification..."
|
|
bash "$TRIGGER_SCRIPT" 2>&1 | while read -r line; do echo " $line"; done
|
|
else
|
|
error "CLI test failed (exit code: $TEST_EXIT)"
|
|
if [ -n "$TEST_OUTPUT" ]; then
|
|
error "Output: $TEST_OUTPUT"
|
|
fi
|
|
warn "Skipping auto-refresh trigger installation"
|
|
warn "You can re-run the wizard later to retry"
|
|
fi
|
|
fi
|
|
|
|
# ============================================================================
|
|
# STEP 9: Initial Sync
|
|
# ============================================================================
|
|
header "Step 9: Running Initial Sync"
|
|
|
|
# Create oauth.json
|
|
mkdir -p "$(dirname "$OPENCLAW_OAUTH_FILE")"
|
|
python3 -c "
|
|
import json, time
|
|
|
|
with open('$CLAUDE_CREDS_FILE') as f:
|
|
src = json.load(f)
|
|
|
|
oauth = src['claudeAiOauth']
|
|
openclaw = {
|
|
'anthropic': {
|
|
'access': oauth['accessToken'],
|
|
'refresh': oauth['refreshToken'],
|
|
'expires': oauth['expiresAt'],
|
|
'scopes': oauth.get('scopes', []),
|
|
'subscriptionType': oauth.get('subscriptionType', 'max'),
|
|
'rateLimitTier': oauth.get('rateLimitTier', 'default_claude_max_5x')
|
|
}
|
|
}
|
|
with open('$OPENCLAW_OAUTH_FILE', 'w') as f:
|
|
json.dump(openclaw, f)
|
|
|
|
remaining = (oauth['expiresAt'] / 1000 - time.time()) / 3600
|
|
print(f'Token: {oauth[\"accessToken\"][:20]}... expires in {remaining:.1f}h')
|
|
"
|
|
success "Created $OPENCLAW_OAUTH_FILE"
|
|
|
|
# Update .env token
|
|
python3 -c "
|
|
import json, re
|
|
with open('$CLAUDE_CREDS_FILE') as f:
|
|
token = json.load(f)['claudeAiOauth']['accessToken']
|
|
with open('$ENV_FILE') as f:
|
|
env = f.read()
|
|
env = re.sub(r'ANTHROPIC_OAUTH_TOKEN=.*', f'ANTHROPIC_OAUTH_TOKEN=\"{token}\"', env)
|
|
with open('$ENV_FILE', 'w') as f:
|
|
f.write(env)
|
|
"
|
|
success "Updated $ENV_FILE"
|
|
|
|
# Recreate gateway
|
|
info "Recreating gateway container..."
|
|
cd "$COMPOSE_DIR"
|
|
docker compose down openclaw-gateway 2>&1 | tail -2
|
|
docker compose up -d openclaw-gateway 2>&1 | tail -2
|
|
success "Gateway recreated with fresh token"
|
|
|
|
# ============================================================================
|
|
# STEP 10: Verify
|
|
# ============================================================================
|
|
header "Step 10: Verification"
|
|
|
|
sleep 3
|
|
|
|
ERRORS=0
|
|
|
|
# Check service
|
|
if [ "$USE_INOTIFY" = true ]; then
|
|
if systemctl is-active --quiet sync-oauth-token.service; then
|
|
success "sync-oauth-token.service is running"
|
|
else
|
|
error "Service not running"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
else
|
|
if systemctl is-active --quiet refresh-claude-token.timer; then
|
|
success "refresh-claude-token.timer is active"
|
|
else
|
|
error "Timer not active"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
fi
|
|
|
|
# Check trigger timer
|
|
if [ "$TRIGGER_INSTALLED" = true ]; then
|
|
if systemctl is-active --quiet trigger-claude-refresh.timer; then
|
|
success "trigger-claude-refresh.timer is active"
|
|
else
|
|
error "Trigger timer not active"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
fi
|
|
|
|
# Check oauth.json
|
|
if [ -f "$OPENCLAW_OAUTH_FILE" ]; then
|
|
HAS_ACCESS=$(python3 -c "
|
|
import json
|
|
with open('$OPENCLAW_OAUTH_FILE') as f:
|
|
d = json.load(f)
|
|
print('yes' if d.get('anthropic', {}).get('access') else 'no')
|
|
" 2>/dev/null)
|
|
if [ "$HAS_ACCESS" = "yes" ]; then
|
|
success "oauth.json has correct format"
|
|
else
|
|
error "oauth.json missing anthropic.access"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
else
|
|
error "oauth.json not found"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
|
|
# Check gateway
|
|
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "$GATEWAY_CONTAINER"; then
|
|
success "Gateway container is running"
|
|
CTR_TOKEN=$(docker exec "$GATEWAY_CONTAINER" printenv ANTHROPIC_OAUTH_TOKEN 2>/dev/null | cut -c1-20)
|
|
if [ -n "$CTR_TOKEN" ]; then
|
|
success "Container has ANTHROPIC_OAUTH_TOKEN: ${CTR_TOKEN}..."
|
|
fi
|
|
else
|
|
error "Gateway container not running"
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
|
|
# ============================================================================
|
|
# SUMMARY
|
|
# ============================================================================
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}"
|
|
echo " ╔══════════════════════════════════════════════════════╗"
|
|
if [ "$ERRORS" -eq 0 ]; then
|
|
echo " ║ Setup Complete! ║"
|
|
else
|
|
echo " ║ Setup Complete (with $ERRORS warning(s)) ║"
|
|
fi
|
|
echo " ╚══════════════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
|
|
echo -e " ${BOLD}Installed:${NC}"
|
|
if [ "$USE_INOTIFY" = true ]; then
|
|
echo " - sync-oauth-token.sh (real-time file watcher)"
|
|
echo " - sync-oauth-token.service (systemd)"
|
|
else
|
|
echo " - refresh-claude-token.sh (direct API refresh)"
|
|
echo " - refresh-claude-token.timer (every 6 hours)"
|
|
fi
|
|
if [ "$TRIGGER_INSTALLED" = true ]; then
|
|
echo " - trigger-claude-refresh.sh (auto-refresh trigger)"
|
|
echo " - trigger-claude-refresh.timer (every 30 minutes)"
|
|
echo " - Claude CLI mode: $CLI_MODE$([ "$CLI_MODE" = "container" ] && echo " ($CLI_CONTAINER)")"
|
|
fi
|
|
echo " - Anthropic model configured in openclaw.json"
|
|
echo " - Auth profiles updated for all agents"
|
|
echo " - oauth.json created with fresh token"
|
|
echo ""
|
|
echo -e " ${BOLD}Useful commands:${NC}"
|
|
if [ "$USE_INOTIFY" = true ]; then
|
|
echo " journalctl -u sync-oauth-token.service -f # Watch sync logs"
|
|
echo " systemctl restart sync-oauth-token.service # Force re-sync"
|
|
else
|
|
echo " journalctl -u refresh-claude-token.service # View last refresh"
|
|
echo " systemctl list-timers refresh-claude-token* # Check timer"
|
|
fi
|
|
if [ "$TRIGGER_INSTALLED" = true ]; then
|
|
echo " journalctl -u trigger-claude-refresh -n 20 # Trigger logs"
|
|
echo " systemctl list-timers trigger-claude-refresh* # Check trigger timer"
|
|
fi
|
|
echo " ./scripts/verify.sh # Health check"
|
|
echo " ./setup.sh --uninstall # Remove everything"
|
|
echo ""
|
|
echo -e " ${DIM}Created by ROOH — www.rooh.red${NC}"
|
|
echo ""
|