Files
giuseppe-trisciuoglio__deve…/scripts/install-claude.sh
T
2026-03-23 18:34:49 +01:00

507 lines
18 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Claude Code Interactive Developer Kit Installer Script
# Usage: install-claude.sh "<plugin_json_files>"
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Get plugin files from argument
PLUGIN_JSON_FILES="$1"
DEVKIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Confirm installation
read -p "Are you installing for Claude Code? (y/N): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo -e "${RED}✗ Installation cancelled.${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}Step 1: Target Project${NC}"
read -p "Enter the project path (absolute or relative, or press Enter for current directory): " project_path
if [[ -z "$project_path" ]]; then
project_path="."
fi
if [[ ! -d "$project_path" ]]; then
echo -n " ⚠ $project_path does not exist. Create it? (y/N): "
read create_dir
if [[ "$create_dir" != "y" && "$create_dir" != "Y" ]]; then
echo -e "${RED}✗ Installation cancelled.${NC}"
exit 1
fi
mkdir -p "$project_path"
fi
TARGET_PROJECT="$(cd "$project_path" && pwd)"
TARGET_DIR="$TARGET_PROJECT/.claude"
mkdir -p "$TARGET_DIR/agents"
mkdir -p "$TARGET_DIR/commands"
mkdir -p "$TARGET_DIR/skills"
mkdir -p "$TARGET_DIR/rules"
mkdir -p "$TARGET_DIR/hooks"
echo " → Target: $TARGET_PROJECT"
echo ""
# List available plugins
echo -e "${GREEN}Step 2: Available Plugins${NC}"
echo ""
declare -a PLUGIN_NAMES
declare -a PLUGIN_PATHS
declare -a PLUGIN_DIRS
declare -a PLUGIN_BASE_DIRS
plugin_num=0
for plugin_json in $PLUGIN_JSON_FILES; do
if [[ -f "$plugin_json" ]]; then
plugin_num=$((plugin_num + 1))
plugin_name=$(jq -r '.name' "$plugin_json" 2>/dev/null || echo "unknown")
plugin_desc=$(jq -r '.description' "$plugin_json" 2>/dev/null || echo "")
num_agents=$(jq -r '.agents | length' "$plugin_json" 2>/dev/null || echo "0")
num_commands=$(jq -r '.commands | length' "$plugin_json" 2>/dev/null || echo "0")
num_skills=$(jq -r '.skills | length' "$plugin_json" 2>/dev/null || echo "0")
num_rules=$(jq -r '.rules | length' "$plugin_json" 2>/dev/null || echo "0")
PLUGIN_NAMES[$plugin_num]="$plugin_name"
PLUGIN_PATHS[$plugin_num]="$plugin_json"
PLUGIN_DIRS[$plugin_num]=$(dirname "$plugin_json")
PLUGIN_BASE_DIRS[$plugin_num]=$(dirname "$(dirname "$plugin_json")")
printf " %2d) ${GREEN}%s${NC}\n" "$plugin_num" "$plugin_name"
echo " $plugin_desc"
echo " Components: $num_agents agents, $num_commands commands, $num_skills skills, $num_rules rules"
echo ""
fi
done
if [[ $plugin_num -eq 0 ]]; then
echo -e "${RED}✗ No plugins found.${NC}"
exit 1
fi
read -p "Select plugins to install (comma-separated numbers, or 'all'): " selected_plugins
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}ℹ Starting Installation...${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
installed_count=0
skipped_count=0
# Register legacy single-file PreToolUse Bash hook in .claude/settings.json
_register_legacy_hook_in_settings() {
local target_dir="$1"
local hook_name="$2"
local settings_file="$target_dir/settings.json"
local hook_command="python3 \"\$CLAUDE_PROJECT_DIR/.claude/hooks/$hook_name\""
if [[ ! -f "$settings_file" ]]; then
cat > "$settings_file" << EOF
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$hook_command"
}
]
}
]
}
}
EOF
echo " ✓ Created .claude/settings.json with legacy hook: $hook_name"
return
fi
python3 - << PYEOF
import json, sys
settings_file = "$settings_file"
hook_command = "$hook_command"
with open(settings_file) as f:
settings = json.load(f)
settings.setdefault("hooks", {})
settings["hooks"].setdefault("PreToolUse", [])
bash_entry = None
for entry in settings["hooks"]["PreToolUse"]:
if entry.get("matcher") == "Bash":
bash_entry = entry
break
if bash_entry is None:
bash_entry = {"matcher": "Bash", "hooks": []}
settings["hooks"]["PreToolUse"].append(bash_entry)
bash_entry.setdefault("hooks", [])
for h in bash_entry["hooks"]:
if h.get("command") == hook_command:
print(" ○ Legacy hook already registered in settings.json")
sys.exit(0)
bash_entry["hooks"].append({"type": "command", "command": hook_command})
with open(settings_file, "w") as f:
json.dump(settings, f, indent=2)
f.write("\n")
print(" ✓ Registered legacy hook in .claude/settings.json: $hook_name")
PYEOF
}
_copy_hook_file() {
local source_file="$1"
local target_dir="$2"
local hook_name
local target_file
hook_name=$(basename "$source_file")
target_file="$target_dir/hooks/$hook_name"
if [[ -f "$target_file" ]]; then
echo -n " ⚠ $hook_name already exists. [O]verwrite, [S]kip? "
read -n 1 conflict_action
echo ""
case $conflict_action in
O|o)
cp "$source_file" "$target_file"
chmod +x "$target_file"
echo " ✓ Overwritten: $hook_name"
installed_count=$((installed_count + 1))
return 0
;;
*)
echo " ○ Skipped: $hook_name"
skipped_count=$((skipped_count + 1))
return 1
;;
esac
fi
cp "$source_file" "$target_file"
chmod +x "$target_file"
echo " ✓ Hook file: $hook_name"
installed_count=$((installed_count + 1))
return 0
}
_register_hooks_config_in_settings() {
local target_dir="$1"
local hooks_config_file="$2"
local settings_file="$target_dir/settings.json"
python3 - << PYEOF
import json
from pathlib import Path
settings_file = Path("$settings_file")
hooks_file = Path("$hooks_config_file")
if settings_file.exists():
with settings_file.open() as f:
settings = json.load(f)
else:
settings = {}
with hooks_file.open() as f:
hook_config = json.load(f)
settings.setdefault("hooks", {})
for event_name, matcher_entries in hook_config.get("hooks", {}).items():
target_entries = settings["hooks"].setdefault(event_name, [])
for matcher_entry in matcher_entries:
normalized_entry = json.loads(json.dumps(matcher_entry))
for hook in normalized_entry.get("hooks", []):
command = hook.get("command")
if isinstance(command, str):
hook["command"] = command.replace(
"${CLAUDE_PLUGIN_ROOT}",
"$CLAUDE_PROJECT_DIR/.claude"
)
if normalized_entry not in target_entries:
target_entries.append(normalized_entry)
with settings_file.open("w") as f:
json.dump(settings, f, indent=2)
f.write("\n")
PYEOF
echo " ✓ Registered hooks config in .claude/settings.json: $(basename "$hooks_config_file")"
}
# Install selected plugins
for ((i=1; i<=plugin_num; i++)); do
should_install=0
if [[ "$selected_plugins" == "all" ]]; then
should_install=1
else
# Check if current plugin number is in selected list
for num in $(echo "$selected_plugins" | tr ',' ' '); do
if [[ "$num" == "$i" ]]; then
should_install=1
break
fi
done
fi
if [[ "$should_install" == "1" ]]; then
plugin_json="${PLUGIN_PATHS[$i]}"
base_dir="${PLUGIN_BASE_DIRS[$i]}"
plugin_name="${PLUGIN_NAMES[$i]}"
echo ""
echo -e "${CYAN}Installing from: $plugin_name${NC}"
# Install agents
agents=$(jq -r '.agents[]? // empty' "$plugin_json" 2>/dev/null)
if [[ -n "$agents" ]]; then
for agent in $agents; do
agent_path="$base_dir/$agent"
if [[ -f "$agent_path" ]]; then
agent_name=$(basename "$agent")
target_file="$TARGET_DIR/agents/$agent_name"
if [[ -f "$target_file" ]]; then
echo -n " ⚠ $agent_name already exists. [O]verwrite, [S]kip, [R]ename? "
read -n 1 conflict_action
echo ""
case $conflict_action in
O|o)
cp "$agent_path" "$target_file"
echo " ✓ Overwritten: $agent_name"
installed_count=$((installed_count + 1))
;;
R|r)
read -p " Enter new name: " new_name
cp "$agent_path" "$TARGET_DIR/agents/$new_name.md"
echo " ✓ Installed as: $new_name.md"
installed_count=$((installed_count + 1))
;;
*)
echo " ○ Skipped: $agent_name"
skipped_count=$((skipped_count + 1))
;;
esac
else
cp "$agent_path" "$target_file"
echo " ✓ Agent: $agent_name"
installed_count=$((installed_count + 1))
fi
fi
done
fi
# Install commands
commands=$(jq -r '.commands[]? // empty' "$plugin_json" 2>/dev/null)
if [[ -n "$commands" ]]; then
for cmd in $commands; do
cmd_path="$base_dir/$cmd"
if [[ -f "$cmd_path" ]]; then
cmd_name=$(basename "$cmd")
cmd_subdir=$(dirname "$cmd")
cmd_subdir_rel=""
if [[ "$cmd_subdir" != "." ]]; then
cmd_subdir_rel=$(echo "$cmd_subdir" | sed 's|^\./commands||' | sed 's|^\./commands/||')
if [[ -n "$cmd_subdir_rel" ]]; then
mkdir -p "$TARGET_DIR/commands/$cmd_subdir_rel"
target_file="$TARGET_DIR/commands/$cmd_subdir_rel/$cmd_name"
else
target_file="$TARGET_DIR/commands/$cmd_name"
fi
else
target_file="$TARGET_DIR/commands/$cmd_name"
fi
if [[ -f "$target_file" ]]; then
echo -n " ⚠ $cmd_name already exists. [O]verwrite, [S]kip, [R]ename? "
read -n 1 conflict_action
echo ""
case $conflict_action in
O|o)
cp "$cmd_path" "$target_file"
echo " ✓ Overwritten: $cmd_name"
installed_count=$((installed_count + 1))
;;
R|r)
read -p " Enter new name: " new_name
if [[ -n "$cmd_subdir_rel" ]]; then
cp "$cmd_path" "$TARGET_DIR/commands/$cmd_subdir_rel/$new_name.md"
else
cp "$cmd_path" "$TARGET_DIR/commands/$new_name.md"
fi
echo " ✓ Installed as: $new_name.md"
installed_count=$((installed_count + 1))
;;
*)
echo " ○ Skipped: $cmd_name"
skipped_count=$((skipped_count + 1))
;;
esac
else
cp "$cmd_path" "$target_file"
echo " ✓ Command: $cmd_name"
installed_count=$((installed_count + 1))
fi
fi
done
fi
# Install skills
skills=$(jq -r '.skills[]? // empty' "$plugin_json" 2>/dev/null)
if [[ -n "$skills" ]]; then
for skill_pattern in $skills; do
for skill_dir in $base_dir/$skill_pattern; do
if [[ -d "$skill_dir" ]]; then
skill_name=$(basename "$skill_dir")
target_skill_dir="$TARGET_DIR/skills/$skill_name"
if [[ -d "$target_skill_dir" ]]; then
echo -n " ⚠ $skill_name already exists. [O]verwrite, [S]kip, [R]ename? "
read -n 1 conflict_action
echo ""
case $conflict_action in
O|o)
rm -rf "$target_skill_dir"
cp -r "$skill_dir" "$target_skill_dir"
echo " ✓ Overwritten: $skill_name"
installed_count=$((installed_count + 1))
;;
R|r)
read -p " Enter new name: " new_name
cp -r "$skill_dir" "$TARGET_DIR/skills/$new_name"
echo " ✓ Installed as: $new_name"
installed_count=$((installed_count + 1))
;;
*)
echo " ○ Skipped: $skill_name"
skipped_count=$((skipped_count + 1))
;;
esac
else
cp -r "$skill_dir" "$target_skill_dir"
echo " ✓ Skill: $skill_name"
installed_count=$((installed_count + 1))
fi
fi
done
done
fi
# Install rules
rules=$(jq -r '.rules[]? // empty' "$plugin_json" 2>/dev/null)
if [[ -n "$rules" ]]; then
rules_target_dir="$TARGET_DIR/rules/$plugin_name"
mkdir -p "$rules_target_dir"
for rule in $rules; do
rule_path="$base_dir/$rule"
if [[ -f "$rule_path" ]]; then
rule_name=$(basename "$rule")
target_file="$rules_target_dir/$rule_name"
if [[ -f "$target_file" ]]; then
echo -n " ⚠ Rule $rule_name already exists. [O]verwrite, [S]kip? "
read -n 1 conflict_action
echo ""
case $conflict_action in
O|o)
cp "$rule_path" "$target_file"
echo " ✓ Overwritten: $rule_name"
installed_count=$((installed_count + 1))
;;
*)
echo " ○ Skipped: $rule_name"
skipped_count=$((skipped_count + 1))
;;
esac
else
cp "$rule_path" "$target_file"
echo " ✓ Rule: $plugin_name/$rule_name"
installed_count=$((installed_count + 1))
fi
fi
done
fi
# Install hooks
hooks=$(jq -r '
if .hooks == null then empty
elif (.hooks | type) == "array" then .hooks[]
elif (.hooks | type) == "string" then .hooks
else empty
end
' "$plugin_json" 2>/dev/null)
if [[ -n "$hooks" ]]; then
for hook in $hooks; do
hook_path="$base_dir/$hook"
if [[ -f "$hook_path" ]]; then
hook_name=$(basename "$hook_path")
if [[ "$hook_name" == "hooks.json" ]]; then
hook_dir=$(dirname "$hook_path")
for hook_impl in "$hook_dir"/*; do
if [[ -f "$hook_impl" && "$(basename "$hook_impl")" != "hooks.json" ]]; then
_copy_hook_file "$hook_impl" "$TARGET_DIR"
fi
done
_register_hooks_config_in_settings "$TARGET_DIR" "$hook_path"
else
if _copy_hook_file "$hook_path" "$TARGET_DIR"; then
_register_legacy_hook_in_settings "$TARGET_DIR" "$hook_name"
fi
fi
fi
done
fi
fi
done
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}✓ Installation Complete!${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " Target directory: $TARGET_DIR/"
echo " Files installed: $installed_count"
echo " Files skipped: $skipped_count"
echo ""
echo -e "${YELLOW}Next Steps:${NC}"
echo " 1. Navigate to your project: cd $TARGET_PROJECT"
echo " 2. Start Claude Code in the project directory"
echo " 3. Your skills, agents, and commands are now available!"
echo ""
echo -e "${YELLOW}Usage:${NC}"
echo " - Skills are automatically discovered by Claude"
echo " - Rules are loaded from .claude/rules/ to enforce coding standards"
echo " - Hooks run automatically on tool events (configured in .claude/settings.json)"
echo " - Use @agent-name to invoke agents"
echo " - Use /command-name to run commands"
echo ""