diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..6722bd5 --- /dev/null +++ b/install.sh @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# +# Claude Code Vertical - Install Script +# +# Installs skills and slash commands to your project's .claude directory +# Uses symlinks so updates to the source are automatically reflected +# +# Usage: +# ./install.sh # Install to current directory +# ./install.sh /path/to/dir # Install to specified directory +# + +set -euo pipefail + +# Colors (disable if not a terminal) +if [[ -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + BLUE='\033[0;34m' + BOLD='\033[1m' + NC='\033[0m' +else + RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC='' +fi + +# Get the directory where this script lives (source of truth) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Target directory - default to current directory +TARGET_DIR="${1:-.}" +TARGET_DIR="$(cd "$TARGET_DIR" && pwd)" + +# What we're installing +COMPONENTS=( + "commands:commands" + "skills:skills" + "skill-index:skill-index" + "lib:lib" +) + +# ------------------------------------------------------------------------------ +# Functions +# ------------------------------------------------------------------------------ + +log_info() { + printf "${BLUE}[info]${NC} %s\n" "$1" +} + +log_success() { + printf "${GREEN}[ok]${NC} %s\n" "$1" +} + +log_warn() { + printf "${YELLOW}[warn]${NC} %s\n" "$1" +} + +log_error() { + printf "${RED}[error]${NC} %s\n" "$1" +} + +log_header() { + printf "\n${BOLD}%s${NC}\n" "$1" + printf "%s\n" "$(printf '=%.0s' $(seq 1 ${#1}))" +} + +check_prerequisites() { + log_header "Checking prerequisites" + + # Check if source directories exist + for component in "${COMPONENTS[@]}"; do + local src="${component%%:*}" + if [[ ! -d "$SCRIPT_DIR/$src" ]]; then + log_error "Source directory not found: $SCRIPT_DIR/$src" + exit 1 + fi + done + log_success "All source directories found" + + # Check if target is a git repo (optional warning) + if [[ ! -d "$TARGET_DIR/.git" ]]; then + log_warn "Target is not a git repository: $TARGET_DIR" + log_info "Continuing anyway - this is not required" + else + log_success "Target is a git repository" + fi + + # Check for tmux + if command -v tmux &> /dev/null; then + log_success "tmux is installed ($(tmux -V))" + else + log_warn "tmux is not installed - install with: brew install tmux" + fi +} + +create_directories() { + log_header "Creating directories" + + local claude_dir="$TARGET_DIR/.claude" + + if [[ -d "$claude_dir" ]]; then + log_info ".claude directory already exists" + else + mkdir -p "$claude_dir" + log_success "Created $claude_dir" + fi + + # Create vertical plans directory + mkdir -p "$claude_dir/vertical/plans" + log_success "Created $claude_dir/vertical/plans" +} + +install_component() { + local src_name="$1" + local dest_name="$2" + local src_path="$SCRIPT_DIR/$src_name" + local dest_path="$TARGET_DIR/.claude/$dest_name" + + # Remove existing symlink or directory + if [[ -L "$dest_path" ]]; then + rm "$dest_path" + log_info "Removed existing symlink: $dest_path" + elif [[ -d "$dest_path" ]]; then + log_warn "Directory exists (not a symlink): $dest_path" + printf " Replace with symlink? [y/N] " + read -r response + if [[ "$response" =~ ^[Yy]$ ]]; then + rm -rf "$dest_path" + log_info "Removed existing directory" + else + log_warn "Skipping $dest_name" + return 0 + fi + fi + + # Create symlink + ln -s "$src_path" "$dest_path" + log_success "Linked $dest_name -> $src_path" +} + +install_components() { + log_header "Installing components" + + for component in "${COMPONENTS[@]}"; do + local src="${component%%:*}" + local dest="${component##*:}" + install_component "$src" "$dest" + done +} + +verify_installation() { + log_header "Verifying installation" + + local claude_dir="$TARGET_DIR/.claude" + local all_ok=true + + for component in "${COMPONENTS[@]}"; do + local dest="${component##*:}" + local dest_path="$claude_dir/$dest" + + if [[ -L "$dest_path" && -d "$dest_path" ]]; then + local count=$(find -L "$dest_path" -type f | wc -l | tr -d ' ') + log_success "$dest: $count files" + else + log_error "$dest: not found or broken symlink" + all_ok=false + fi + done + + if $all_ok; then + return 0 + else + return 1 + fi +} + +print_summary() { + log_header "Installation complete" + + printf "\n${BOLD}Installed to:${NC} %s/.claude/\n" "$TARGET_DIR" + printf "\n${BOLD}Available commands:${NC}\n" + printf " /plan Start interactive planning session\n" + printf " /build Execute a plan with parallel weavers\n" + printf " /status [id] Check plan and weaver status\n" + + printf "\n${BOLD}Quick start:${NC}\n" + printf " cd %s\n" "$TARGET_DIR" + printf " claude\n" + printf " > /plan\n" + + printf "\n${BOLD}Tmux helpers:${NC}\n" + printf " source %s/.claude/lib/tmux.sh\n" "$TARGET_DIR" + printf " vertical_status\n" + + printf "\n" +} + +# ------------------------------------------------------------------------------ +# Main +# ------------------------------------------------------------------------------ + +main() { + printf "${BOLD}Claude Code Vertical Installer${NC}\n" + printf "Source: %s\n" "$SCRIPT_DIR" + printf "Target: %s\n" "$TARGET_DIR" + + # Don't install to self + if [[ "$SCRIPT_DIR" == "$TARGET_DIR" ]]; then + log_error "Cannot install to source directory" + log_info "Run from your project directory or specify a target:" + printf " %s/install.sh /path/to/your/project\n" "$SCRIPT_DIR" + exit 1 + fi + + check_prerequisites + create_directories + install_components + + if verify_installation; then + print_summary + else + log_error "Installation completed with errors" + exit 1 + fi +} + +main "$@"