git pull --rebase fails with 'cannot pull with rebase: You have unstaged changes' when the skills repo has local modifications. Fix: detect dirty index/worktree, stash before pull, restore after. Also gracefully handle pull failures (warn and continue with existing checkout) and stash pop conflicts.
203 lines
5.6 KiB
Bash
Executable File
203 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
|
SKILLS_REPO_URL="${MOSAIC_SKILLS_REPO_URL:-https://git.mosaicstack.dev/mosaic/agent-skills.git}"
|
|
SKILLS_REPO_DIR="${MOSAIC_SKILLS_REPO_DIR:-$MOSAIC_HOME/sources/agent-skills}"
|
|
MOSAIC_SKILLS_DIR="$MOSAIC_HOME/skills"
|
|
MOSAIC_LOCAL_SKILLS_DIR="$MOSAIC_HOME/skills-local"
|
|
|
|
fetch=1
|
|
link_only=0
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: $(basename "$0") [options]
|
|
|
|
Sync canonical skills into ~/.config/mosaic/skills and link all Mosaic skills into runtime skill directories.
|
|
|
|
Options:
|
|
--link-only Skip git clone/pull and only relink from ~/.config/mosaic/{skills,skills-local}
|
|
--no-link Sync canonical skills but do not update runtime links
|
|
-h, --help Show help
|
|
|
|
Env:
|
|
MOSAIC_HOME Default: ~/.config/mosaic
|
|
MOSAIC_SKILLS_REPO_URL Default: https://git.mosaicstack.dev/mosaic/agent-skills.git
|
|
MOSAIC_SKILLS_REPO_DIR Default: ~/.config/mosaic/sources/agent-skills
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--link-only)
|
|
fetch=0
|
|
shift
|
|
;;
|
|
--no-link)
|
|
link_only=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
mkdir -p "$MOSAIC_HOME" "$MOSAIC_SKILLS_DIR" "$MOSAIC_LOCAL_SKILLS_DIR"
|
|
|
|
if [[ $fetch -eq 1 ]]; then
|
|
if [[ -d "$SKILLS_REPO_DIR/.git" ]]; then
|
|
echo "[mosaic-skills] Updating skills source: $SKILLS_REPO_DIR"
|
|
|
|
# Stash any local changes (dirty index or worktree) before pulling
|
|
local_changes=0
|
|
if ! git -C "$SKILLS_REPO_DIR" diff --quiet 2>/dev/null || \
|
|
! git -C "$SKILLS_REPO_DIR" diff --cached --quiet 2>/dev/null; then
|
|
local_changes=1
|
|
echo "[mosaic-skills] Stashing local changes..."
|
|
git -C "$SKILLS_REPO_DIR" stash push -q -m "mosaic-sync-skills auto-stash"
|
|
fi
|
|
|
|
if ! git -C "$SKILLS_REPO_DIR" pull --rebase; then
|
|
echo "[mosaic-skills] WARN: pull failed — continuing with existing checkout" >&2
|
|
fi
|
|
|
|
# Restore stashed changes
|
|
if [[ $local_changes -eq 1 ]]; then
|
|
echo "[mosaic-skills] Restoring local changes..."
|
|
git -C "$SKILLS_REPO_DIR" stash pop -q 2>/dev/null || \
|
|
echo "[mosaic-skills] WARN: stash pop had conflicts — check $SKILLS_REPO_DIR" >&2
|
|
fi
|
|
else
|
|
echo "[mosaic-skills] Cloning skills source to: $SKILLS_REPO_DIR"
|
|
mkdir -p "$(dirname "$SKILLS_REPO_DIR")"
|
|
git clone "$SKILLS_REPO_URL" "$SKILLS_REPO_DIR"
|
|
fi
|
|
|
|
SOURCE_SKILLS_DIR="$SKILLS_REPO_DIR/skills"
|
|
if [[ ! -d "$SOURCE_SKILLS_DIR" ]]; then
|
|
echo "[mosaic-skills] Missing source skills dir: $SOURCE_SKILLS_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v rsync >/dev/null 2>&1; then
|
|
rsync -a --delete "$SOURCE_SKILLS_DIR/" "$MOSAIC_SKILLS_DIR/"
|
|
else
|
|
rm -rf "$MOSAIC_SKILLS_DIR"/*
|
|
cp -R "$SOURCE_SKILLS_DIR"/* "$MOSAIC_SKILLS_DIR"/
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -d "$MOSAIC_SKILLS_DIR" ]]; then
|
|
echo "[mosaic-skills] Canonical skills dir missing: $MOSAIC_SKILLS_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ $link_only -eq 1 ]]; then
|
|
echo "[mosaic-skills] Canonical sync completed (link update skipped)"
|
|
exit 0
|
|
fi
|
|
|
|
link_targets=(
|
|
"$HOME/.claude/skills"
|
|
"$HOME/.codex/skills"
|
|
"$HOME/.config/opencode/skills"
|
|
"$HOME/.pi/agent/skills"
|
|
)
|
|
|
|
canonical_real="$(readlink -f "$MOSAIC_SKILLS_DIR")"
|
|
|
|
link_skill_into_target() {
|
|
local skill_path="$1"
|
|
local target_dir="$2"
|
|
|
|
local name link_path
|
|
name="$(basename "$skill_path")"
|
|
|
|
# Do not distribute hidden/system skill directories globally.
|
|
if [[ "$name" == .* ]]; then
|
|
return
|
|
fi
|
|
|
|
link_path="$target_dir/$name"
|
|
|
|
if [[ -L "$link_path" ]]; then
|
|
ln -sfn "$skill_path" "$link_path"
|
|
return
|
|
fi
|
|
|
|
if [[ -e "$link_path" ]]; then
|
|
echo "[mosaic-skills] Preserve existing runtime-specific entry: $link_path"
|
|
return
|
|
fi
|
|
|
|
ln -s "$skill_path" "$link_path"
|
|
}
|
|
|
|
is_mosaic_skill_name() {
|
|
local name="$1"
|
|
# -d follows symlinks; -L catches broken symlinks that still indicate ownership
|
|
[[ -d "$MOSAIC_SKILLS_DIR/$name" || -L "$MOSAIC_SKILLS_DIR/$name" ]] && return 0
|
|
[[ -d "$MOSAIC_LOCAL_SKILLS_DIR/$name" || -L "$MOSAIC_LOCAL_SKILLS_DIR/$name" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
prune_stale_links_in_target() {
|
|
local target_dir="$1"
|
|
|
|
while IFS= read -r -d '' link_path; do
|
|
local name resolved
|
|
name="$(basename "$link_path")"
|
|
|
|
if is_mosaic_skill_name "$name"; then
|
|
continue
|
|
fi
|
|
|
|
resolved="$(readlink -f "$link_path" 2>/dev/null || true)"
|
|
if [[ -z "$resolved" ]]; then
|
|
rm -f "$link_path"
|
|
echo "[mosaic-skills] Removed stale broken skill link: $link_path"
|
|
continue
|
|
fi
|
|
|
|
if [[ "$resolved" == "$MOSAIC_HOME/"* ]]; then
|
|
rm -f "$link_path"
|
|
echo "[mosaic-skills] Removed stale retired skill link: $link_path"
|
|
fi
|
|
done < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type l -print0)
|
|
}
|
|
|
|
for target in "${link_targets[@]}"; do
|
|
mkdir -p "$target"
|
|
|
|
# If target already resolves to canonical dir, skip to avoid self-link recursion/corruption.
|
|
target_real="$(readlink -f "$target" 2>/dev/null || true)"
|
|
if [[ -n "$target_real" && "$target_real" == "$canonical_real" ]]; then
|
|
echo "[mosaic-skills] Skip target (already canonical): $target"
|
|
continue
|
|
fi
|
|
|
|
prune_stale_links_in_target "$target"
|
|
|
|
while IFS= read -r -d '' skill; do
|
|
link_skill_into_target "$skill" "$target"
|
|
done < <(find "$MOSAIC_SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d -print0)
|
|
|
|
if [[ -d "$MOSAIC_LOCAL_SKILLS_DIR" ]]; then
|
|
while IFS= read -r -d '' skill; do
|
|
link_skill_into_target "$skill" "$target"
|
|
done < <(find "$MOSAIC_LOCAL_SKILLS_DIR" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -print0)
|
|
fi
|
|
|
|
echo "[mosaic-skills] Linked skills into: $target"
|
|
done
|
|
|
|
echo "[mosaic-skills] Complete"
|