From e686bcbc82013e9f70fd2a3edf8d03be9ee9ceb1 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Sat, 28 Mar 2026 20:45:37 -0400 Subject: [PATCH] fix(trae): harden install and uninstall flow --- .trae/README.md | 11 +++--- .trae/README.zh-CN.md | 9 +++-- .trae/install.sh | 87 +++++++++++++++++++++++++++---------------- .trae/uninstall.sh | 71 +++++++++++++++++++++++++---------- 4 files changed, 118 insertions(+), 60 deletions(-) diff --git a/.trae/README.md b/.trae/README.md index 0c04a685..0eca108f 100644 --- a/.trae/README.md +++ b/.trae/README.md @@ -1,6 +1,6 @@ # 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 @@ -76,11 +76,12 @@ This creates `~/.trae-cn/` with all ECC components. All Trae projects will use t ### Force Environment ```bash -# Force CN environment (global setting) -TRAE_ENV=cn ./install.sh +# From project root, force the CN environment +TRAE_ENV=cn .trae/install.sh -# Use default environment (default) -./install.sh +# From inside the .trae folder +cd .trae +TRAE_ENV=cn ./install.sh ``` **Note**: `TRAE_ENV` is a global environment variable that applies to the entire installation session. diff --git a/.trae/README.zh-CN.md b/.trae/README.zh-CN.md index 406372d6..3fcc43e3 100644 --- a/.trae/README.zh-CN.md +++ b/.trae/README.zh-CN.md @@ -1,6 +1,6 @@ # 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 -# 强制使用 CN 环境 -TRAE_ENV=cn ./install.sh +# 从项目根目录强制使用 CN 环境 +TRAE_ENV=cn .trae/install.sh -# 使用默认环境 +# 进入 .trae 目录后使用默认环境 +cd .trae ./install.sh ``` diff --git a/.trae/install.sh b/.trae/install.sh index 39f4324e..da0d860c 100755 --- a/.trae/install.sh +++ b/.trae/install.sh @@ -29,6 +29,16 @@ get_trae_dir() { fi } +ensure_manifest_entry() { + local manifest="$1" + local entry="$2" + + touch "$manifest" + if ! grep -Fqx "$entry" "$manifest"; then + echo "$entry" >> "$manifest" + fi +} + # Install function do_install() { local target_dir="$PWD" @@ -70,7 +80,7 @@ do_install() { # Manifest file to track installed files MANIFEST="$trae_full_path/.ecc-manifest" - rm -f "$MANIFEST" + touch "$MANIFEST" # Counters for summary commands=0 @@ -86,9 +96,11 @@ do_install() { local_name=$(basename "$f") target_path="$trae_full_path/commands/$local_name" if [ ! -f "$target_path" ]; then - cp "$f" "$target_path" 2>/dev/null || true - echo "commands/$local_name" >> "$MANIFEST" + cp "$f" "$target_path" + ensure_manifest_entry "$MANIFEST" "commands/$local_name" commands=$((commands + 1)) + else + ensure_manifest_entry "$MANIFEST" "commands/$local_name" fi done fi @@ -100,9 +112,11 @@ do_install() { local_name=$(basename "$f") target_path="$trae_full_path/agents/$local_name" if [ ! -f "$target_path" ]; then - cp "$f" "$target_path" 2>/dev/null || true - echo "agents/$local_name" >> "$MANIFEST" + cp "$f" "$target_path" + ensure_manifest_entry "$MANIFEST" "agents/$local_name" agents=$((agents + 1)) + else + ensure_manifest_entry "$MANIFEST" "agents/$local_name" fi done fi @@ -113,15 +127,21 @@ do_install() { [ -d "$d" ] || continue skill_name="$(basename "$d")" target_skill_dir="$trae_full_path/skills/$skill_name" - if [ ! -d "$target_skill_dir" ]; then - mkdir -p "$target_skill_dir" - cp -r "$d"* "$target_skill_dir/" 2>/dev/null || true - for skill_file in "$target_skill_dir"/*; do - [ -f "$skill_file" ] || continue - relative_path="skills/$skill_name/$(basename "$skill_file")" - echo "$relative_path" >> "$MANIFEST" - done - echo "skills/$skill_name" >> "$MANIFEST" + skill_copied=0 + + while IFS= read -r source_file; do + relative_path="${source_file#$d}" + target_path="$target_skill_dir/$relative_path" + + mkdir -p "$(dirname "$target_path")" + if [ ! -f "$target_path" ]; then + 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)) fi done @@ -129,18 +149,17 @@ do_install() { # Copy rules from repo root if [ -d "$REPO_ROOT/rules" ]; then - if [ -d "$REPO_ROOT/rules/common" ]; then - for f in "$REPO_ROOT/rules/common"/*.md; do - [ -f "$f" ] || continue - local_name=$(basename "$f") - target_path="$trae_full_path/rules/$local_name" - if [ ! -f "$target_path" ]; then - cp "$f" "$target_path" 2>/dev/null || true - echo "rules/$local_name" >> "$MANIFEST" - rules=$((rules + 1)) - fi - done - fi + while IFS= read -r rule_file; do + relative_path="${rule_file#$REPO_ROOT/rules/}" + target_path="$trae_full_path/rules/$relative_path" + + mkdir -p "$(dirname "$target_path")" + if [ ! -f "$target_path" ]; then + cp "$rule_file" "$target_path" + rules=$((rules + 1)) + fi + ensure_manifest_entry "$MANIFEST" "rules/$relative_path" + done < <(find "$REPO_ROOT/rules" -type f | sort) fi # Copy README files from this directory @@ -149,9 +168,11 @@ do_install() { local_name=$(basename "$readme_file") target_path="$trae_full_path/$local_name" if [ ! -f "$target_path" ]; then - cp "$readme_file" "$target_path" 2>/dev/null || true - echo "$local_name" >> "$MANIFEST" + cp "$readme_file" "$target_path" + ensure_manifest_entry "$MANIFEST" "$local_name" other=$((other + 1)) + else + ensure_manifest_entry "$MANIFEST" "$local_name" fi fi done @@ -162,16 +183,18 @@ do_install() { local_name=$(basename "$script_file") target_path="$trae_full_path/$local_name" if [ ! -f "$target_path" ]; then - cp "$script_file" "$target_path" 2>/dev/null || true - chmod +x "$target_path" 2>/dev/null || true - echo "$local_name" >> "$MANIFEST" + cp "$script_file" "$target_path" + chmod +x "$target_path" + ensure_manifest_entry "$MANIFEST" "$local_name" other=$((other + 1)) + else + ensure_manifest_entry "$MANIFEST" "$local_name" fi fi done # Add manifest file itself to manifest - echo ".ecc-manifest" >> "$MANIFEST" + ensure_manifest_entry "$MANIFEST" ".ecc-manifest" # Installation summary echo "Installation complete!" diff --git a/.trae/uninstall.sh b/.trae/uninstall.sh index f2bd09f5..005216d9 100755 --- a/.trae/uninstall.sh +++ b/.trae/uninstall.sh @@ -26,6 +26,22 @@ get_trae_dir() { 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 do_uninstall() { local target_dir="$PWD" @@ -61,6 +77,8 @@ do_uninstall() { exit 1 fi + trae_root_resolved="$(resolve_path "$trae_full_path")" + # Manifest file path MANIFEST="$trae_full_path/.ecc-manifest" @@ -100,18 +118,35 @@ do_uninstall() { # Read manifest and remove files while IFS= read -r file_path; do [ -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" - - if [ -f "$full_path" ]; then - rm -f "$full_path" + resolved_full="$(resolve_path "$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" removed=$((removed + 1)) - elif [ -d "$full_path" ]; then + elif [ -d "$resolved_full" ]; then # Only remove directory if it's empty - if [ -z "$(ls -A "$full_path" 2>/dev/null)" ]; then - rmdir "$full_path" 2>/dev/null || true - if [ ! -d "$full_path" ]; then + if [ -z "$(ls -A "$resolved_full" 2>/dev/null)" ]; then + rmdir "$resolved_full" 2>/dev/null || true + if [ ! -d "$resolved_full" ]; then echo "Removed: $file_path/" removed=$((removed + 1)) fi @@ -123,18 +158,16 @@ do_uninstall() { skipped=$((skipped + 1)) fi done < "$MANIFEST" - - # Try to remove subdirectories if they're empty - for subdir in commands agents skills rules; do - subdir_path="$trae_full_path/$subdir" - if [ -d "$subdir_path" ] && [ -z "$(ls -A "$subdir_path" 2>/dev/null)" ]; then - rmdir "$subdir_path" 2>/dev/null || true - if [ ! -d "$subdir_path" ]; then - echo "Removed: $subdir/" - removed=$((removed + 1)) - fi + + while IFS= read -r empty_dir; do + [ "$empty_dir" = "$trae_full_path" ] && continue + relative_dir="${empty_dir#$trae_full_path/}" + rmdir "$empty_dir" 2>/dev/null || true + if [ ! -d "$empty_dir" ]; then + echo "Removed: $relative_dir/" + removed=$((removed + 1)) 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 if [ -d "$trae_full_path" ] && [ -z "$(ls -A "$trae_full_path" 2>/dev/null)" ]; then