mirror of
https://github.com/chainbase-labs/Agentkey.git
synced 2026-09-20 14:20:23 +08:00
fix(security): notify-only update check + interactive upgrade flow (#21)
## Summary Hardens the AgentKey skill's security posture so ClawHub's `Suspicious` flags can be cleared, and rebuilds the update UX into a two-tier design modeled after [gstack](https://github.com/garrytan/gstack): - **Tier 1 (`check-update.sh`):** scanner-safe, notify-only detection. Single `GET https://api.github.com/...`, prints `UP_TO_DATE` / `UPGRADE_AVAILABLE <old> <new>` / silent. No `git`, no `execve`, no in-place writes outside `${TMPDIR}` cache. - **Tier 2 (`SKILL.md` Step 0):** interactive upgrade flow. Agent surfaces `AskUserQuestion` with **Yes / Always / Not now / Never** and runs the actual update via `npx skills update` only after explicit consent (or a previously persisted opt-in). ## What's in the PR ### 1. `check-update.sh` — notify-only with snooze + disable Replaces the previous self-applying `git fetch` + `git checkout` with a pure detection script. New behavior: - Issues a single `GET` to `api.github.com`, validates the response with a regex, compares against local `version.txt`. - Two-tier cache TTL (60 min for `UP_TO_DATE`, 12 h for `UPGRADE_AVAILABLE`). - Honors `~/.config/agentkey/update-disabled` (silent if present). - Honors `~/.config/agentkey/update-snoozed` (escalating 24h / 48h / 7d backoff per `<version> <level> <epoch>` format; new remote version invalidates the snooze). - Cache invalidates automatically when local moves past cached `<old>` (handles the user-updated-manually case). ### 2. `SKILL.md` Step 0 — interactive upgrade flow When `check-update.sh` outputs `UPGRADE_AVAILABLE <old> <new>`, the agent now: 1. Checks `AGENTKEY_AUTO_UPGRADE=1` env var or `~/.config/agentkey/auto-upgrade` file. If either is set: announce "Auto-upgrading…" and run the upgrade silently. 2. Otherwise, surface `AskUserQuestion`: - **Yes, upgrade now** → run `npx skills update chainbase-labs/agentkey` - **Always keep me up to date** → touch `~/.config/agentkey/auto-upgrade`, then upgrade - **Not now** → write `~/.config/agentkey/update-snoozed` with escalating level - **Never ask again** → touch `~/.config/agentkey/update-disabled` ### 3. `check-mcp.sh` — shell-injection hardening Replace shell-interpolated `'$HOME/.claude.json'` inside the `python3 -c` block with `os.path.expanduser('~/.claude.json')`. Removes a latent injection vector when `$HOME` contains quote characters. Also tighten `except:` → `except Exception:`. ### 4. `SECURITY.md` — Security Posture + scanner notes - Documents what each script reads/writes, byte-precise - Lists the three new config files under `~/.config/agentkey/` and which side reads/writes each - Network egress (one GET to `api.github.com` from the script; `npx` to npm registry only via user-consented agent action) - Credential handling (key never leaves the machine except as Authorization header to AgentKey's own API) - Scanner false-positive notes explaining why `check-update.sh` (notify-only GitHub call) and `check-mcp.sh` (credential-pattern read for status check) may match heuristics, and why both patterns are intentional and bounded ## Threat-model framing Scanners scanning the repo source see: - `check-update.sh` — pure detection. One outbound GET. No `git`, no `execve`. No "remote-controlled binary update" pattern any more. - `check-mcp.sh` — local read of two known config paths to verify the API key is configured. Output is a status code; key value discarded. No exfiltration path. - `SKILL.md` — text instructions for the agent (not executable code) describing the interactive flow. The actual `npx skills update` invocation lives in the agent's runtime, gated by either explicit user consent (`AskUserQuestion`) or a previously persisted opt-in flag the user set themselves. This is the same threat model as gstack and any other Claude Code skill that performs interactive operations. ## What is NOT in this PR (and why) - **Pinning `@agentkey/mcp@^1` in manual-install JSON** — drafted and reverted. `^1` only blocks major-version supply-chain attacks; minor/patch attacks within `1.x` still go through, and the doc-snippet pin doesn't reach the `--auth-login` writer (lives in the server repo). Cost (manual installers must re-edit JSON for patches) outweighed the marginal protection. ## Test plan - [x] `bash -n` passes on both shell scripts - [x] `check-update.sh` end-to-end tested: - `UP_TO_DATE` when versions match - `UPGRADE_AVAILABLE 0.9.0 1.2.0` when local lags - Cache hit replays, then auto-invalidates when local moves past cached old - **Snoozed within window** → silent - **Snooze expired (>24h)** → re-emits - **Snoozed for old remote version** → re-emits (new version invalidates snooze) - **`update-disabled` present** → silent regardless - [x] Snooze escalation logic (level 1 → 2 → 3 → 3 capped) tested standalone - [x] Auto-upgrade detection (env var, config file, neither) tested standalone - [x] Python `os.path.expanduser` block runs cleanly against an existing `~/.claude.json` - [ ] After merge + release-please tag, run `clawhub skill publish` so the new SECURITY.md and scripts ship in the published bundle, then `clawhub skill rescan agentkey` - [ ] If ClawScan / VirusTotal still flag after rescan, file an appeal at [openclaw/clawhub](https://github.com/openclaw/clawhub) linking to SECURITY.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+52
@@ -25,3 +25,55 @@ Pre-1.0 releases are no longer maintained. Please upgrade to the latest 1.x rele
|
||||
## Disclosure
|
||||
|
||||
We follow coordinated disclosure. Once a fix is available, we publish a security advisory via GitHub Security Advisories and credit the reporter (with permission).
|
||||
|
||||
## Security Posture
|
||||
|
||||
### What this skill does on your machine
|
||||
|
||||
The skill ships two helper scripts that the agent invokes:
|
||||
|
||||
- **`skills/agentkey/scripts/check-update.sh`** — **notify-only**. At most every 60 minutes (12 hours once an upgrade is known), it calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against the local `version.txt`, and prints `UPGRADE_AVAILABLE <old> <new>` if they differ. The script also honors a snooze file (`~/.config/agentkey/update-snoozed`, escalating 24h/48h/7d backoff) and a disable file (`~/.config/agentkey/update-disabled`); both are read-only from this script's perspective. The script never runs `git`, never writes to anything except its TMPDIR cache, and never executes downloaded code.
|
||||
|
||||
When the agent sees `UPGRADE_AVAILABLE` it surfaces an `AskUserQuestion` prompt (Yes / Always / Not now / Never). The actual update — `npx skills update chainbase-labs/agentkey` — runs only after the user picks "Yes" or "Always", or if the user has previously opted into auto-upgrade via `AGENTKEY_AUTO_UPGRADE=1` or `~/.config/agentkey/auto-upgrade`. The agent invokes that command via its own Bash tool, not via this script.
|
||||
- **`skills/agentkey/scripts/check-mcp.sh`** — reads `~/.claude.json` and `~/.env.local` to verify the AgentKey MCP server is registered and the API key is present. **Read-only**; no network egress; output is a single status code.
|
||||
|
||||
### Files the skill reads or writes
|
||||
|
||||
| Path | Mode | Purpose |
|
||||
|---|---|---|
|
||||
| `~/.claude.json` | read | Detect MCP registration; read `AGENTKEY_API_KEY` env value |
|
||||
| `~/.env.local` | read | Fallback location for `AGENTKEY_API_KEY` |
|
||||
| `${TMPDIR}/agentkey-update-check` | read/write | Cache for the update check |
|
||||
| `~/.config/agentkey/auto-upgrade` | written by the agent on user's "Always keep me up to date" choice; read by Step 0 to skip the prompt | Persistent auto-upgrade opt-in |
|
||||
| `~/.config/agentkey/update-snoozed` | written by the agent on user's "Not now" choice; read by `check-update.sh` to suppress reminders | Snooze state (`<version> <level> <epoch>`) |
|
||||
| `~/.config/agentkey/update-disabled` | written by the agent on user's "Never ask again" choice; read by `check-update.sh` to exit silently | Permanent disable for update checks |
|
||||
| `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `%APPDATA%/Claude/...` (Windows) | written by the separate `npx -y @agentkey/mcp --auth-login` command, **not** by the skill | MCP registration |
|
||||
| `~/.cursor/mcp.json` | written by `--auth-login`, **not** by the skill | MCP registration |
|
||||
|
||||
### Network egress from the skill
|
||||
|
||||
| Destination | When | Why |
|
||||
|---|---|---|
|
||||
| `api.github.com` | At most every 24 hours | Look up the latest release tag |
|
||||
| npm registry | When the user first runs `npx -y @agentkey/mcp` | Resolve and run the MCP server |
|
||||
|
||||
### Credential handling
|
||||
|
||||
- `AGENTKEY_API_KEY` is stored only in user-local config files (paths above).
|
||||
- The key leaves the user's machine only as the `Authorization` header to AgentKey's own API endpoints.
|
||||
- The skill collects no telemetry.
|
||||
|
||||
### Supply chain
|
||||
|
||||
- Releases are cut by [release-please](https://github.com/googleapis/release-please) from merged Conventional-Commit PRs on `main` — no manual artifact uploads, no manual tag pushes.
|
||||
- The companion `@agentkey/mcp` npm package is published from the same organization. Users invoke it via `npx -y @agentkey/mcp`, which resolves to the latest published version at runtime — this is the same threat model as any other `npx`-launched CLI.
|
||||
- Future work: SLSA provenance attestation via GitHub OIDC + sigstore; signed npm provenance.
|
||||
|
||||
## Scanner false-positive notes
|
||||
|
||||
Automated scanners (VirusTotal, ClawScan) may flag this skill as `Suspicious` due to two intentional patterns. We document them here so reviewers can verify intent:
|
||||
|
||||
1. **`check-update.sh` contacts GitHub.** Pattern may match "remote-controlled binary update" heuristics. **Why this is intentional:** the script is notify-only — it issues a single `GET https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against `version.txt`, prints a one-line status, and exits. It never writes anywhere except the cache file at `${TMPDIR}/agentkey-update-check`, never invokes `git`, and never executes downloaded code. Update execution lives entirely in the agent's interactive layer (`AskUserQuestion` → `npx skills update`), gated by explicit user consent or a previously persisted opt-in flag.
|
||||
2. **`check-mcp.sh` reads `*API_KEY*` env values.** Pattern matches "credential harvesting" heuristics. **Why this is intentional:** the read is local-only, never transmitted, and exists purely to confirm `AGENTKEY_API_KEY` is configured before the agent attempts an MCP call. The script's only output is a one-word status code (`MCP_OK` / `MCP_NO_KEY` / `MCP_NOT_CONFIGURED`); the key value itself is discarded.
|
||||
|
||||
If you operate a scanner and need additional context to triage, please email `support@chainbase.com`.
|
||||
|
||||
@@ -10,16 +10,62 @@ version: 1.0.0
|
||||
|
||||
**Step 0 (always run first):**
|
||||
|
||||
1. Run the auto-update check silently (cached 24h — repeat calls are <10ms):
|
||||
1. Run the version check silently (cached — repeat calls are <10ms):
|
||||
```bash
|
||||
bash "${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}/skills/agentkey/scripts/check-update.sh" 2>/dev/null
|
||||
```
|
||||
- `UPDATED: vX.Y.Z` → Tell the user once: "✓ AgentKey Skill updated to vX.Y.Z."
|
||||
- `UPDATE_FAILED: ...` → Show the message verbatim to the user.
|
||||
- `UP_TO_DATE` or empty → continue silently.
|
||||
- `UP_TO_DATE` or empty → continue silently to step 2.
|
||||
- `UPGRADE_AVAILABLE <old> <new>` → run the **Upgrade flow** below, then continue to step 2.
|
||||
|
||||
2. Confirm the 4 MCP tools — `list_tools`, `find_tools`, `describe_tool`, `execute_tool` — are visible in the current toolset. If **any** are missing → **Setup** (regardless of what the user asked). Do not attempt Query without all 4.
|
||||
|
||||
### Upgrade flow
|
||||
|
||||
Triggered when `check-update.sh` outputs `UPGRADE_AVAILABLE <old> <new>`. Substitute `<old>` and `<new>` with the actual versions parsed from that line.
|
||||
|
||||
**Step A — Check for auto-upgrade opt-in.** Run:
|
||||
```bash
|
||||
if [ "${AGENTKEY_AUTO_UPGRADE:-0}" = "1" ] || [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/auto-upgrade" ]; then echo AUTO=1; fi
|
||||
```
|
||||
If the output is `AUTO=1`: tell the user once "Auto-upgrading AgentKey v\<old\> → v\<new\>…", run **Step C**, then continue to step 2. **Do not** show the AskUserQuestion prompt.
|
||||
|
||||
**Step B — Otherwise, prompt the user with AskUserQuestion:**
|
||||
- Question: `AgentKey v<new> is available (currently on v<old>). Upgrade now?`
|
||||
- Options:
|
||||
- **`Yes, upgrade now`** → run **Step C**.
|
||||
- **`Always keep me up to date`** → run:
|
||||
```bash
|
||||
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" && touch "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/auto-upgrade"
|
||||
```
|
||||
Tell the user "Auto-upgrade enabled — future AgentKey updates install automatically. Remove `~/.config/agentkey/auto-upgrade` to undo." Then run **Step C**.
|
||||
- **`Not now`** → run:
|
||||
```bash
|
||||
_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/agentkey"
|
||||
_SNOOZE="$_CFG/update-snoozed"
|
||||
_NEW="<new>"
|
||||
_LEVEL=0
|
||||
if [ -f "$_SNOOZE" ]; then
|
||||
_SVER=$(awk '{print $1}' "$_SNOOZE" 2>/dev/null)
|
||||
[ "$_SVER" = "$_NEW" ] && _LEVEL=$(awk '{print $2}' "$_SNOOZE" 2>/dev/null)
|
||||
case "$_LEVEL" in *[!0-9]*) _LEVEL=0 ;; esac
|
||||
fi
|
||||
_LEVEL=$((_LEVEL + 1)); [ "$_LEVEL" -gt 3 ] && _LEVEL=3
|
||||
mkdir -p "$_CFG" && echo "$_NEW $_LEVEL $(date +%s)" > "$_SNOOZE"
|
||||
echo "SNOOZED_LEVEL=$_LEVEL"
|
||||
```
|
||||
Translate the level into a duration for the user — `SNOOZED_LEVEL=1` → "Next reminder in 24h", `2` → "in 48h", `3` → "in 1 week". Continue to step 2 — **do not** upgrade.
|
||||
- **`Never ask again`** → run:
|
||||
```bash
|
||||
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" && touch "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/update-disabled"
|
||||
```
|
||||
Tell the user "Update checks disabled. Remove `~/.config/agentkey/update-disabled` to re-enable." Continue to step 2 — **do not** upgrade.
|
||||
|
||||
**Step C — Run the upgrade.** Invoke:
|
||||
```bash
|
||||
npx skills update chainbase-labs/agentkey
|
||||
```
|
||||
On success: tell the user "✓ AgentKey updated to v\<new\>." On failure: show the failure verbatim and tell the user "Run `npx skills update chainbase-labs/agentkey` manually to retry." Either way, continue to step 2.
|
||||
|
||||
Then route by intent:
|
||||
- "setup"/"install"/"api key"/"reinstall" → **Setup**
|
||||
- "status"/"diagnose" → **Status**
|
||||
|
||||
@@ -14,11 +14,12 @@ check_key_exists() {
|
||||
if [ -f "$HOME/.claude.json" ]; then
|
||||
local key_val
|
||||
key_val=$(python3 -c "
|
||||
import json, sys
|
||||
import json, os
|
||||
try:
|
||||
d = json.load(open('$HOME/.claude.json'))
|
||||
with open(os.path.expanduser('~/.claude.json')) as f:
|
||||
d = json.load(f)
|
||||
print(d.get('mcpServers', {}).get('agentkey', {}).get('env', {}).get('AGENTKEY_API_KEY', ''))
|
||||
except: pass
|
||||
except Exception: pass
|
||||
" 2>/dev/null | tr -d '[:space:]')
|
||||
[ -n "$key_val" ] && return 0
|
||||
fi
|
||||
|
||||
@@ -1,70 +1,121 @@
|
||||
#!/bin/bash
|
||||
# AgentKey — Auto-update to latest GitHub Release.
|
||||
# Result cached in TMPDIR to keep repeat skill invocations fast.
|
||||
# Outputs a single line: UP_TO_DATE | UPDATED: vX.Y.Z | UPDATE_FAILED: <reason>
|
||||
# AgentKey — Notify when a newer release is available on GitHub.
|
||||
# Notify-only: this script never modifies the install. It tells the agent
|
||||
# there's a new version; the agent surfaces a prompt and (with the user's
|
||||
# consent) invokes the upgrade.
|
||||
#
|
||||
# Result cached in TMPDIR for fast repeat invocations. Persistent state
|
||||
# (snooze, disable, auto-upgrade flag) lives under ~/.config/agentkey/.
|
||||
#
|
||||
# Outputs a single line, or nothing:
|
||||
# UP_TO_DATE — local matches latest release
|
||||
# UPGRADE_AVAILABLE <old> <new> — local differs from latest release
|
||||
# AND not currently snoozed/disabled
|
||||
# (empty / silent) — disabled, snoozed, no version file,
|
||||
# network down, or unexpected response
|
||||
|
||||
REPO="chainbase-labs/agentkey"
|
||||
CACHE_TTL_SUCCESS=86400 # 24h for UP_TO_DATE
|
||||
CACHE_TTL_FAILURE=3600 # 1h for UPDATE_FAILED (retry sooner)
|
||||
CACHE_TTL_UP_TO_DATE=3600 # 60 min — detect new releases quickly
|
||||
CACHE_TTL_UPGRADE=43200 # 12 h — keep nagging once an upgrade is known
|
||||
CURL_TIMEOUT=3
|
||||
|
||||
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." 2>/dev/null && pwd)}"
|
||||
VERSION_FILE="$PLUGIN_ROOT/version.txt"
|
||||
CACHE_FILE="${TMPDIR:-/tmp}/agentkey-update-check"
|
||||
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/agentkey"
|
||||
DISABLED_FILE="$CONFIG_DIR/update-disabled"
|
||||
SNOOZE_FILE="$CONFIG_DIR/update-snoozed"
|
||||
|
||||
# Disabled by user ("Never ask again") — exit silently.
|
||||
if [ -f "$DISABLED_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
LOCAL_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE" 2>/dev/null)
|
||||
if [ -z "$LOCAL_VERSION" ]; then
|
||||
echo "UP_TO_DATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# check_snooze <remote_version> → returns 0 (snoozed) or 1 (not snoozed).
|
||||
# Snooze file format: "<version> <level> <epoch>" where level 1=24h, 2=48h, 3+=7d.
|
||||
# A new remote version invalidates the snooze.
|
||||
check_snooze() {
|
||||
local remote_ver="$1"
|
||||
[ -f "$SNOOZE_FILE" ] || return 1
|
||||
local sver slevel sepoch
|
||||
sver=$(awk '{print $1}' "$SNOOZE_FILE" 2>/dev/null)
|
||||
slevel=$(awk '{print $2}' "$SNOOZE_FILE" 2>/dev/null)
|
||||
sepoch=$(awk '{print $3}' "$SNOOZE_FILE" 2>/dev/null)
|
||||
[ -n "$sver" ] && [ -n "$slevel" ] && [ -n "$sepoch" ] || return 1
|
||||
case "$slevel" in *[!0-9]*) return 1 ;; esac
|
||||
case "$sepoch" in *[!0-9]*) return 1 ;; esac
|
||||
[ "$sver" = "$remote_ver" ] || return 1
|
||||
local duration
|
||||
case "$slevel" in
|
||||
1) duration=86400 ;;
|
||||
2) duration=172800 ;;
|
||||
*) duration=604800 ;;
|
||||
esac
|
||||
local now
|
||||
now=$(date +%s)
|
||||
[ $((sepoch + duration)) -gt "$now" ]
|
||||
}
|
||||
|
||||
# Fast path: recent cache hit — avoids the GitHub API round-trip (~1.5s).
|
||||
if [ -f "$CACHE_FILE" ]; then
|
||||
MTIME=$(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0)
|
||||
AGE=$(( $(date +%s) - MTIME ))
|
||||
case "$(head -1 "$CACHE_FILE" 2>/dev/null)" in
|
||||
"UPDATE_FAILED:"*) TTL=$CACHE_TTL_FAILURE ;;
|
||||
*) TTL=$CACHE_TTL_SUCCESS ;;
|
||||
CACHED=$(head -1 "$CACHE_FILE" 2>/dev/null || true)
|
||||
case "$CACHED" in
|
||||
"UP_TO_DATE") TTL=$CACHE_TTL_UP_TO_DATE ;;
|
||||
"UPGRADE_AVAILABLE "*) TTL=$CACHE_TTL_UPGRADE ;;
|
||||
*) TTL=0 ;;
|
||||
esac
|
||||
if [ "$AGE" -ge 0 ] && [ "$AGE" -lt "$TTL" ]; then
|
||||
cat "$CACHE_FILE"
|
||||
exit 0
|
||||
case "$CACHED" in
|
||||
"UP_TO_DATE")
|
||||
echo "UP_TO_DATE"
|
||||
exit 0
|
||||
;;
|
||||
"UPGRADE_AVAILABLE "*)
|
||||
CACHED_OLD=$(echo "$CACHED" | awk '{print $2}')
|
||||
if [ "$CACHED_OLD" = "$LOCAL_VERSION" ]; then
|
||||
CACHED_NEW=$(echo "$CACHED" | awk '{print $3}')
|
||||
if check_snooze "$CACHED_NEW"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "$CACHED"
|
||||
exit 0
|
||||
fi
|
||||
# Local moved on — fall through to re-check.
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remote check — fetch latest release tag.
|
||||
# Slow path: fetch latest release tag from GitHub.
|
||||
LATEST_TAG=$(curl -sf --max-time "$CURL_TIMEOUT" \
|
||||
"https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \
|
||||
| grep -m1 '"tag_name"' \
|
||||
| sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||
LATEST_VERSION=${LATEST_TAG#[vV]}
|
||||
|
||||
# Network failure — stay silent; skip caching so we retry on the next call.
|
||||
if [ -z "$LATEST_VERSION" ]; then
|
||||
echo "UP_TO_DATE"
|
||||
# Validate response looks like a version number — rejects HTML error pages,
|
||||
# rate-limit JSON, and other surprises that slipped past curl -f.
|
||||
if ! echo "$LATEST_VERSION" | grep -qE '^[0-9]+\.[0-9.]+$'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Already current.
|
||||
if [ "$LOCAL_VERSION" = "$LATEST_VERSION" ]; then
|
||||
echo "UP_TO_DATE" > "$CACHE_FILE" 2>/dev/null
|
||||
echo "UP_TO_DATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Newer version available — attempt git auto-update.
|
||||
# Shallow-fetch only the target tag (not all tags) for speed.
|
||||
if [ -d "$PLUGIN_ROOT/.git" ]; then
|
||||
if git -C "$PLUGIN_ROOT" fetch --quiet --depth=1 origin \
|
||||
"+refs/tags/$LATEST_TAG:refs/tags/$LATEST_TAG" 2>/dev/null \
|
||||
&& git -C "$PLUGIN_ROOT" checkout --quiet "$LATEST_TAG" 2>/dev/null; then
|
||||
# After a successful checkout, subsequent checks are UP_TO_DATE.
|
||||
echo "UP_TO_DATE" > "$CACHE_FILE" 2>/dev/null
|
||||
echo "UPDATED: v$LATEST_VERSION"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
MSG="UPDATE_FAILED: Run \`/plugin update agentkey\` to update to v$LATEST_VERSION"
|
||||
# Newer version available — cache the result, then suppress output if snoozed.
|
||||
MSG="UPGRADE_AVAILABLE $LOCAL_VERSION $LATEST_VERSION"
|
||||
echo "$MSG" > "$CACHE_FILE" 2>/dev/null
|
||||
if check_snooze "$LATEST_VERSION"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "$MSG"
|
||||
|
||||
Reference in New Issue
Block a user