Skip to content

Instantly share code, notes, and snippets.

@wangyiyang
Created February 12, 2026 01:16
Show Gist options
  • Select an option

  • Save wangyiyang/90d2734165d7386d6dd0a66571f14624 to your computer and use it in GitHub Desktop.

Select an option

Save wangyiyang/90d2734165d7386d6dd0a66571f14624 to your computer and use it in GitHub Desktop.
Sync ~/.codex + ~/.claude to remote
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
sync-code-cli.sh
用途:
将本机的 Code CLI 配置(Codex/Claude)同步到远端同路径:
~/.claude/commands/ -> <remote>:~/.claude/commands/
~/.claude/CLAUDE.md -> <remote>:~/.claude/CLAUDE.md
~/.codex/AGENTS.md -> <remote>:~/.codex/AGENTS.md
~/.codex/prompts/ -> <remote>:~/.codex/prompts/
安全默认:
- 默认 dry-run:只显示将要变更的内容,不会写入远端
- 只有加 --apply 才会真正覆盖远端
- 目录同步在 --apply 时默认使用 --delete(镜像覆盖);可用 --no-delete 关闭
用法:
./sync-code-cli.sh --remote <user@host> [options]
必选参数:
--remote <user@host> 例如:--remote kk@ops.wangyiyang.cc
可选参数:
--apply 真正执行(否则默认 dry-run)
--no-delete 目录同步不使用 --delete(保留远端多余文件)
--cleanup-misplaced 清理误同步到远端 ~ 根目录的:commands/prompts/CLAUDE.md/AGENTS.md
(不会直接删除,会移动到 ~/.sync-cleanup-backup/<timestamp>/)
--backup-remote 在同步前备份远端目标到 ~/.sync-backup/<timestamp>/
--ssh-opts "<...>" 透传 ssh 参数(示例:--ssh-opts "-p 2222 -J jump")
--rsync-opts "<...>" 透传 rsync 参数(示例:--rsync-opts "--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r")
-h, --help 显示帮助
示例:
# 预览将要变更(dry-run)
./sync-code-cli.sh --remote kk@ops.wangyiyang.cc
# 真正执行并镜像覆盖(目录包含 --delete)
./sync-code-cli.sh --remote kk@ops.wangyiyang.cc --apply
# 真正执行但不删除远端多余文件
./sync-code-cli.sh --remote kk@ops.wangyiyang.cc --apply --no-delete
# 同步前备份 + 清理误同步路径
./sync-code-cli.sh --remote kk@ops.wangyiyang.cc --apply --backup-remote --cleanup-misplaced
注意:
- macOS 自带 rsync 版本较老,脚本避免使用较新的 rsync 参数(例如 --info=...)。
- 使用 --delete 有误删风险;建议先 dry-run 再 --apply。
EOF
}
die() {
echo "错误:$*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1"
}
REMOTE=""
APPLY=0
NO_DELETE=0
CLEANUP_MISPLACED=0
BACKUP_REMOTE=0
SSH_OPTS=""
RSYNC_OPTS_EXTRA=""
while [[ $# -gt 0 ]]; do
case "$1" in
--remote)
[[ $# -ge 2 ]] || die "--remote 需要参数"
REMOTE="$2"
shift 2
;;
--apply)
APPLY=1
shift
;;
--no-delete)
NO_DELETE=1
shift
;;
--cleanup-misplaced)
CLEANUP_MISPLACED=1
shift
;;
--backup-remote)
BACKUP_REMOTE=1
shift
;;
--ssh-opts)
[[ $# -ge 2 ]] || die "--ssh-opts 需要参数"
SSH_OPTS="$2"
shift 2
;;
--rsync-opts)
[[ $# -ge 2 ]] || die "--rsync-opts 需要参数"
RSYNC_OPTS_EXTRA="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "未知参数:$1(用 -h 查看帮助)"
;;
esac
done
[[ -n "$REMOTE" ]] || { usage; exit 2; }
need_cmd ssh
need_cmd rsync
LOCAL_CLAUDE_DIR="${HOME}/.claude"
LOCAL_CODEX_DIR="${HOME}/.codex"
[[ -d "${LOCAL_CLAUDE_DIR}/commands" ]] || die "本地不存在目录:${LOCAL_CLAUDE_DIR}/commands"
[[ -f "${LOCAL_CLAUDE_DIR}/CLAUDE.md" ]] || die "本地不存在文件:${LOCAL_CLAUDE_DIR}/CLAUDE.md"
[[ -f "${LOCAL_CODEX_DIR}/AGENTS.md" ]] || die "本地不存在文件:${LOCAL_CODEX_DIR}/AGENTS.md"
[[ -d "${LOCAL_CODEX_DIR}/prompts" ]] || die "本地不存在目录:${LOCAL_CODEX_DIR}/prompts"
# Common rsync flags; keep compatibility with older rsync (e.g., macOS built-in).
RSYNC_COMMON="-az --no-owner --no-group --stats --progress"
RSYNC_RSH="ssh ${SSH_OPTS}"
DRY_RUN_FLAG="--dry-run"
if [[ "$APPLY" -eq 1 ]]; then
DRY_RUN_FLAG=""
fi
DELETE_FLAG="--delete"
if [[ "$NO_DELETE" -eq 1 ]]; then
DELETE_FLAG=""
fi
echo "远端:${REMOTE}"
echo "模式:$([[ "$APPLY" -eq 1 ]] && echo APPLY || echo DRY-RUN)"
echo "目录同步:$([[ "$APPLY" -eq 1 ]] && [[ "$NO_DELETE" -eq 0 ]] && echo \"镜像(--delete)\" || echo \"不删除\")"
echo "ssh opts:${SSH_OPTS:-<none>}"
echo "rsync extra:${RSYNC_OPTS_EXTRA:-<none>}"
echo
remote_exec() {
# Intentionally allow word splitting in SSH_OPTS to pass multiple flags.
# shellcheck disable=SC2086
ssh ${SSH_OPTS} "$REMOTE" "$@"
}
if [[ "$APPLY" -eq 1 ]]; then
echo "[1/5] 确保远端目录存在:~/.claude/commands ~/.codex/prompts"
remote_exec "mkdir -p ~/.claude/commands ~/.codex/prompts ~/.claude ~/.codex"
if [[ "$CLEANUP_MISPLACED" -eq 1 ]]; then
echo "[2/5] 清理误同步到远端 ~ 根目录的位置(移动到备份目录)"
remote_exec "set -euo pipefail
TS=\$(date +%Y%m%d-%H%M%S)
BACKUP=\$HOME/.sync-cleanup-backup/\$TS
mkdir -p \"\$BACKUP\"
moved=0
for p in commands prompts CLAUDE.md AGENTS.md; do
src=\$HOME/\$p
if [ -e \"\$src\" ]; then
mv \"\$src\" \"\$BACKUP/\"
moved=1
fi
done
if [ \"\$moved\" -eq 1 ]; then
echo \"已移动误同步内容到:\$BACKUP\"
else
echo \"未发现远端 ~ 根目录的误同步内容(无需清理)。\"
fi"
fi
if [[ "$BACKUP_REMOTE" -eq 1 ]]; then
echo "[3/5] 备份远端目标到 ~/.sync-backup/<timestamp>/"
remote_exec "set -euo pipefail
TS=\$(date +%Y%m%d-%H%M%S)
BACKUP=\$HOME/.sync-backup/\$TS
mkdir -p \"\$BACKUP\"
if [ -e \"\$HOME/.claude/CLAUDE.md\" ]; then
mkdir -p \"\$BACKUP/.claude\"
cp -a \"\$HOME/.claude/CLAUDE.md\" \"\$BACKUP/.claude/CLAUDE.md\"
fi
if [ -d \"\$HOME/.claude/commands\" ]; then
mkdir -p \"\$BACKUP/.claude\"
cp -a \"\$HOME/.claude/commands\" \"\$BACKUP/.claude/commands\"
fi
if [ -e \"\$HOME/.codex/AGENTS.md\" ]; then
mkdir -p \"\$BACKUP/.codex\"
cp -a \"\$HOME/.codex/AGENTS.md\" \"\$BACKUP/.codex/AGENTS.md\"
fi
if [ -d \"\$HOME/.codex/prompts\" ]; then
mkdir -p \"\$BACKUP/.codex\"
cp -a \"\$HOME/.codex/prompts\" \"\$BACKUP/.codex/prompts\"
fi
echo \"备份完成:\$BACKUP\""
fi
else
echo "[提示] 当前为 dry-run,不会创建远端目录、不做备份/清理。"
echo "[提示] 若远端 ~/.claude/ 或 ~/.codex/ 不存在,dry-run 可能会失败;可先加 --apply(或手动 mkdir)。"
fi
echo
echo "[4/5] 执行同步($([[ "$APPLY" -eq 1 ]] && echo apply || echo dry-run))"
echo
# Directory sync: optionally include --delete when applying.
# Intentionally expand RSYNC_OPTS_EXTRA as words.
# shellcheck disable=SC2086
rsync $RSYNC_COMMON -e "$RSYNC_RSH" $DRY_RUN_FLAG $DELETE_FLAG $RSYNC_OPTS_EXTRA \
"${LOCAL_CLAUDE_DIR}/commands/" "${REMOTE}:~/.claude/commands/"
# Single files: no --delete needed.
# shellcheck disable=SC2086
rsync $RSYNC_COMMON -e "$RSYNC_RSH" $DRY_RUN_FLAG $RSYNC_OPTS_EXTRA \
"${LOCAL_CLAUDE_DIR}/CLAUDE.md" "${REMOTE}:~/.claude/CLAUDE.md"
# shellcheck disable=SC2086
rsync $RSYNC_COMMON -e "$RSYNC_RSH" $DRY_RUN_FLAG $RSYNC_OPTS_EXTRA \
"${LOCAL_CODEX_DIR}/AGENTS.md" "${REMOTE}:~/.codex/AGENTS.md"
# shellcheck disable=SC2086
rsync $RSYNC_COMMON -e "$RSYNC_RSH" $DRY_RUN_FLAG $DELETE_FLAG $RSYNC_OPTS_EXTRA \
"${LOCAL_CODEX_DIR}/prompts/" "${REMOTE}:~/.codex/prompts/"
echo
if [[ "$APPLY" -eq 1 ]]; then
echo "[5/5] 远端校验(ls)"
remote_exec "ls -la ~/.claude/CLAUDE.md ~/.codex/AGENTS.md && ls -la ~/.claude/commands && ls -la ~/.codex/prompts"
else
echo "[完成] dry-run 结束(未对远端做任何写入)。"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment