fix(trae): harden install and uninstall flow

This commit is contained in:
Affaan Mustafa
2026-03-28 20:45:37 -04:00
parent 4fcaaf8a89
commit e686bcbc82
4 changed files with 118 additions and 60 deletions

View File

@@ -1,6 +1,6 @@
# Everything Claude Code for Trae # Everything Claude Code for Trae
Bring [Everything Claude Code](https://github.com/anthropics/courses/tree/master/everything-claude-code) (ECC) workflows to Trae IDE. This repository provides custom commands, agents, skills, and rules that can be installed into any Trae project with a single command. Bring Everything Claude Code (ECC) workflows to Trae IDE. This repository provides custom commands, agents, skills, and rules that can be installed into any Trae project with a single command.
## Quick Start ## Quick Start
@@ -76,11 +76,12 @@ This creates `~/.trae-cn/` with all ECC components. All Trae projects will use t
### Force Environment ### Force Environment
```bash ```bash
# Force CN environment (global setting) # From project root, force the CN environment
TRAE_ENV=cn ./install.sh TRAE_ENV=cn .trae/install.sh
# Use default environment (default) # From inside the .trae folder
./install.sh cd .trae
TRAE_ENV=cn ./install.sh
``` ```
**Note**: `TRAE_ENV` is a global environment variable that applies to the entire installation session. **Note**: `TRAE_ENV` is a global environment variable that applies to the entire installation session.

View File

@@ -1,6 +1,6 @@
# Everything Claude Code for Trae # Everything Claude Code for Trae
为 Trae IDE 带来 [Everything Claude Code](https://github.com/anthropics/courses/tree/master/everything-claude-code) (ECC) 工作流。此仓库提供自定义命令、智能体、技能和规则,可以通过单个命令安装到任何 Trae 项目中。 为 Trae IDE 带来 Everything Claude Code (ECC) 工作流。此仓库提供自定义命令、智能体、技能和规则,可以通过单个命令安装到任何 Trae 项目中。
## 快速开始 ## 快速开始
@@ -84,10 +84,11 @@ TRAE_ENV=cn .trae/install.sh ~
### 强制指定环境 ### 强制指定环境
```bash ```bash
# 强制使用 CN 环境 # 从项目根目录强制使用 CN 环境
TRAE_ENV=cn ./install.sh TRAE_ENV=cn .trae/install.sh
# 使用默认环境 # 进入 .trae 目录后使用默认环境
cd .trae
./install.sh ./install.sh
``` ```

View File

@@ -29,6 +29,16 @@ get_trae_dir() {
fi fi
} }
ensure_manifest_entry() {
local manifest="$1"
local entry="$2"
touch "$manifest"
if ! grep -Fqx "$entry" "$manifest"; then
echo "$entry" >> "$manifest"
fi
}
# Install function # Install function
do_install() { do_install() {
local target_dir="$PWD" local target_dir="$PWD"
@@ -70,7 +80,7 @@ do_install() {
# Manifest file to track installed files # Manifest file to track installed files
MANIFEST="$trae_full_path/.ecc-manifest" MANIFEST="$trae_full_path/.ecc-manifest"
rm -f "$MANIFEST" touch "$MANIFEST"
# Counters for summary # Counters for summary
commands=0 commands=0
@@ -86,9 +96,11 @@ do_install() {
local_name=$(basename "$f") local_name=$(basename "$f")
target_path="$trae_full_path/commands/$local_name" target_path="$trae_full_path/commands/$local_name"
if [ ! -f "$target_path" ]; then if [ ! -f "$target_path" ]; then
cp "$f" "$target_path" 2>/dev/null || true cp "$f" "$target_path"
echo "commands/$local_name" >> "$MANIFEST" ensure_manifest_entry "$MANIFEST" "commands/$local_name"
commands=$((commands + 1)) commands=$((commands + 1))
else
ensure_manifest_entry "$MANIFEST" "commands/$local_name"
fi fi
done done
fi fi
@@ -100,9 +112,11 @@ do_install() {
local_name=$(basename "$f") local_name=$(basename "$f")
target_path="$trae_full_path/agents/$local_name" target_path="$trae_full_path/agents/$local_name"
if [ ! -f "$target_path" ]; then if [ ! -f "$target_path" ]; then
cp "$f" "$target_path" 2>/dev/null || true cp "$f" "$target_path"
echo "agents/$local_name" >> "$MANIFEST" ensure_manifest_entry "$MANIFEST" "agents/$local_name"
agents=$((agents + 1)) agents=$((agents + 1))
else
ensure_manifest_entry "$MANIFEST" "agents/$local_name"
fi fi
done done
fi fi
@@ -113,15 +127,21 @@ do_install() {
[ -d "$d" ] || continue [ -d "$d" ] || continue
skill_name="$(basename "$d")" skill_name="$(basename "$d")"
target_skill_dir="$trae_full_path/skills/$skill_name" target_skill_dir="$trae_full_path/skills/$skill_name"
if [ ! -d "$target_skill_dir" ]; then skill_copied=0
mkdir -p "$target_skill_dir"
cp -r "$d"* "$target_skill_dir/" 2>/dev/null || true while IFS= read -r source_file; do
for skill_file in "$target_skill_dir"/*; do relative_path="${source_file#$d}"
[ -f "$skill_file" ] || continue target_path="$target_skill_dir/$relative_path"
relative_path="skills/$skill_name/$(basename "$skill_file")"
echo "$relative_path" >> "$MANIFEST" mkdir -p "$(dirname "$target_path")"
done if [ ! -f "$target_path" ]; then
echo "skills/$skill_name" >> "$MANIFEST" cp "$source_file" "$target_path"
skill_copied=1
fi
ensure_manifest_entry "$MANIFEST" "skills/$skill_name/$relative_path"
done < <(find "$d" -type f | sort)
if [ "$skill_copied" -eq 1 ]; then
skills=$((skills + 1)) skills=$((skills + 1))
fi fi
done done
@@ -129,18 +149,17 @@ do_install() {
# Copy rules from repo root # Copy rules from repo root
if [ -d "$REPO_ROOT/rules" ]; then if [ -d "$REPO_ROOT/rules" ]; then
if [ -d "$REPO_ROOT/rules/common" ]; then while IFS= read -r rule_file; do
for f in "$REPO_ROOT/rules/common"/*.md; do relative_path="${rule_file#$REPO_ROOT/rules/}"
[ -f "$f" ] || continue target_path="$trae_full_path/rules/$relative_path"
local_name=$(basename "$f")
target_path="$trae_full_path/rules/$local_name" mkdir -p "$(dirname "$target_path")"
if [ ! -f "$target_path" ]; then if [ ! -f "$target_path" ]; then
cp "$f" "$target_path" 2>/dev/null || true cp "$rule_file" "$target_path"
echo "rules/$local_name" >> "$MANIFEST" rules=$((rules + 1))
rules=$((rules + 1)) fi
fi ensure_manifest_entry "$MANIFEST" "rules/$relative_path"
done done < <(find "$REPO_ROOT/rules" -type f | sort)
fi
fi fi
# Copy README files from this directory # Copy README files from this directory
@@ -149,9 +168,11 @@ do_install() {
local_name=$(basename "$readme_file") local_name=$(basename "$readme_file")
target_path="$trae_full_path/$local_name" target_path="$trae_full_path/$local_name"
if [ ! -f "$target_path" ]; then if [ ! -f "$target_path" ]; then
cp "$readme_file" "$target_path" 2>/dev/null || true cp "$readme_file" "$target_path"
echo "$local_name" >> "$MANIFEST" ensure_manifest_entry "$MANIFEST" "$local_name"
other=$((other + 1)) other=$((other + 1))
else
ensure_manifest_entry "$MANIFEST" "$local_name"
fi fi
fi fi
done done
@@ -162,16 +183,18 @@ do_install() {
local_name=$(basename "$script_file") local_name=$(basename "$script_file")
target_path="$trae_full_path/$local_name" target_path="$trae_full_path/$local_name"
if [ ! -f "$target_path" ]; then if [ ! -f "$target_path" ]; then
cp "$script_file" "$target_path" 2>/dev/null || true cp "$script_file" "$target_path"
chmod +x "$target_path" 2>/dev/null || true chmod +x "$target_path"
echo "$local_name" >> "$MANIFEST" ensure_manifest_entry "$MANIFEST" "$local_name"
other=$((other + 1)) other=$((other + 1))
else
ensure_manifest_entry "$MANIFEST" "$local_name"
fi fi
fi fi
done done
# Add manifest file itself to manifest # Add manifest file itself to manifest
echo ".ecc-manifest" >> "$MANIFEST" ensure_manifest_entry "$MANIFEST" ".ecc-manifest"
# Installation summary # Installation summary
echo "Installation complete!" echo "Installation complete!"

View File

@@ -26,6 +26,22 @@ get_trae_dir() {
fi fi
} }
resolve_path() {
python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$1"
}
is_valid_manifest_entry() {
local file_path="$1"
case "$file_path" in
""|/*|~*|*/../*|../*|*/..|..)
return 1
;;
esac
return 0
}
# Main uninstall function # Main uninstall function
do_uninstall() { do_uninstall() {
local target_dir="$PWD" local target_dir="$PWD"
@@ -61,6 +77,8 @@ do_uninstall() {
exit 1 exit 1
fi fi
trae_root_resolved="$(resolve_path "$trae_full_path")"
# Manifest file path # Manifest file path
MANIFEST="$trae_full_path/.ecc-manifest" MANIFEST="$trae_full_path/.ecc-manifest"
@@ -100,18 +118,35 @@ do_uninstall() {
# Read manifest and remove files # Read manifest and remove files
while IFS= read -r file_path; do while IFS= read -r file_path; do
[ -z "$file_path" ] && continue [ -z "$file_path" ] && continue
if ! is_valid_manifest_entry "$file_path"; then
echo "Skipped: $file_path (invalid manifest entry)"
skipped=$((skipped + 1))
continue
fi
full_path="$trae_full_path/$file_path" full_path="$trae_full_path/$file_path"
resolved_full="$(resolve_path "$full_path")"
if [ -f "$full_path" ]; then
rm -f "$full_path" case "$resolved_full" in
"$trae_root_resolved"|"$trae_root_resolved"/*)
;;
*)
echo "Skipped: $file_path (invalid manifest entry)"
skipped=$((skipped + 1))
continue
;;
esac
if [ -f "$resolved_full" ]; then
rm -f "$resolved_full"
echo "Removed: $file_path" echo "Removed: $file_path"
removed=$((removed + 1)) removed=$((removed + 1))
elif [ -d "$full_path" ]; then elif [ -d "$resolved_full" ]; then
# Only remove directory if it's empty # Only remove directory if it's empty
if [ -z "$(ls -A "$full_path" 2>/dev/null)" ]; then if [ -z "$(ls -A "$resolved_full" 2>/dev/null)" ]; then
rmdir "$full_path" 2>/dev/null || true rmdir "$resolved_full" 2>/dev/null || true
if [ ! -d "$full_path" ]; then if [ ! -d "$resolved_full" ]; then
echo "Removed: $file_path/" echo "Removed: $file_path/"
removed=$((removed + 1)) removed=$((removed + 1))
fi fi
@@ -123,18 +158,16 @@ do_uninstall() {
skipped=$((skipped + 1)) skipped=$((skipped + 1))
fi fi
done < "$MANIFEST" done < "$MANIFEST"
# Try to remove subdirectories if they're empty while IFS= read -r empty_dir; do
for subdir in commands agents skills rules; do [ "$empty_dir" = "$trae_full_path" ] && continue
subdir_path="$trae_full_path/$subdir" relative_dir="${empty_dir#$trae_full_path/}"
if [ -d "$subdir_path" ] && [ -z "$(ls -A "$subdir_path" 2>/dev/null)" ]; then rmdir "$empty_dir" 2>/dev/null || true
rmdir "$subdir_path" 2>/dev/null || true if [ ! -d "$empty_dir" ]; then
if [ ! -d "$subdir_path" ]; then echo "Removed: $relative_dir/"
echo "Removed: $subdir/" removed=$((removed + 1))
removed=$((removed + 1))
fi
fi fi
done done < <(find "$trae_full_path" -depth -type d -empty 2>/dev/null | sort -r)
# Try to remove the main trae directory if it's empty # Try to remove the main trae directory if it's empty
if [ -d "$trae_full_path" ] && [ -z "$(ls -A "$trae_full_path" 2>/dev/null)" ]; then if [ -d "$trae_full_path" ] && [ -z "$(ls -A "$trae_full_path" 2>/dev/null)" ]; then