#!/bin/bash # omarchy:summary=Update installed Omarchy plugins from their sources # omarchy:group=plugin # omarchy:args=[plugin-id] [--all] [--force] [--review] [--no-refresh] [--yes] # omarchy:examples=omarchy plugin update | omarchy plugin update orbit --review | omarchy plugin update --all --yes # Re-pulls a plugin's folder from its source when the upstream manifest version # is newer. Updates are just re-installs, so the same "review the code" rule # applies — by default this prints a diff of exactly what changed before # touching anything. --yes skips the diff and confirmation for scripts/agents. set -o pipefail PLUGINS_DIR="$HOME/.config/omarchy/plugins" fail() { echo "omarchy-plugin-update: $*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required" } interactive() { [[ -t 0 && -t 1 ]] } PLUGIN_ID="" ALL=0 FORCE=0 DO_REVIEW=0 NO_REFRESH=0 ASSUME_YES=0 while (($# > 0)); do case "$1" in --all | -a) ALL=1; shift ;; --force | -f) FORCE=1; shift ;; --review) DO_REVIEW=1; shift ;; --no-refresh) NO_REFRESH=1; shift ;; --yes | -y) ASSUME_YES=1; shift ;; -h | --help) cat < update one plugin --all update every plugin with an available update --force re-pull even when versions match (or local looks newer) --review always show the file diff before updating (default in a TTY) --no-refresh use cached clones, skip the network refresh --yes skip the diff and confirmation USAGE exit 0 ;; -*) fail "unknown option: $1" ;; *) [[ -z $PLUGIN_ID ]] || fail "unexpected argument: $1" PLUGIN_ID="$1"; shift ;; esac done require_command jq ((ALL)) && [[ -n $PLUGIN_ID ]] && fail "pass either a plugin-id or --all, not both" confirm() { local prompt="$1" ((ASSUME_YES)) && return 0 if command -v gum >/dev/null 2>&1 && interactive; then gum confirm "$prompt"; return; fi if interactive; then local reply; read -r -p "$prompt [y/N] " reply; [[ $reply == [yY]* ]]; return; fi fail "refusing to update without confirmation; pass --yes" } if ((!NO_REFRESH)); then omarchy-plugin-source refresh >/dev/null 2>&1 || true fi CATALOG=$(omarchy-plugin-scan 2>/dev/null || echo "[]") # Map of currently enabled third-party plugins, so we can preserve state. declare -A enabled_map if shell_plugins=$(omarchy-shell shell listPlugins 2>/dev/null) && [[ -n $shell_plugins ]]; then while IFS=$'\t' read -r pid pen; do [[ -n $pid ]] && enabled_map["$pid"]="$pen" done < <(jq -r '.[] | select(.firstParty == false) | [.id, (.enabled|tostring)] | @tsv' <<<"$shell_plugins" 2>/dev/null) fi # Echoes "\t\t\t\t" plugin_status() { local id="$1" installed_version="$2" local up up=$(jq -c --arg id "$id" 'map(select(.manifest.id == $id)) | .[0] // empty' <<<"$CATALOG") if [[ -z $up ]]; then printf 'unmanaged\t\t%s\t\t\n' "$installed_version" return fi local upstream_version src_id cache_path status upstream_version=$(jq -r '.manifest.version // ""' <<<"$up") src_id=$(jq -r '.sourceId' <<<"$up") cache_path=$(jq -r '.path' <<<"$up") if [[ -z $installed_version || -z $upstream_version ]]; then status="unknown" elif [[ $installed_version == "$upstream_version" ]]; then status="up-to-date" elif [[ $(printf '%s\n%s\n' "$installed_version" "$upstream_version" | sort -V | tail -n1) == "$upstream_version" ]]; then status="update-available" else status="local-newer" fi printf '%s\t%s\t%s\t%s\t%s\n' "$status" "$src_id" "$installed_version" "$upstream_version" "$cache_path" } installed_version_of() { local id="$1" [[ -f "$PLUGINS_DIR/$id/manifest.json" ]] || return 1 jq -r '.version // ""' "$PLUGINS_DIR/$id/manifest.json" 2>/dev/null } # ---------------------------------------------------------------- pick targets [[ -d $PLUGINS_DIR ]] || fail "no plugins installed" declare -a targets=() if ((ALL)); then for d in "$PLUGINS_DIR"/*/; do [[ -d $d ]] || continue id=$(basename "$d"); [[ $id == .* ]] && continue iv=$(installed_version_of "$id") || continue IFS=$'\t' read -r status _ _ _ _ < <(plugin_status "$id" "$iv") [[ $status == "update-available" ]] && targets+=("$id") done ((${#targets[@]})) || { echo "No updates available."; exit 0; } elif [[ -n $PLUGIN_ID ]]; then installed_version_of "$PLUGIN_ID" >/dev/null || fail "plugin '$PLUGIN_ID' is not installed" targets=("$PLUGIN_ID") else interactive || fail "a plugin-id is required (or pass --all)" outdated="" for d in "$PLUGINS_DIR"/*/; do [[ -d $d ]] || continue id=$(basename "$d"); [[ $id == .* ]] && continue iv=$(installed_version_of "$id") || continue IFS=$'\t' read -r status _ instv upv _ < <(plugin_status "$id" "$iv") [[ $status == "update-available" ]] && outdated+="$id"$'\t'"$instv -> $upv"$'\n' done [[ -n $outdated ]] || { echo "No updates available."; exit 0; } rows=$(awk -F '\t' 'NF{printf "%-28s %s\n", $1, $2}' <<<"$outdated") if command -v gum >/dev/null 2>&1; then pick=$(gum choose --no-limit --header="Update which plugins? (space toggles, enter confirms)" <<<"$rows") || fail "cancelled" elif command -v fzf >/dev/null 2>&1; then pick=$(fzf --multi --prompt "Update which plugins (tab to mark) > " <<<"$rows") || fail "cancelled" else fail "a plugin-id is required (install gum or fzf for an interactive picker, or pass --all)" fi [[ -n $pick ]] || { echo "Nothing selected."; exit 0; } mapfile -t targets < <(awk '{print $1}' <<<"$pick") fi # ---------------------------------------------------------------- apply show_diff() { local installed="$1" cache="$2" echo "Changes for $3 ($4 -> $5):" if command -v delta >/dev/null 2>&1; then diff -ruN "$installed" "$cache" | delta --paging=never else diff -ruN "$installed" "$cache" fi echo } any_enabled=0 rc=0 for id in "${targets[@]}"; do iv=$(installed_version_of "$id") || { echo "Skipping $id: not installed." >&2; rc=1; continue; } IFS=$'\t' read -r status src_id instv upv cache_path < <(plugin_status "$id" "$iv") case "$status" in unmanaged) echo "Skipping $id: not found in any configured source." >&2; rc=1; continue ;; unknown) echo "Skipping $id: missing version metadata to compare." >&2; rc=1; continue ;; up-to-date) ((FORCE)) || { echo "Skipping $id: already up to date ($instv). Use --force to re-pull."; continue; } ;; local-newer) ((FORCE)) || { echo "Skipping $id: installed $instv is newer than upstream $upv. Use --force to overwrite." >&2; rc=1; continue; } ;; esac if ((!ASSUME_YES)); then if ((DO_REVIEW)) || interactive; then show_diff "$PLUGINS_DIR/$id" "$cache_path" "$id" "$instv" "$upv" fi confirm "Update $id ($instv -> $upv)?" || { echo "Skipped $id."; continue; } fi echo "Updating $id ..." install_args=("$id" --from "$src_id" --no-refresh --yes) case "${enabled_map[$id]:-}" in true) install_args+=(--enable); any_enabled=1 ;; false) install_args+=(--no-enable) ;; esac if ! omarchy-plugin-add "${install_args[@]}"; then echo "Failed to update $id" >&2 rc=1 fi done if ((any_enabled)); then echo echo "Updated plugins that were enabled may need a shell restart to fully reload:" echo " omarchy restart shell" fi exit $rc