2026-04-10 20:15:35 +00:00
|
|
|
#!/usr/bin/env bash
|
2026-04-12 01:50:23 +00:00
|
|
|
# lib/claude-config.sh — Shared Claude config directory helpers (#641, #707)
|
2026-04-10 20:15:35 +00:00
|
|
|
#
|
2026-04-12 01:50:23 +00:00
|
|
|
# Provides:
|
|
|
|
|
# setup_claude_dir <dir> [<auto_yes>] — 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
|
2026-04-10 20:15:35 +00:00
|
|
|
#
|
|
|
|
|
# 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
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 01:50:23 +00:00
|
|
|
# Create a Claude config directory, optionally migrating ~/.claude.
|
|
|
|
|
# This is the parameterized helper that handles any CLAUDE_CONFIG_DIR path.
|
|
|
|
|
# Usage: setup_claude_dir <config_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}"
|
2026-04-10 20:15:35 +00:00
|
|
|
local home_claude="${HOME}/.claude"
|
|
|
|
|
|
2026-04-12 01:50:23 +00:00
|
|
|
# Create the config directory (idempotent)
|
|
|
|
|
install -d -m 0700 -o "$USER" "$config_dir"
|
|
|
|
|
echo "Claude: ${config_dir} (ready)"
|
2026-04-10 20:15:35 +00:00
|
|
|
|
2026-04-12 01:50:23 +00:00
|
|
|
# If ~/.claude is already a symlink to config_dir, nothing to do
|
2026-04-10 20:15:35 +00:00
|
|
|
if [ -L "$home_claude" ]; then
|
|
|
|
|
local link_target
|
|
|
|
|
link_target=$(readlink -f "$home_claude")
|
|
|
|
|
local config_real
|
2026-04-12 01:50:23 +00:00
|
|
|
config_real=$(readlink -f "$config_dir")
|
2026-04-10 20:15:35 +00:00
|
|
|
if [ "$link_target" = "$config_real" ]; then
|
2026-04-12 01:50:23 +00:00
|
|
|
echo "Claude: ${home_claude} -> ${config_dir} (symlink OK)"
|
2026-04-10 20:15:35 +00:00
|
|
|
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
|
|
|
|
|
|
2026-04-12 01:50:23 +00:00
|
|
|
# Check config_dir contents
|
|
|
|
|
if [ -n "$(ls -A "$config_dir" 2>/dev/null)" ]; then
|
2026-04-10 20:15:35 +00:00
|
|
|
config_nonempty=true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Case: both non-empty — abort, operator must reconcile
|
|
|
|
|
if [ "$home_nonempty" = true ] && [ "$config_nonempty" = true ]; then
|
2026-04-12 01:50:23 +00:00
|
|
|
echo "ERROR: both ${home_claude} and ${config_dir} exist and are non-empty" >&2
|
2026-04-10 20:15:35 +00:00
|
|
|
echo " Reconcile manually: merge or remove one, then re-run disinto init" >&2
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-04-12 01:50:23 +00:00
|
|
|
# Case: ~/.claude exists and config_dir is empty — offer migration
|
2026-04-10 20:15:35 +00:00
|
|
|
if [ "$home_nonempty" = true ] && [ "$config_nonempty" = false ]; then
|
|
|
|
|
local do_migrate=false
|
|
|
|
|
if [ "$auto_yes" = true ]; then
|
|
|
|
|
do_migrate=true
|
|
|
|
|
elif [ -t 0 ]; then
|
2026-04-12 01:50:23 +00:00
|
|
|
read -rp "Migrate ${home_claude} to ${config_dir}? [Y/n] " confirm
|
2026-04-10 20:15:35 +00:00
|
|
|
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
|
2026-04-12 01:50:23 +00:00
|
|
|
# Move contents (not the dir itself) to preserve config_dir ownership
|
|
|
|
|
cp -a "$home_claude/." "$config_dir/"
|
2026-04-10 20:15:35 +00:00
|
|
|
rm -rf "$home_claude"
|
2026-04-12 01:50:23 +00:00
|
|
|
ln -sfn "$config_dir" "$home_claude"
|
|
|
|
|
echo "Claude: migrated ${home_claude} -> ${config_dir}"
|
2026-04-10 20:15:35 +00:00
|
|
|
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
|
2026-04-12 01:50:23 +00:00
|
|
|
ln -sfn "$config_dir" "$home_claude"
|
|
|
|
|
echo "Claude: ${home_claude} -> ${config_dir} (symlink created)"
|
2026-04-10 20:15:35 +00:00
|
|
|
fi
|
|
|
|
|
}
|
2026-04-12 01:50:23 +00:00
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
|
}
|