Comprehensive standalone guide + setup script so anyone can replicate the Claude Code overnight automation system for their own projects. Includes loop.sh, hook templates, plan/prompt templates, and an interactive setup script. No project-specific content included. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
251 lines
9.1 KiB
Bash
Executable File
251 lines
9.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# Claude Code Overnight Automation — Setup Script
|
|
# ============================================================================
|
|
# Creates the loop directory, hook scripts, and configures settings.json.
|
|
# Run this from your project root:
|
|
# bash path/to/setup.sh
|
|
# ============================================================================
|
|
set -euo pipefail
|
|
|
|
# ── Colors ───────────────────────────────────────────────────────────────────
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
CYAN='\033[0;36m'
|
|
RED='\033[0;31m'
|
|
NC='\033[0m'
|
|
|
|
ok() { echo -e " ${GREEN}+${NC} $1"; }
|
|
warn() { echo -e " ${YELLOW}!${NC} $1"; }
|
|
err() { echo -e " ${RED}x${NC} $1"; }
|
|
info() { echo -e " ${DIM}$1${NC}"; }
|
|
|
|
# ── Header ───────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD} Claude Code Overnight Automation${NC}"
|
|
echo -e " ${DIM}────────────────────────────────────${NC}"
|
|
echo ""
|
|
|
|
# ── Find template directory ──────────────────────────────────────────────────
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
TEMPLATE_DIR="$SCRIPT_DIR/templates"
|
|
|
|
if [[ ! -d "$TEMPLATE_DIR" ]]; then
|
|
err "Templates directory not found at: $TEMPLATE_DIR"
|
|
err "Make sure setup.sh is in the same folder as the templates/ directory."
|
|
exit 1
|
|
fi
|
|
|
|
# ── Check prerequisites ─────────────────────────────────────────────────────
|
|
echo -e " ${BOLD}Checking prerequisites...${NC}"
|
|
echo ""
|
|
|
|
MISSING=0
|
|
|
|
if command -v claude &>/dev/null; then
|
|
ok "Claude CLI found: $(which claude)"
|
|
else
|
|
err "Claude CLI not found. Install from: https://docs.anthropic.com/en/docs/claude-code"
|
|
MISSING=1
|
|
fi
|
|
|
|
if command -v git &>/dev/null; then
|
|
ok "Git found: $(which git)"
|
|
else
|
|
err "Git not found. Install git first."
|
|
MISSING=1
|
|
fi
|
|
|
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
ok "macOS detected (caffeinate + launchd available)"
|
|
else
|
|
warn "Not macOS — caffeinate/launchd hooks will need manual Linux equivalents"
|
|
fi
|
|
|
|
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
PROJECT_DIR="$(git rev-parse --show-toplevel)"
|
|
ok "Git repo found: $PROJECT_DIR"
|
|
else
|
|
PROJECT_DIR="$(pwd)"
|
|
warn "Not in a git repo. Using current directory: $PROJECT_DIR"
|
|
fi
|
|
|
|
if [[ "$MISSING" -eq 1 ]]; then
|
|
echo ""
|
|
err "Missing prerequisites. Fix the above and re-run."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Create loop directory ────────────────────────────────────────────────────
|
|
echo -e " ${BOLD}Setting up loop directory...${NC}"
|
|
echo ""
|
|
|
|
LOOP_DIR="$PROJECT_DIR/loop"
|
|
mkdir -p "$LOOP_DIR"
|
|
|
|
# Copy loop.sh
|
|
if [[ -f "$LOOP_DIR/loop.sh" ]]; then
|
|
warn "loop/loop.sh already exists — skipping (won't overwrite)"
|
|
else
|
|
cp "$TEMPLATE_DIR/loop.sh" "$LOOP_DIR/loop.sh"
|
|
chmod +x "$LOOP_DIR/loop.sh"
|
|
ok "Created loop/loop.sh"
|
|
fi
|
|
|
|
# Copy prompt.md
|
|
if [[ -f "$LOOP_DIR/prompt.md" ]]; then
|
|
warn "loop/prompt.md already exists — skipping"
|
|
else
|
|
cp "$TEMPLATE_DIR/prompt.md" "$LOOP_DIR/prompt.md"
|
|
ok "Created loop/prompt.md (edit this with your project rules)"
|
|
fi
|
|
|
|
# Copy plan.md
|
|
if [[ -f "$LOOP_DIR/plan.md" ]]; then
|
|
warn "loop/plan.md already exists — skipping"
|
|
else
|
|
cp "$TEMPLATE_DIR/plan.md" "$LOOP_DIR/plan.md"
|
|
ok "Created loop/plan.md (edit this with your tasks)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Install hooks ────────────────────────────────────────────────────────────
|
|
echo -e " ${BOLD}Installing hooks...${NC}"
|
|
echo ""
|
|
|
|
HOOKS_DIR="$HOME/.claude/hooks"
|
|
mkdir -p "$HOOKS_DIR"
|
|
|
|
for hook in prevent-sleep.sh stop-hook-autonomous.sh allow-sleep.sh; do
|
|
if [[ -f "$HOOKS_DIR/$hook" ]]; then
|
|
warn "$hook already exists — skipping (won't overwrite)"
|
|
else
|
|
cp "$TEMPLATE_DIR/hooks/$hook" "$HOOKS_DIR/$hook"
|
|
chmod +x "$HOOKS_DIR/$hook"
|
|
ok "Installed ~/.claude/hooks/$hook"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# ── Update settings.json ────────────────────────────────────────────────────
|
|
echo -e " ${BOLD}Configuring Claude settings...${NC}"
|
|
echo ""
|
|
|
|
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
|
|
# Backup existing settings
|
|
if [[ -f "$SETTINGS_FILE" ]]; then
|
|
cp "$SETTINGS_FILE" "${SETTINGS_FILE}.backup.$(date +%s)"
|
|
info "Backed up existing settings.json"
|
|
fi
|
|
|
|
# Check if hooks are already registered
|
|
if [[ -f "$SETTINGS_FILE" ]] && grep -q "stop-hook-autonomous" "$SETTINGS_FILE" 2>/dev/null; then
|
|
ok "Hooks already registered in settings.json"
|
|
else
|
|
# Create or update settings.json with hook registrations
|
|
if command -v python3 &>/dev/null; then
|
|
python3 << 'PYEOF'
|
|
import json, os
|
|
|
|
settings_path = os.path.expanduser("~/.claude/settings.json")
|
|
hooks_dir = os.path.expanduser("~/.claude/hooks")
|
|
|
|
# Load existing or start fresh
|
|
if os.path.exists(settings_path):
|
|
with open(settings_path) as f:
|
|
settings = json.load(f)
|
|
else:
|
|
settings = {}
|
|
|
|
# Ensure hooks section exists
|
|
if "hooks" not in settings:
|
|
settings["hooks"] = {}
|
|
|
|
hooks = settings["hooks"]
|
|
|
|
# Add UserPromptSubmit hook (prevent sleep)
|
|
if "UserPromptSubmit" not in hooks:
|
|
hooks["UserPromptSubmit"] = []
|
|
prevent_sleep = {"matcher": "", "hooks": [{"type": "command", "command": f"{hooks_dir}/prevent-sleep.sh"}]}
|
|
if not any("prevent-sleep" in json.dumps(h) for h in hooks["UserPromptSubmit"]):
|
|
hooks["UserPromptSubmit"].append(prevent_sleep)
|
|
|
|
# Add Stop hook (autonomous block)
|
|
if "Stop" not in hooks:
|
|
hooks["Stop"] = []
|
|
stop_hook = {"matcher": "", "hooks": [{"type": "command", "command": f"{hooks_dir}/stop-hook-autonomous.sh"}]}
|
|
if not any("stop-hook-autonomous" in json.dumps(h) for h in hooks["Stop"]):
|
|
hooks["Stop"].append(stop_hook)
|
|
|
|
# Add SessionEnd hook (allow sleep)
|
|
if "SessionEnd" not in hooks:
|
|
hooks["SessionEnd"] = []
|
|
session_end = {"matcher": "", "hooks": [{"type": "command", "command": f"{hooks_dir}/allow-sleep.sh"}]}
|
|
if not any("allow-sleep" in json.dumps(h) for h in hooks["SessionEnd"]):
|
|
hooks["SessionEnd"].append(session_end)
|
|
|
|
with open(settings_path, "w") as f:
|
|
json.dump(settings, f, indent=2)
|
|
f.write("\n")
|
|
|
|
print("done")
|
|
PYEOF
|
|
ok "Registered hooks in ~/.claude/settings.json"
|
|
else
|
|
warn "python3 not found — you'll need to manually add hooks to ~/.claude/settings.json"
|
|
info "See the README for the required settings.json format"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── Summary ──────────────────────────────────────────────────────────────────
|
|
echo -e " ${BOLD}${GREEN}Setup complete!${NC}"
|
|
echo ""
|
|
echo -e " ${DIM}────────────────────────────────────────────────────────${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}How to use:${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1.${NC} Edit your task list:"
|
|
echo -e " ${DIM}vim loop/plan.md${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}2.${NC} Edit your prompt (add your project's rules and specs):"
|
|
echo -e " ${DIM}vim loop/prompt.md${NC}"
|
|
echo ""
|
|
echo -e " Replace the {{PLACEHOLDERS}} with your project's:"
|
|
echo -e " - Spec file path (detailed task descriptions)"
|
|
echo -e " - Project rules file path (coding conventions)"
|
|
echo -e " - Inline project rules (style, frameworks, constraints)"
|
|
echo ""
|
|
echo -e " ${CYAN}3.${NC} Start the overnight run:"
|
|
echo -e " ${DIM}./loop/loop.sh${NC}"
|
|
echo ""
|
|
echo -e " Or with caffeinate (belt-and-suspenders sleep prevention):"
|
|
echo -e " ${DIM}caffeinate -i ./loop/loop.sh${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}4.${NC} Monitor progress:"
|
|
echo -e " ${DIM}tail -f loop/loop.log${NC}"
|
|
echo -e " ${DIM}grep -c '\\[x\\]' loop/plan.md${NC} # completed tasks"
|
|
echo -e " ${DIM}grep -c '\\[ \\]' loop/plan.md${NC} # remaining tasks"
|
|
echo ""
|
|
echo -e " ${BOLD}Configuration:${NC}"
|
|
echo ""
|
|
echo -e " ${DIM}CLAUDE_AUTONOMOUS=1${NC} Stop hook active (default)"
|
|
echo -e " ${DIM}CLAUDE_AUTONOMOUS=0${NC} Claude can stop freely"
|
|
echo -e " ${DIM}ITERATION_COUNT=20${NC} Run 20 iterations"
|
|
echo -e " ${DIM}ITERATION_DELAY=60${NC} 60s between iterations"
|
|
echo ""
|
|
echo -e " ${BOLD}Quick test:${NC}"
|
|
echo -e " ${DIM}CLAUDE_AUTONOMOUS=0 ITERATION_COUNT=1 ./loop/loop.sh${NC}"
|
|
echo ""
|
|
echo -e " ${DIM}────────────────────────────────────────────────────────${NC}"
|
|
echo -e " ${DIM}Full documentation: README.md${NC}"
|
|
echo ""
|