#!/usr/bin/env bash # lib/claude-config.sh — Shared Claude config directory helpers (#641, #707) # # Provides: # setup_claude_dir [] — Create/migrate a Claude config directory # setup_claude_config_dir [auto_yes] — Wrapper for default CLAUDE_CONFIG_DIR # _env_set_idempotent() — Write env vars to .env files # # Requires: CLAUDE_CONFIG_DIR, CLAUDE_SHARED_DIR (set by lib/env.sh) # Idempotent .env writer. # Usage: _env_set_idempotent KEY VALUE FILE _env_set_idempotent() { local key="$1" value="$2" file="$3" if grep -q "^${key}=" "$file" 2>/dev/null; then local existing existing=$(grep "^${key}=" "$file" | head -1 | cut -d= -f2-) if [ "$existing" != "$value" ]; then sed -i "s|^${key}=.*|${key}=${value}|" "$file" fi else printf '%s=%s\n' "$key" "$value" >> "$file" fi } # Create a Claude config directory, optionally migrating ~/.claude. # This is the parameterized helper that handles any CLAUDE_CONFIG_DIR path. # Usage: setup_claude_dir [auto_yes] # setup_claude_dir /path/to/config [true] # # Parameters: # $1 - Path to the Claude config directory to create # $2 - Auto-confirm mode (true/false), defaults to false # # Returns: 0 on success, 1 on failure setup_claude_dir() { local config_dir="$1" local auto_yes="${2:-false}" local home_claude="${HOME}/.claude" # Create the config directory (idempotent) install -d -m 0700 -o "$USER" "$config_dir" echo "Claude: ${config_dir} (ready)" # If ~/.claude is already a symlink to config_dir, nothing to do if [ -L "$home_claude" ]; then local link_target link_target=$(readlink -f "$home_claude") local config_real config_real=$(readlink -f "$config_dir") if [ "$link_target" = "$config_real" ]; then echo "Claude: ${home_claude} -> ${config_dir} (symlink OK)" return 0 fi fi local home_exists=false home_nonempty=false local config_nonempty=false # Check ~/.claude (skip if it's a symlink — already handled above) if [ -d "$home_claude" ] && [ ! -L "$home_claude" ]; then home_exists=true if [ -n "$(ls -A "$home_claude" 2>/dev/null)" ]; then home_nonempty=true fi fi # Check config_dir contents if [ -n "$(ls -A "$config_dir" 2>/dev/null)" ]; then config_nonempty=true fi # Case: both non-empty — abort, operator must reconcile if [ "$home_nonempty" = true ] && [ "$config_nonempty" = true ]; then echo "ERROR: both ${home_claude} and ${config_dir} exist and are non-empty" >&2 echo " Reconcile manually: merge or remove one, then re-run disinto init" >&2 return 1 fi # Case: ~/.claude exists and config_dir is empty — offer migration if [ "$home_nonempty" = true ] && [ "$config_nonempty" = false ]; then local do_migrate=false if [ "$auto_yes" = true ]; then do_migrate=true elif [ -t 0 ]; then read -rp "Migrate ${home_claude} to ${config_dir}? [Y/n] " confirm if [[ ! "$confirm" =~ ^[Nn] ]]; then do_migrate=true fi else echo "Warning: ${home_claude} exists but cannot prompt for migration (no TTY)" >&2 echo " Re-run with --yes to auto-migrate, or move files manually" >&2 return 0 fi if [ "$do_migrate" = true ]; then # Move contents (not the dir itself) to preserve config_dir ownership cp -a "$home_claude/." "$config_dir/" rm -rf "$home_claude" ln -sfn "$config_dir" "$home_claude" echo "Claude: migrated ${home_claude} -> ${config_dir}" return 0 fi fi # Case: ~/.claude exists but is empty, or doesn't exist — create symlink if [ "$home_exists" = true ] && [ "$home_nonempty" = false ]; then rmdir "$home_claude" 2>/dev/null || true fi if [ ! -e "$home_claude" ]; then ln -sfn "$config_dir" "$home_claude" echo "Claude: ${home_claude} -> ${config_dir} (symlink created)" fi } # Create the shared CLAUDE_CONFIG_DIR, optionally migrating ~/.claude. # Wrapper around setup_claude_dir for the default config directory. # Usage: setup_claude_config_dir [auto_yes] setup_claude_config_dir() { local auto_yes="${1:-false}" setup_claude_dir "$CLAUDE_CONFIG_DIR" "$auto_yes" }