- 新增 /optimize 系统优化命令,支持 full/ux/perf/code 四种模式 - 新增 .codex/ 配置及 sync-config 同步逻辑(sh + bat) - redis-safety 从 rules 下沉到 skills,减少每次会话固定 token 消耗 - java-dev 新增 Native SQL 规范(MySQL 保留字别名检测) - go-dev 性能优化表补充 SQL 保留字提示 - ui-ux-pro-max 去掉 paths 自动触发,改为手动/描述触发 - plugins.json 清理 3 个低频插件,新增 code-review - defensive rule 新增上下文管理提示(/btw、/compact) - check-toolsearch 修复版本显示(改用 claude --version) - gitignore 模板新增 10 个 AI 工具忽略规则 - Dockerfile 模板引入 BuildKit 缓存和 GOPROXY - publish.sh 优化 release notes 格式 - 更新 LICENSE 为 PolyForm NC、新增 NOTICE - README 大幅更新:Codex Part 3、插件列表、上下文管理最佳实践
424 lines
16 KiB
Bash
Executable File
424 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
if [ -z "${BASH_VERSION:-}" ]; then
|
||
exec /usr/bin/env bash "$0" "$@"
|
||
fi
|
||
|
||
set -euo pipefail
|
||
|
||
# 同步 .claude、.gemini 和 .codex 配置到用户根目录
|
||
|
||
# 颜色输出
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
print_line() {
|
||
printf '%b\n' "$1"
|
||
}
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||
CODEX_MANAGED_START="<!-- cc-use-exp codex managed:start -->"
|
||
CODEX_MANAGED_END="<!-- cc-use-exp codex managed:end -->"
|
||
CODEX_PROFILE_START="# cc-use-exp codex profiles:start"
|
||
CODEX_PROFILE_END="# cc-use-exp codex profiles:end"
|
||
|
||
merge_managed_block() {
|
||
local src_file="$1"
|
||
local dst_file="$2"
|
||
local start_marker="$3"
|
||
local end_marker="$4"
|
||
local tmp_file
|
||
|
||
tmp_file="$(mktemp)"
|
||
|
||
if [[ -f "$dst_file" ]]; then
|
||
awk -v start="$start_marker" -v end="$end_marker" '
|
||
$0 == start { skip = 1; next }
|
||
$0 == end { skip = 0; next }
|
||
!skip { print }
|
||
' "$dst_file" > "$tmp_file"
|
||
fi
|
||
|
||
if [[ -s "$tmp_file" ]]; then
|
||
printf '\n' >> "$tmp_file"
|
||
fi
|
||
|
||
printf '%s\n' "$start_marker" >> "$tmp_file"
|
||
cat "$src_file" >> "$tmp_file"
|
||
if [[ -s "$src_file" && -n "$(tail -c 1 "$src_file" 2>/dev/null)" ]]; then
|
||
printf '\n' >> "$tmp_file"
|
||
fi
|
||
printf '%s\n' "$end_marker" >> "$tmp_file"
|
||
|
||
mv "$tmp_file" "$dst_file"
|
||
}
|
||
|
||
build_profiles_bundle() {
|
||
local src_dir="$1"
|
||
local profile_file
|
||
local profile_list
|
||
|
||
CODEX_PROFILE_BUNDLE="$(mktemp)"
|
||
CODEX_PROFILES_SYNCED=0
|
||
profile_list="$(mktemp)"
|
||
|
||
find "$src_dir" -maxdepth 1 -type f -name '*.toml' | LC_ALL=C sort > "$profile_list"
|
||
|
||
printf '# Managed Codex profiles from cc-use-exp\n' >> "$CODEX_PROFILE_BUNDLE"
|
||
printf '# Use with: codex -p cc-fast-api | cc-balanced | cc-deep\n\n' >> "$CODEX_PROFILE_BUNDLE"
|
||
|
||
while IFS= read -r profile_file; do
|
||
[[ -n "$profile_file" ]] || continue
|
||
CODEX_PROFILES_SYNCED=$((CODEX_PROFILES_SYNCED + 1))
|
||
cat "$profile_file" >> "$CODEX_PROFILE_BUNDLE"
|
||
if [[ -s "$profile_file" && -n "$(tail -c 1 "$profile_file" 2>/dev/null)" ]]; then
|
||
printf '\n' >> "$CODEX_PROFILE_BUNDLE"
|
||
fi
|
||
printf '\n' >> "$CODEX_PROFILE_BUNDLE"
|
||
done < "$profile_list"
|
||
|
||
rm -f "$profile_list"
|
||
}
|
||
|
||
sync_managed_rules() {
|
||
local src_dir="$1"
|
||
local dst_dir="$2"
|
||
local manifest_file="$dst_dir/.cc-use-exp-managed"
|
||
local new_manifest
|
||
local rule_file
|
||
local rule_name
|
||
local rule_list
|
||
|
||
CODEX_RULES_SYNCED=0
|
||
mkdir -p "$dst_dir"
|
||
new_manifest="$(mktemp)"
|
||
rule_list="$(mktemp)"
|
||
|
||
find "$src_dir" -maxdepth 1 -type f -name '*.rules' | LC_ALL=C sort > "$rule_list"
|
||
|
||
while IFS= read -r rule_file; do
|
||
[[ -n "$rule_file" ]] || continue
|
||
rule_name="$(basename "$rule_file")"
|
||
printf '%s\n' "$rule_name" >> "$new_manifest"
|
||
cp "$rule_file" "$dst_dir/$rule_name"
|
||
CODEX_RULES_SYNCED=$((CODEX_RULES_SYNCED + 1))
|
||
done < "$rule_list"
|
||
|
||
rm -f "$rule_list"
|
||
|
||
if [[ -f "$manifest_file" ]]; then
|
||
while IFS= read -r rule_name; do
|
||
[[ -n "$rule_name" ]] || continue
|
||
if ! grep -Fxq "$rule_name" "$new_manifest"; then
|
||
rm -f "$dst_dir/$rule_name"
|
||
fi
|
||
done < "$manifest_file"
|
||
fi
|
||
|
||
mv "$new_manifest" "$manifest_file"
|
||
}
|
||
|
||
sync_managed_skills() {
|
||
local src_dir="$1"
|
||
local dst_dir="$2"
|
||
local manifest_file="$dst_dir/.cc-use-exp-managed"
|
||
local new_manifest
|
||
local skill_dir
|
||
local skill_name
|
||
local skill_list
|
||
|
||
CODEX_SKILLS_SYNCED=0
|
||
mkdir -p "$dst_dir"
|
||
new_manifest="$(mktemp)"
|
||
skill_list="$(mktemp)"
|
||
|
||
find "$src_dir" -mindepth 1 -maxdepth 1 -type d | LC_ALL=C sort > "$skill_list"
|
||
|
||
while IFS= read -r skill_dir; do
|
||
[[ -n "$skill_dir" ]] || continue
|
||
skill_name="$(basename "$skill_dir")"
|
||
printf '%s\n' "$skill_name" >> "$new_manifest"
|
||
rm -rf "$dst_dir/$skill_name"
|
||
cp -R "$skill_dir" "$dst_dir/$skill_name"
|
||
CODEX_SKILLS_SYNCED=$((CODEX_SKILLS_SYNCED + 1))
|
||
done < "$skill_list"
|
||
|
||
rm -f "$skill_list"
|
||
|
||
if [[ -f "$manifest_file" ]]; then
|
||
while IFS= read -r skill_name; do
|
||
[[ -n "$skill_name" ]] || continue
|
||
if ! grep -Fxq "$skill_name" "$new_manifest"; then
|
||
rm -rf "$dst_dir/$skill_name"
|
||
fi
|
||
done < "$manifest_file"
|
||
fi
|
||
|
||
mv "$new_manifest" "$manifest_file"
|
||
}
|
||
|
||
print_line "${GREEN}=== 配置同步工具 ===${NC}"
|
||
print_line "源目录: ${SCRIPT_DIR}"
|
||
print_line "目标目录: ${HOME}"
|
||
printf '\n'
|
||
|
||
# --- Claude Code ---
|
||
if [[ -d "${SCRIPT_DIR}/.claude" ]]; then
|
||
print_line "${GREEN}[Claude Code] 开始同步${NC}"
|
||
|
||
# 确保目标目录存在
|
||
mkdir -p ~/.claude
|
||
|
||
# 删除旧配置目录(保留历史记录、projects 等)
|
||
rm -rf ~/.claude/rules ~/.claude/skills ~/.claude/commands ~/.claude/templates ~/.claude/tasks
|
||
print_line "${YELLOW} 已清理旧配置目录${NC}"
|
||
|
||
# 复制配置目录
|
||
cp -r "${SCRIPT_DIR}/.claude/rules" ~/.claude/
|
||
cp -r "${SCRIPT_DIR}/.claude/skills" ~/.claude/
|
||
cp -r "${SCRIPT_DIR}/.claude/commands" ~/.claude/
|
||
cp -r "${SCRIPT_DIR}/.claude/templates" ~/.claude/
|
||
cp -r "${SCRIPT_DIR}/.claude/tasks" ~/.claude/
|
||
cp "${SCRIPT_DIR}/.claude/CLAUDE.md" ~/.claude/
|
||
|
||
print_line "${GREEN} ✓ rules/ skills/ commands/ templates/ tasks/ CLAUDE.md${NC}"
|
||
|
||
# --- Claude Code 插件检测 ---
|
||
PLUGIN_JSON="${SCRIPT_DIR}/.claude/plugins.json"
|
||
INSTALLED_JSON="${HOME}/.claude/plugins/installed_plugins.json"
|
||
if ! command -v claude &>/dev/null; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Claude Code] 未检测到 claude 命令,跳过插件检测${NC}"
|
||
print_line "${YELLOW} 安装方式: npm install -g @anthropic-ai/claude-code${NC}"
|
||
elif ! command -v python3 &>/dev/null; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Claude Code] 未检测到 python3,跳过插件检测${NC}"
|
||
print_line "${YELLOW} 插件检测需要 python3 解析 JSON,请安装后重试${NC}"
|
||
print_line "${YELLOW} macOS: brew install python3 | Ubuntu: sudo apt install python3${NC}"
|
||
elif [[ -f "$PLUGIN_JSON" ]]; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Claude Code] 正在检测推荐插件...${NC}"
|
||
|
||
# 使用 python3 解析 JSON 检查缺失插件
|
||
MISSING_PLUGINS=$(python3 -c "
|
||
import json, sys
|
||
try:
|
||
with open(sys.argv[1]) as f:
|
||
recommended = json.load(f)
|
||
installed = {}
|
||
try:
|
||
with open(sys.argv[2]) as f:
|
||
installed = json.load(f).get('plugins', {})
|
||
except (FileNotFoundError, json.JSONDecodeError):
|
||
pass
|
||
for p in recommended.get('recommendations', []):
|
||
key = p['id'] + '@' + p['marketplace']
|
||
if key not in installed:
|
||
print(p['id'] + '|' + p['name'] + '|' + p['marketplace'])
|
||
except Exception:
|
||
pass
|
||
" "$PLUGIN_JSON" "$INSTALLED_JSON")
|
||
|
||
if [[ -n "$MISSING_PLUGINS" ]]; then
|
||
print_line "${YELLOW}检测到以下推荐插件尚未安装:${NC}"
|
||
IFS=$'\n'
|
||
for item in $MISSING_PLUGINS; do
|
||
IFS='|' read -r id name marketplace <<< "$item"
|
||
print_line " - ${YELLOW}$name${NC} ($id)"
|
||
done
|
||
|
||
printf '\n'
|
||
read -p "是否现在安装上述缺失的插件?[Y/n] " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ || "$confirm" == "" ]]; then
|
||
IFS=$'\n'
|
||
for item in $MISSING_PLUGINS; do
|
||
IFS='|' read -r id name marketplace <<< "$item"
|
||
print_line "${GREEN}正在安装 $name...${NC}"
|
||
INSTALL_OUTPUT=$(claude plugin install "${id}@${marketplace}" 2>&1) || {
|
||
if echo "$INSTALL_OUTPUT" | grep -qi "auth\|login\|token\|credential\|unauthorized\|forbidden"; then
|
||
print_line "${RED}错误: Claude Code 未认证,请先运行 'claude login' 登录${NC}"
|
||
break
|
||
elif echo "$INSTALL_OUTPUT" | grep -qi "already installed\|already exists"; then
|
||
print_line "${YELLOW} $name 已安装,跳过${NC}"
|
||
else
|
||
print_line "${YELLOW}警告: $name 安装失败 — $INSTALL_OUTPUT${NC}"
|
||
fi
|
||
}
|
||
done
|
||
print_line "${GREEN}✓ 插件安装完成${NC}"
|
||
else
|
||
print_line "${YELLOW}已跳过插件安装。你可以之后手动安装。${NC}"
|
||
fi
|
||
else
|
||
print_line "${GREEN}✓ 所有推荐插件已安装${NC}"
|
||
fi
|
||
fi
|
||
else
|
||
print_line "${YELLOW}[Claude Code] 源目录不存在,跳过${NC}"
|
||
fi
|
||
|
||
printf '\n'
|
||
|
||
# --- Gemini CLI ---
|
||
if [[ -d "${SCRIPT_DIR}/.gemini" ]]; then
|
||
print_line "${GREEN}[Gemini CLI] 开始同步${NC}"
|
||
|
||
# 确保目标目录存在
|
||
mkdir -p ~/.gemini
|
||
|
||
# 删除旧配置目录(保留认证信息)
|
||
rm -rf ~/.gemini/commands ~/.gemini/skills ~/.gemini/rules ~/.gemini/policies
|
||
print_line "${YELLOW} 已清理旧配置目录${NC}"
|
||
|
||
# 确保目标目录存在
|
||
mkdir -p ~/.gemini/policies
|
||
|
||
# 复制配置
|
||
cp -r "${SCRIPT_DIR}/.gemini/commands" ~/.gemini/
|
||
cp -r "${SCRIPT_DIR}/.gemini/skills" ~/.gemini/
|
||
cp -r "${SCRIPT_DIR}/.gemini/rules" ~/.gemini/
|
||
cp "${SCRIPT_DIR}/.gemini/GEMINI.md" ~/.gemini/
|
||
cp "${SCRIPT_DIR}/.gemini/settings.json" ~/.gemini/
|
||
|
||
# 策略同步逻辑:最新规范要求使用 policies 目录
|
||
if [[ -d "${SCRIPT_DIR}/.gemini/policies" ]]; then
|
||
cp -r "${SCRIPT_DIR}/.gemini/policies/"* ~/.gemini/policies/
|
||
fi
|
||
# 清理旧的全局策略文件以防冲突
|
||
[ -f "${HOME}/.gemini/policy.toml" ] && rm "${HOME}/.gemini/policy.toml"
|
||
if [[ -f "${HOME}/.gemini/policy.json" ]]; then
|
||
mv "${HOME}/.gemini/policy.json" "${HOME}/.gemini/policy.json.bak_$(date +%F)"
|
||
fi
|
||
|
||
print_line "${GREEN} ✓ commands/ skills/ rules/ policies/ GEMINI.md settings.json${NC}"
|
||
|
||
# --- MCP 扩展检测 ---
|
||
EXT_JSON="${SCRIPT_DIR}/.gemini/extensions.json"
|
||
if ! command -v gemini &>/dev/null; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Gemini CLI] 未检测到 gemini 命令,跳过扩展检测${NC}"
|
||
print_line "${YELLOW} 安装方式: brew install gemini-cli (详见 README)${NC}"
|
||
elif ! command -v python3 &>/dev/null; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Gemini CLI] 未检测到 python3,跳过扩展检测${NC}"
|
||
print_line "${YELLOW} 扩展检测需要 python3 解析 JSON,请安装后重试${NC}"
|
||
print_line "${YELLOW} macOS: brew install python3 | Ubuntu: sudo apt install python3${NC}"
|
||
elif [[ -f "$EXT_JSON" ]]; then
|
||
printf '\n'
|
||
print_line "${YELLOW}[Gemini CLI] 正在检测推荐扩展...${NC}"
|
||
|
||
# 获取当前已安装的扩展列表(gemini extensions list 输出走 stderr)
|
||
INSTALLED_EXTS=$(gemini extensions list 2>&1 || echo "")
|
||
|
||
# 使用 python3 解析 JSON 并检查缺失
|
||
# 优化匹配逻辑:检查 id 是否作为独立单词出现在已安装列表中
|
||
MISSING_EXTS=$(python3 -c "
|
||
import json, sys, re
|
||
try:
|
||
with open(sys.argv[1]) as f:
|
||
data = json.load(f)
|
||
installed = sys.argv[2]
|
||
for ext in data.get('recommendations', []):
|
||
# 使用正则确保是完整匹配 ID
|
||
if not re.search(r'\b' + re.escape(ext['id']) + r'\b', installed):
|
||
print(ext['id'] + '|' + ext['name'] + '|' + ext['url'])
|
||
except Exception:
|
||
pass
|
||
" "$EXT_JSON" "$INSTALLED_EXTS")
|
||
|
||
if [[ -n "$MISSING_EXTS" ]]; then
|
||
print_line "${YELLOW}检测到以下推荐扩展尚未安装:${NC}"
|
||
IFS=$'\n'
|
||
for item in $MISSING_EXTS; do
|
||
IFS='|' read -r id name url <<< "$item"
|
||
print_line " - ${YELLOW}$name${NC} ($id)"
|
||
done
|
||
|
||
printf '\n'
|
||
read -p "是否现在安装上述缺失的扩展?[Y/n] " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ || "$confirm" == "" ]]; then
|
||
IFS=$'\n'
|
||
for item in $MISSING_EXTS; do
|
||
IFS='|' read -r id name url <<< "$item"
|
||
print_line "${GREEN}正在安装 $name...${NC}"
|
||
INSTALL_OUTPUT=$(gemini extensions install "$url" 2>&1) || {
|
||
if echo "$INSTALL_OUTPUT" | grep -qi "auth\|login\|token\|credential\|unauthorized\|forbidden"; then
|
||
print_line "${RED}错误: Gemini CLI 未认证,请先运行 'gemini auth login' 登录${NC}"
|
||
break
|
||
elif echo "$INSTALL_OUTPUT" | grep -qi "already installed\|already exists"; then
|
||
print_line "${YELLOW} $name 已安装,跳过${NC}"
|
||
else
|
||
print_line "${YELLOW}警告: $name 安装失败 — $INSTALL_OUTPUT${NC}"
|
||
fi
|
||
}
|
||
done
|
||
print_line "${GREEN}✓ 扩展安装完成${NC}"
|
||
else
|
||
print_line "${YELLOW}已跳过扩展安装。你可以之后手动安装。${NC}"
|
||
fi
|
||
else
|
||
print_line "${GREEN}✓ 所有推荐扩展已安装${NC}"
|
||
fi
|
||
fi
|
||
else
|
||
print_line "${YELLOW}[Gemini CLI] 源目录不存在,跳过${NC}"
|
||
fi
|
||
|
||
printf '\n'
|
||
|
||
# --- Codex ---
|
||
if [[ -d "${SCRIPT_DIR}/.codex" ]]; then
|
||
print_line "${GREEN}[Codex] 开始同步${NC}"
|
||
|
||
mkdir -p ~/.codex ~/.codex/rules ~/.agents/skills
|
||
|
||
CODEX_RULES_SYNCED=0
|
||
CODEX_SKILLS_SYNCED=0
|
||
|
||
CODEX_AGENTS_SRC="${SCRIPT_DIR}/.codex/global/AGENTS.md"
|
||
CODEX_AGENTS_DST="${HOME}/.codex/AGENTS.md"
|
||
CODEX_RULES_SRC="${SCRIPT_DIR}/.codex/global/rules"
|
||
CODEX_SKILLS_SRC="${SCRIPT_DIR}/.codex/skills"
|
||
|
||
if [[ -f "$CODEX_AGENTS_SRC" ]]; then
|
||
merge_managed_block "$CODEX_AGENTS_SRC" "$CODEX_AGENTS_DST" "$CODEX_MANAGED_START" "$CODEX_MANAGED_END"
|
||
print_line "${GREEN} ✓ 已合并 ~/.codex/AGENTS.md 受管区块${NC}"
|
||
else
|
||
print_line "${YELLOW} 未找到 global/AGENTS.md,跳过 AGENTS 同步${NC}"
|
||
fi
|
||
|
||
if [[ -d "$CODEX_RULES_SRC" ]]; then
|
||
sync_managed_rules "$CODEX_RULES_SRC" "${HOME}/.codex/rules"
|
||
fi
|
||
|
||
if [[ -d "$CODEX_SKILLS_SRC" ]]; then
|
||
sync_managed_skills "$CODEX_SKILLS_SRC" "${HOME}/.agents/skills"
|
||
fi
|
||
|
||
CODEX_PROFILES_SRC="${SCRIPT_DIR}/.codex/profiles"
|
||
CODEX_CONFIG_DST="${HOME}/.codex/config.toml"
|
||
CODEX_PROFILES_SYNCED=0
|
||
|
||
if [[ -d "$CODEX_PROFILES_SRC" ]]; then
|
||
build_profiles_bundle "$CODEX_PROFILES_SRC"
|
||
merge_managed_block "$CODEX_PROFILE_BUNDLE" "$CODEX_CONFIG_DST" "$CODEX_PROFILE_START" "$CODEX_PROFILE_END"
|
||
rm -f "$CODEX_PROFILE_BUNDLE"
|
||
print_line "${GREEN} ✓ profiles: ${CODEX_PROFILES_SYNCED} 个,已合并到 ~/.codex/config.toml${NC}"
|
||
else
|
||
print_line "${YELLOW} 未找到 profiles/,跳过 profile 同步${NC}"
|
||
fi
|
||
|
||
print_line "${GREEN} ✓ rules: ${CODEX_RULES_SYNCED} 个,同步到 ~/.codex/rules/${NC}"
|
||
print_line "${GREEN} ✓ skills: ${CODEX_SKILLS_SYNCED} 个,同步到 ~/.agents/skills/${NC}"
|
||
print_line "${YELLOW} 已保留 ~/.codex 运行态文件(auth/history/logs/cache)${NC}"
|
||
else
|
||
print_line "${YELLOW}[Codex] 源目录不存在,跳过${NC}"
|
||
fi
|
||
|
||
printf '\n'
|
||
print_line "${GREEN}=== 同步完成 ===${NC}"
|
||
print_line "${YELLOW}提示: 若在项目目录运行 gemini 出现 'Skill conflict detected' 警告:${NC}"
|
||
print_line " 这是由于 Gemini CLI 同时加载了全局 (~/.gemini) 和局部 (.gemini) 配置,属于预期行为。"
|
||
print_line " 局部配置将自动覆盖全局配置,不影响功能使用。"
|