88 lines
2.6 KiB
Bash
88 lines
2.6 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
#
|
||
|
|
# dev-sync.sh — copy this working tree into the installed /watch plugin cache so
|
||
|
|
# local edits are picked up by Claude Code without publishing a release.
|
||
|
|
#
|
||
|
|
# The install path is resolved from ~/.claude/plugins/installed_plugins.json, so
|
||
|
|
# it follows version bumps automatically. Override it by passing a path as $1 or
|
||
|
|
# setting WATCH_INSTALL_PATH. Pass --dry-run to preview without writing.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# ./dev-sync.sh # sync into the resolved install path
|
||
|
|
# ./dev-sync.sh --dry-run # show what would change
|
||
|
|
# ./dev-sync.sh /some/other/dir # sync into an explicit path
|
||
|
|
#
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
PLUGIN_KEY="watch@claude-video"
|
||
|
|
INSTALLED_JSON="${HOME}/.claude/plugins/installed_plugins.json"
|
||
|
|
|
||
|
|
DRY_RUN=()
|
||
|
|
DEST=""
|
||
|
|
for arg in "$@"; do
|
||
|
|
case "$arg" in
|
||
|
|
--dry-run) DRY_RUN=(--dry-run --itemize-changes) ;;
|
||
|
|
-*) echo "unknown flag: $arg" >&2; exit 2 ;;
|
||
|
|
*) DEST="$arg" ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
# Resolve the destination install path if not given explicitly.
|
||
|
|
if [[ -z "$DEST" ]]; then
|
||
|
|
DEST="${WATCH_INSTALL_PATH:-}"
|
||
|
|
fi
|
||
|
|
if [[ -z "$DEST" ]]; then
|
||
|
|
if [[ ! -f "$INSTALLED_JSON" ]]; then
|
||
|
|
echo "error: $INSTALLED_JSON not found; pass an install path explicitly" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
DEST="$(PLUGIN_KEY="$PLUGIN_KEY" python3 - "$INSTALLED_JSON" <<'PY'
|
||
|
|
import json, os, sys
|
||
|
|
data = json.load(open(sys.argv[1]))
|
||
|
|
key = os.environ["PLUGIN_KEY"]
|
||
|
|
records = data.get("plugins", {}).get(key, [])
|
||
|
|
# Prefer a record whose installPath actually exists on disk.
|
||
|
|
paths = [r.get("installPath") for r in records if r.get("installPath")]
|
||
|
|
for p in paths:
|
||
|
|
if os.path.isdir(p):
|
||
|
|
print(p); break
|
||
|
|
else:
|
||
|
|
print(paths[0] if paths else "")
|
||
|
|
PY
|
||
|
|
)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -z "$DEST" ]]; then
|
||
|
|
echo "error: could not resolve an install path for '$PLUGIN_KEY'" >&2
|
||
|
|
echo " install the plugin first, or pass a path: scripts/dev-sync.sh /path/to/install" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
if [[ ! -d "$DEST" ]]; then
|
||
|
|
echo "error: install path does not exist: $DEST" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "source: $REPO_ROOT"
|
||
|
|
echo "dest: $DEST"
|
||
|
|
echo
|
||
|
|
|
||
|
|
# Mirror shipped files only. Dev-only artifacts and runtime state are excluded.
|
||
|
|
# No --delete: the cache holds runtime state (.in_use/) we must not touch.
|
||
|
|
rsync -a ${DRY_RUN[@]+"${DRY_RUN[@]}"} \
|
||
|
|
--exclude '.git/' \
|
||
|
|
--exclude '.venv/' \
|
||
|
|
--exclude '.pytest_cache/' \
|
||
|
|
--exclude '__pycache__/' \
|
||
|
|
--exclude '*.pyc' \
|
||
|
|
--exclude '.DS_Store' \
|
||
|
|
--exclude '.in_use/' \
|
||
|
|
--exclude 'tests/' \
|
||
|
|
--exclude 'docs/' \
|
||
|
|
--exclude 'dist/' \
|
||
|
|
--exclude 'V2_*.md' \
|
||
|
|
--exclude 'dev-sync.sh' \
|
||
|
|
"$REPO_ROOT/" "$DEST/"
|
||
|
|
|
||
|
|
echo "done."
|