diff --git a/README.md b/README.md
index f6d3d4d..27cb06b 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ It is built for local agent-heavy development setups with tools such as Codex, C
- Groups known MCP/helper processes by owner and type, with per-process debug output behind `AGENTWATCH_SHOW_HELPERS=1`.
- Shows installed coding CLIs and versions when they are discoverable on `PATH`.
- Supports cached update checks for npm-distributed CLIs; checks are spaced by `AGENTWATCH_UPDATE_TTL_SECONDS`.
-- Shows the Agent Watch plugin version and, when installed from a git checkout, an update action.
+- Shows the Agent Watch plugin version and whether release installs are current or have a newer release available.
- Hides process command lines by default. Optional redacted command output can be enabled with `AGENTWATCH_SHOW_COMMANDS=1`.
- Uses only local process inspection and local HTTP endpoints by default.
@@ -54,7 +54,7 @@ cd agent-watch
./scripts/install-dev-swiftbar.sh "$HOME/SwiftBarPlugins"
```
-Release installs show `Update to latest release`, so normal users do not need git. Source checkout installs show the current branch and commit for diagnostics, but hide the menu updater to avoid overwriting checkout-managed files. Development updates should use normal git commands in the checkout.
+Release installs show whether the installed plugin is current or whether a newer release is available. `Update to latest release` runs in the background, writes to `AGENTWATCH_UPDATE_LOG`, and replaces the plugin with the latest release asset. Source checkout installs show the current branch and commit for diagnostics, but hide the menu updater to avoid overwriting checkout-managed files. Development updates should use normal git commands in the checkout.
### Linux
@@ -104,6 +104,9 @@ The plugin also supports a local config file at `~/.config/agent-watch/config.en
| `AGENTWATCH_REPO_URL` | `https://github.com/flamerged/agent-watch` | Project page opened from the menu |
| `AGENTWATCH_RELEASE_ASSET_URL` | `https://github.com/flamerged/agent-watch/releases/latest/download/agent-watch.30s.sh` | HTTPS latest release asset URL used by copied-plugin updates |
| `AGENTWATCH_UPDATE_LOG` | `$HOME/.cache/agent-watch/update.log` | Update log path |
+| `AGENTWATCH_CHECK_RELEASE_UPDATES` | `1` | Set to `0` to disable cached plugin release checks |
+| `AGENTWATCH_RELEASE_CHECK_TTL_SECONDS` | `86400` | Minimum seconds between automatic latest-release checks |
+| `AGENTWATCH_RELEASE_CHECK_CACHE` | `$HOME/.cache/agent-watch/release-check.tsv` | Latest-release check cache path |
| `AGENTWATCH_INTERESTING_PORTS` | `8000,11434,3000,4000,5000` | Comma-separated TCP listening ports to show |
## Privacy And Security
@@ -114,9 +117,9 @@ By default it inspects the local process table with `ps`, working directories an
Process command lines are hidden by default because they can contain sensitive arguments. If `AGENTWATCH_SHOW_COMMANDS=1` is enabled, commands are still passed through a redactor for common API keys, bearer tokens, GitHub tokens, Anthropic/OpenAI-style keys, Gemini keys, Slack tokens, and Context7 tokens.
-Update checks are enabled by default. Agent Watch uses package registry lookups only to refresh a local cache, not on every menu refresh.
+Update checks are enabled by default. Agent Watch uses package registry and GitHub release lookups only to refresh local caches, not on every menu refresh.
-The default update-check TTL is one day. Set `AGENTWATCH_UPDATE_TTL_SECONDS` to adjust that interval, or use the menu action to refresh the cache manually.
+The default update-check TTLs are one day. Set `AGENTWATCH_UPDATE_TTL_SECONDS` for CLI package checks and `AGENTWATCH_RELEASE_CHECK_TTL_SECONDS` for plugin release checks, or use the menu actions to refresh the caches manually.
The "Watched Local Ports" section is controlled by `AGENTWATCH_INTERESTING_PORTS`. By default it watches the configured oMLX port, the configured Ollama port, and common local development ports `3000`, `4000`, and `5000`.
diff --git a/bin/agent-watch.30s.sh b/bin/agent-watch.30s.sh
index a6c966d..7a60372 100755
--- a/bin/agent-watch.30s.sh
+++ b/bin/agent-watch.30s.sh
@@ -19,6 +19,9 @@
# string(AGENTWATCH_REPO_URL="https://github.com/flamerged/agent-watch"): Agent Watch repository URL
# string(AGENTWATCH_RELEASE_ASSET_URL="https://github.com/flamerged/agent-watch/releases/latest/download/agent-watch.30s.sh"): Latest release asset URL for copied-plugin updates
# string(AGENTWATCH_UPDATE_LOG="~/.cache/agent-watch/update.log"): Update log file path
+# boolean(AGENTWATCH_CHECK_RELEASE_UPDATES=true): Check latest Agent Watch release in the background
+# string(AGENTWATCH_RELEASE_CHECK_TTL_SECONDS="86400"): Seconds between latest-release checks when enabled
+# string(AGENTWATCH_RELEASE_CHECK_CACHE="~/.cache/agent-watch/release-check.tsv"): Latest-release check cache path
# string(AGENTWATCH_INTERESTING_PORTS="8000,11434,3000,4000,5000"): TCP ports to show
# Agent Watch
# v0.3.0 # x-release-please-version
@@ -78,6 +81,8 @@ write_default_config() {
builtin print -r -- "# CLI update checks are cached and spaced by this TTL."
builtin print -r -- "AGENTWATCH_CHECK_UPDATES=true"
builtin print -r -- "AGENTWATCH_UPDATE_TTL_SECONDS=86400"
+ builtin print -r -- "AGENTWATCH_CHECK_RELEASE_UPDATES=true"
+ builtin print -r -- "AGENTWATCH_RELEASE_CHECK_TTL_SECONDS=86400"
builtin print -r -- ""
builtin print -r -- "# Uncomment these when you want extra debug or local actions."
builtin print -r -- "# AGENTWATCH_SHOW_HELPERS=true"
@@ -165,6 +170,10 @@ AGENTWATCH_REPO_URL="${AGENTWATCH_REPO_URL:-https://github.com/flamerged/agent-w
RELEASE_ASSET_URL="${AGENTWATCH_RELEASE_ASSET_URL:-https://github.com/flamerged/agent-watch/releases/latest/download/agent-watch.30s.sh}"
UPDATE_LOG="${AGENTWATCH_UPDATE_LOG:-$HOME/.cache/agent-watch/update.log}"
UPDATE_LOG="${UPDATE_LOG/#\~/$HOME}"
+CHECK_RELEASE_UPDATES="${AGENTWATCH_CHECK_RELEASE_UPDATES:-1}"
+RELEASE_CHECK_TTL_SECONDS="${AGENTWATCH_RELEASE_CHECK_TTL_SECONDS:-86400}"
+RELEASE_CHECK_CACHE="${AGENTWATCH_RELEASE_CHECK_CACHE:-$HOME/.cache/agent-watch/release-check.tsv}"
+RELEASE_CHECK_CACHE="${RELEASE_CHECK_CACHE/#\~/$HOME}"
AGENTMEMORY_LOG="${AGENTWATCH_AGENTMEMORY_LOG:-$HOME/local-agentmemory/logs/agentmemory.log}"
OMLX_URL="$(strip_slash "${AGENTWATCH_OMLX_URL:-http://127.0.0.1:8000}")"
OLLAMA_URL="$(strip_slash "${AGENTWATCH_OLLAMA_URL:-http://127.0.0.1:11434}")"
@@ -248,6 +257,153 @@ plugin_version_label() {
emit "v${PLUGIN_VERSION}"
}
+file_mtime() {
+ local file="$1"
+ [[ -f "$file" ]] || { emit ""; return; }
+ if stat -f %m "$file" >/dev/null 2>&1; then
+ stat -f %m "$file"
+ elif stat -c %Y "$file" >/dev/null 2>&1; then
+ stat -c %Y "$file"
+ fi
+}
+
+release_tag_norm() {
+ emit "${1%%-*}" | "$SED" 's/^v//'
+}
+
+latest_release_tag_from_asset_url() {
+ local tag
+ tag="$(emit "$RELEASE_ASSET_URL" | "$SED" -n 's#.*releases/download/\([^/]*\)/.*#\1#p' | head -1)"
+ [[ -n "$tag" && "$tag" != "latest" ]] || return 1
+ emit "$tag"
+}
+
+github_repo_slug() {
+ local slug
+ [[ "$AGENTWATCH_REPO_URL" == https://github.com/* ]] || return 1
+ slug="${AGENTWATCH_REPO_URL#https://github.com/}"
+ slug="${slug%%\?*}"
+ slug="${slug%%#*}"
+ slug="${slug%/}"
+ slug="${slug%.git}"
+ [[ "$slug" == */* && "$slug" != */*/* ]] || return 1
+ emit "$slug"
+}
+
+latest_release_tag() {
+ local repo tag
+ have "$CURL" || return 1
+ if repo="$(github_repo_slug)" && [[ -n "$repo" ]]; then
+ tag="$("$CURL" -fsSL \
+ --connect-timeout 5 \
+ --max-time 15 \
+ --retry 1 \
+ -H 'Accept: application/vnd.github+json' \
+ "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null \
+ | "$JQ" -r '.tag_name // empty' 2>/dev/null)"
+ if [[ -n "$tag" && "$tag" != "null" ]]; then
+ emit "$tag"
+ return
+ fi
+ fi
+ latest_release_tag_from_asset_url
+}
+
+write_release_check_cache() {
+ local now tmp latest check_status rc
+ now="$(date +%s 2>/dev/null || emit "0")"
+ mkdir -p "${RELEASE_CHECK_CACHE:h}" 2>/dev/null || {
+ rm -f "${RELEASE_CHECK_CACHE}.lock" 2>/dev/null || true
+ return 1
+ }
+ tmp="${RELEASE_CHECK_CACHE}.$$"
+ if latest="$(latest_release_tag)"; then
+ check_status="ok"
+ else
+ check_status="error"
+ latest=""
+ fi
+ if builtin printf '%s\t%s\t%s\n' "$now" "$check_status" "$latest" > "$tmp" \
+ && mv "$tmp" "$RELEASE_CHECK_CACHE"; then
+ [[ "$check_status" == "ok" ]]
+ rc=$?
+ else
+ rm -f "$tmp" 2>/dev/null || true
+ rc=1
+ fi
+ rm -f "${RELEASE_CHECK_CACHE}.lock" 2>/dev/null || true
+ return "$rc"
+}
+
+release_check_cache_fields() {
+ [[ -f "$RELEASE_CHECK_CACHE" ]] || return 1
+ local ts check_status latest rest
+ IFS=$'\t' read -r ts check_status latest rest < "$RELEASE_CHECK_CACHE" || return 1
+ builtin printf '%s\t%s\t%s\n' "$ts" "$check_status" "$latest"
+}
+
+release_check_cache_age() {
+ local mtime now
+ mtime="$(file_mtime "$RELEASE_CHECK_CACHE")"
+ [[ -n "$mtime" ]] || { emit ""; return; }
+ now="$(date +%s 2>/dev/null || emit "0")"
+ emit $(( now - mtime ))
+}
+
+maybe_refresh_release_check() {
+ truthy "$CHECK_RELEASE_UPDATES" || return
+ [[ "$RELEASE_CHECK_TTL_SECONDS" == <-> ]] || RELEASE_CHECK_TTL_SECONDS=86400
+ local age lock_file lock_mtime current_mtime now lock_age
+ age="$(release_check_cache_age)"
+ if [[ -n "$age" && "$age" -le "$RELEASE_CHECK_TTL_SECONDS" ]]; then
+ return
+ fi
+ mkdir -p "${RELEASE_CHECK_CACHE:h}" 2>/dev/null || return
+ lock_file="${RELEASE_CHECK_CACHE}.lock"
+ lock_mtime="$(file_mtime "$lock_file")"
+ if [[ -n "$lock_mtime" ]]; then
+ now="$(date +%s 2>/dev/null || emit "0")"
+ lock_age=$(( now - lock_mtime ))
+ if [[ "$lock_age" -gt 300 ]]; then
+ current_mtime="$(file_mtime "$lock_file")"
+ [[ "$current_mtime" == "$lock_mtime" ]] && rm -f "$lock_file" 2>/dev/null || true
+ fi
+ fi
+ ( set -C; : > "$lock_file" ) 2>/dev/null || return
+ "$PLUGIN_PATH" check-release >/dev/null 2>&1 &
+}
+
+release_status_label() {
+ local current="$1" fields ts check_status latest
+ truthy "$CHECK_RELEASE_UPDATES" || { emit "update checks disabled"; return; }
+ fields="$(release_check_cache_fields)" || { emit "checking latest release"; return; }
+ IFS=$'\t' read -r ts check_status latest <<< "$fields"
+ if [[ "$check_status" != "ok" || -z "$latest" ]]; then
+ emit "latest check unavailable"
+ elif [[ "$(release_tag_norm "$latest")" == "$(release_tag_norm "$current")" ]]; then
+ emit "latest"
+ else
+ emit "update $latest available"
+ fi
+}
+
+release_status_color() {
+ local label="$1"
+ case "$label" in
+ update\ *\ available) emit "#cc6633" ;;
+ latest) emit "#2f8f46" ;;
+ *) emit "#888888" ;;
+ esac
+}
+
+cached_latest_release_tag() {
+ local fields ts check_status latest
+ fields="$(release_check_cache_fields)" || return 1
+ IFS=$'\t' read -r ts check_status latest <<< "$fields"
+ [[ "$check_status" == "ok" && -n "$latest" ]] || return 1
+ emit "$latest"
+}
+
log_update() {
mkdir -p "${UPDATE_LOG:h}" 2>/dev/null || return
builtin print -r -- "[$(date '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date)] ${1:-}" >> "$UPDATE_LOG" 2>/dev/null || true
@@ -388,6 +544,10 @@ case "$ACTION" in
write_update_cache
exit 0
;;
+ check-release)
+ write_release_check_cache
+ exit $?
+ ;;
configure)
write_default_config && open_text_target "$CONFIG_FILE"
exit $?
@@ -971,10 +1131,17 @@ open_ports_rows() {
}
print_plugin_rows() {
- local root git_summary version_label
+ local root git_summary version_label release_status release_color latest_release
root="$(plugin_repo_root)"
version_label="$(plugin_version_label "$root")"
- emit "--Version: ${version_label} | font=Menlo"
+ if [[ -z "$root" ]]; then
+ maybe_refresh_release_check
+ release_status="$(release_status_label "$version_label")"
+ release_color="$(release_status_color "$release_status")"
+ emit "--Version: ${version_label} (${release_status}) | font=Menlo color=$release_color"
+ else
+ emit "--Version: ${version_label} | font=Menlo"
+ fi
emit "--Config: $(shorten_path "$CONFIG_FILE") | font=Menlo"
emit "--Script: $(shorten_path "$PLUGIN_PATH") | font=Menlo"
if [[ -n "$root" ]]; then
@@ -983,7 +1150,13 @@ print_plugin_rows() {
emit "--Git: ${git_summary:-unknown} | font=Menlo"
emit "----Use git commands for development updates | color=gray"
else
- emit "--Update to latest release | bash=$PLUGIN_PATH param1=update-release terminal=false refresh=true"
+ latest_release="$(cached_latest_release_tag 2>/dev/null || true)"
+ if [[ -n "$latest_release" && "$(release_tag_norm "$latest_release")" != "$(release_tag_norm "$version_label")" ]]; then
+ emit "--Update to $latest_release | bash=$PLUGIN_PATH param1=update-release terminal=false refresh=true"
+ else
+ emit "--Update to latest release | bash=$PLUGIN_PATH param1=update-release terminal=false refresh=true"
+ fi
+ emit "--Check plugin update status now | bash=$PLUGIN_PATH param1=check-release terminal=false refresh=true"
fi
[[ -f "$UPDATE_LOG" ]] && emit "--Open update log | bash=$PLUGIN_PATH param1=open param2=update-log terminal=false"
emit "--Open config file | bash=$PLUGIN_PATH param1=open param2=agent-watch-config terminal=false refresh=true"
diff --git a/scripts/check.sh b/scripts/check.sh
index 32d8787..47687e0 100755
--- a/scripts/check.sh
+++ b/scripts/check.sh
@@ -35,6 +35,9 @@ for var in \
'AGENTWATCH_REPO_URL' \
'AGENTWATCH_RELEASE_ASSET_URL' \
'AGENTWATCH_UPDATE_LOG' \
+ 'AGENTWATCH_CHECK_RELEASE_UPDATES' \
+ 'AGENTWATCH_RELEASE_CHECK_TTL_SECONDS' \
+ 'AGENTWATCH_RELEASE_CHECK_CACHE' \
'AGENTWATCH_INTERESTING_PORTS'; do
grep -q ".*$var" "$PLUGIN"
done
@@ -43,6 +46,7 @@ output="$(
AGENTWATCH_SHOW_COMMANDS=0 \
AGENTWATCH_CONFIG_FILE="$(mktemp)" \
AGENTWATCH_CHECK_UPDATES=0 \
+ AGENTWATCH_CHECK_RELEASE_UPDATES=0 \
AGENTWATCH_OMLX_URL=http://127.0.0.1:1 \
AGENTWATCH_OLLAMA_URL=http://127.0.0.1:1 \
AGENTWATCH_AGENTMEMORY_URL=http://127.0.0.1:1 \
@@ -63,6 +67,7 @@ print -r -- 'AGENTWATCH_INTERESTING_PORTS=54321' > "$config_file"
config_output="$(
AGENTWATCH_CONFIG_FILE="$config_file" \
AGENTWATCH_CHECK_UPDATES=0 \
+ AGENTWATCH_CHECK_RELEASE_UPDATES=0 \
AGENTWATCH_OMLX_URL=http://127.0.0.1:1 \
AGENTWATCH_OLLAMA_URL=http://127.0.0.1:1 \
AGENTWATCH_AGENTMEMORY_URL=http://127.0.0.1:1 \