mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-02 04:37:49 +02:00
Let users add trusted plugin source repos and add/update/remove plugins from them, alongside the existing in-shell plugin commands: omarchy plugin source <add|list|remove|refresh> omarchy plugin available omarchy plugin add | update | remove | validate Each command is interactive (gum/fzf pickers, confirmation, an update diff) in a TTY and fully flag-driven with --yes for scripts and agents. Sources live in ~/.config/omarchy/plugins/sources.json and clone into ~/.cache/omarchy/plugin-sources/. The installer only copies files, validates manifests against the shell's schema, and toggles enabled state over IPC -- it never runs plugin code, hooks, or sudo. Also guard 'plugin bar add' so only known bar-widget ids enter the layout (rejecting typos/non-widgets like a stray '--help').
216 lines
7.4 KiB
Bash
Executable File
216 lines
7.4 KiB
Bash
Executable File
#!/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 <<USAGE
|
|
Usage: omarchy plugin update [plugin-id] [--all] [--force] [--review] [--no-refresh] [--yes]
|
|
|
|
Updates installed plugins when their source has a newer version.
|
|
(no args) pick from the plugins that have an update available
|
|
<plugin-id> 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 "<status>\t<source-id>\t<installed-version>\t<upstream-version>\t<cache-path>"
|
|
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
|