Skip to content

Commit 8a0b53a

Browse files
authored
fix: version-check toast timing, remove terminal warn, add cache path constant (#79)
- Toast now fires after version fetch resolves + 2s delay, preventing slow network fetches from suspending into the user's first prompt - Remove console.warn from warnIfStale; toast is the sole notification channel - Remove system prompt injection of update notice (experimental.chat.system.transform) - Export PLUGIN_CACHE_PATH constant as single source of truth for cache path - warnIfStale accepts optional prefetchedLatest to avoid duplicate registry fetches - Single _latestVersionPromise shared across startup paths (one fetch per launch) - Add scripts/opencode-plugins-refresh helper for checking/clearing stale plugin caches - Add 12 new tests covering PLUGIN_CACHE_PATH and warnIfStale prefetch behavior
1 parent 6ae34b7 commit 8a0b53a

7 files changed

Lines changed: 494 additions & 48 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
## [0.6.1] — 2026-07-27 (patch)
8+
9+
- **Fixed: startup toast no longer suspends into the user's first prompt on slow networks.**
10+
The version-check toast previously ran `setTimeout(callback, 2000)` and then `await
11+
_versionCheckPromise` inside the callback, so a slow npm registry fetch could block the
12+
callback until after the user's first message was sent. The delay now runs *after* the
13+
promise resolves: `_versionCheckPromise.then(async (result) => { await sleep(2000); showToast() })`.
14+
The 2 s TUI-init pause is preserved; only the ordering changes.
15+
- **Removed: terminal `console.warn` for update notifications.** The `warnIfStale` function
16+
previously printed a multi-line warning to stderr on every startup when the plugin was
17+
outdated. This message is removed — the UI toast (introduced in 0.4.5) is the sole
18+
notification channel, avoiding duplicate noise in the terminal.
19+
- **New: `scripts/opencode-plugins-refresh`.** Helper script that compares cached `@latest` plugin
20+
versions against npm and optionally clears outdated caches so opencode re-fetches the latest on
21+
next launch. Supports `--check` (exit 1 if outdated, CI/cron-friendly) and `--force` (clear
22+
without prompting).
23+
- **`install.sh` now offers to install `opencode-plugins-refresh` to `~/.local/bin` (step 4).**
24+
- **`PLUGIN_CACHE_PATH` exported from `src/version-check.ts`.** Single source of truth for the
25+
opencode plugin cache path (cross-platform). Used by both the startup warning and the
26+
`cursor_update_plugin` tool to build the removal command / actually clear the cache — removes
27+
the duplication that could cause them to diverge.
28+
- **`warnIfStale` accepts an optional pre-fetched version string.** `warnIfStale(prefetchedLatest?)`
29+
now skips the registry call when the caller has already resolved it. Paired with a single
30+
`_latestVersionPromise` in the plugin that is shared by the console warning, the UI toast, and
31+
the system-prompt notice — so only one npm registry fetch happens per startup regardless of how
32+
many paths consume it.
33+
734
## [0.6.1] — 2026-07-24
835

936
- **Fixed: reasoning/thinking variants showed as meaningless numbered entries for

‎README.md‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ Drop `@latest` (`"@stablekernel/opencode-cursor"`) or pin a version
7171
The stale-version check is skipped when the `CI` or `NO_UPDATE_NOTIFIER`
7272
environment variable is set.
7373

74+
To keep the plugin up to date easily, install the `opencode-plugins-refresh` helper (offered by
75+
the one-line installer, or install manually — see [Keeping the plugin up to date](#keeping-the-plugin-up-to-date)).
76+
7477
The plugin injects the `provider` block automatically. If you need explicit control:
7578

7679
```json
@@ -85,6 +88,41 @@ The plugin injects the `provider` block automatically. If you need explicit cont
8588
}
8689
```
8790

91+
## Keeping the plugin up to date
92+
93+
opencode pins `@latest` plugins on first install and never auto-updates them. When the installed
94+
version falls behind the latest release, the plugin shows a warning once every 24 hours at startup.
95+
The warning tells you what to do:
96+
97+
- If `opencode-plugins-refresh` is on your `PATH`, the warning says `run: opencode-plugins-refresh`.
98+
- Otherwise it shows the raw `rm -rf` command and suggests re-running the installer to get the
99+
helper script.
100+
101+
### opencode-plugins-refresh
102+
103+
`opencode-plugins-refresh` is a shell script that checks all `@latest` plugin caches for updates
104+
by comparing pinned versions against npm, and optionally clears outdated caches so opencode
105+
re-fetches the latest on next launch.
106+
107+
```bash
108+
opencode-plugins-refresh # check for updates, prompt to clear cache
109+
opencode-plugins-refresh --check # check only, exit 1 if outdated
110+
opencode-plugins-refresh --force # clear all outdated caches without prompting
111+
```
112+
113+
The `--check` flag exits with code `1` when any cache is outdated, making it suitable for CI jobs
114+
or cron checks.
115+
116+
The one-line installer offers to install `opencode-plugins-refresh` to `~/.local/bin` (step 4).
117+
To install it manually at any time:
118+
119+
```bash
120+
curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh \
121+
-o ~/.local/bin/opencode-plugins-refresh && chmod +x ~/.local/bin/opencode-plugins-refresh
122+
```
123+
124+
Make sure `~/.local/bin` is on your `PATH`.
125+
88126
## Authenticate
89127

90128
```bash

‎install.sh‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,38 @@ else
394394
info "Set it with ${DIM}export CURSOR_API_KEY=\"key_...\"${RESET} or run ${DIM}opencode auth login${RESET} (choose \"Cursor\")."
395395
fi
396396

397+
# ---- 4. opencode-plugins-refresh ---------------------------------------------
398+
step "Installing opencode-plugins-refresh helper (optional)"
399+
LOCAL_BIN="$HOME/.local/bin"
400+
mkdir -p "$LOCAL_BIN"
401+
SCRIPT_URL="https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh"
402+
if have_tty; then
403+
printf 'Install opencode-plugins-refresh to %s? [y/N] ' "$LOCAL_BIN"
404+
read -r REPLY <"$TTY" || REPLY=""
405+
case "$REPLY" in
406+
[yY]*)
407+
if curl -fsSL "$SCRIPT_URL" -o "$LOCAL_BIN/opencode-plugins-refresh" 2>/dev/null; then
408+
chmod +x "$LOCAL_BIN/opencode-plugins-refresh"
409+
ok "Installed opencode-plugins-refresh → ${DIM}${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
410+
info "Make sure ${DIM}${LOCAL_BIN}${RESET} is on your PATH."
411+
else
412+
err "Failed to download opencode-plugins-refresh from ${SCRIPT_URL}"
413+
warn "You can install it manually later by re-running this installer."
414+
fi
415+
;;
416+
*) info "Skipped. Install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}" ;;
417+
esac
418+
else
419+
info "Non-interactive install — skipping opencode-plugins-refresh."
420+
info "To install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
421+
fi
422+
397423
# ---- done --------------------------------------------------------------------
398424
step "Done"
399425
ok "opencode-cursor is installed."
400426
info "Next:"
401427
info " 1. ${DIM}Ensure CURSOR_API_KEY is set, or run: opencode auth login${RESET}"
402428
info " 2. ${DIM}Restart opencode, then run: opencode models${RESET} (lists cursor/* models)"
429+
info " 3. ${DIM}Run: opencode-plugins-refresh --check${RESET} (check for plugin updates)"
403430
info ""
404431
info "Docs: ${DIM}${REPO_URL}#readme${RESET}"

‎scripts/opencode-plugins-refresh‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env bash
2+
# Check and/or refresh opencode's @latest plugin cache.
3+
# Compares each cached plugin version against npm and optionally clears
4+
# outdated entries so opencode re-fetches the latest on next launch.
5+
#
6+
# Usage:
7+
# opencode-plugins-refresh # check for updates, prompt to clear cache
8+
# opencode-plugins-refresh --check # check only, no changes
9+
# opencode-plugins-refresh --force # clear all @latest caches without prompting
10+
# opencode-plugins-refresh --package <name> # check/update one specific plugin
11+
# opencode-plugins-refresh --package <name> --check # check only for that plugin
12+
# opencode-plugins-refresh --package <name> --force # force-clear that plugin's cache
13+
14+
set -euo pipefail
15+
16+
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/opencode/packages"
17+
MODE=""
18+
PACKAGE=""
19+
20+
# Parse arguments
21+
while [[ $# -gt 0 ]]; do
22+
case "$1" in
23+
--check|--force)
24+
MODE="$1"
25+
shift
26+
;;
27+
--package)
28+
[[ $# -ge 2 ]] || { echo "Error: --package requires a package name argument."; exit 1; }
29+
PACKAGE="$2"
30+
shift 2
31+
;;
32+
--help|-h)
33+
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
34+
exit 0
35+
;;
36+
*)
37+
echo "Unknown argument: $1"
38+
echo "Usage: $(basename "$0") [--package <name>] [--check|--force]"
39+
exit 1
40+
;;
41+
esac
42+
done
43+
44+
if [[ ! -d "$CACHE_DIR" ]]; then
45+
echo "No opencode plugin cache found at $CACHE_DIR — nothing to do."
46+
exit 0
47+
fi
48+
49+
# Build the list of entries to check
50+
entries=()
51+
52+
if [[ -n "$PACKAGE" ]]; then
53+
# Build the cache path for the given package.
54+
# Most plugins: $CACHE_DIR/my-plugin@latest or $CACHE_DIR/@scope/name@latest
55+
# Some scoped plugins omit the @latest suffix: $CACHE_DIR/@scope/name
56+
pkg_cache_path="$CACHE_DIR/${PACKAGE}@latest"
57+
if [[ ! -d "$pkg_cache_path" ]]; then
58+
pkg_cache_path="$CACHE_DIR/${PACKAGE}"
59+
fi
60+
if [[ ! -d "$pkg_cache_path" ]]; then
61+
echo "Plugin '$PACKAGE' not found in cache — nothing to do."
62+
exit 0
63+
fi
64+
entries=("$pkg_cache_path")
65+
else
66+
# Collect all @latest cache dirs (top-level and scoped).
67+
# Also include scoped packages that lack the @latest suffix (e.g. @stablekernel/opencode-cursor).
68+
while IFS= read -r line; do entries+=("$line"); done < <(
69+
find "$CACHE_DIR" -mindepth 1 -maxdepth 1 -name '*@latest' -print
70+
find "$CACHE_DIR" -mindepth 2 -maxdepth 2 -name '*@latest' -print
71+
# Scoped packages without @latest suffix: @scope/pkg dirs that contain a package.json
72+
find "$CACHE_DIR" -mindepth 2 -maxdepth 2 -not -name '*@latest' -type d -print | while read -r d; do
73+
[[ -f "$d/package.json" ]] && echo "$d"
74+
done
75+
)
76+
fi
77+
78+
if [[ ${#entries[@]} -eq 0 ]]; then
79+
echo "No @latest plugin caches found."
80+
exit 0
81+
fi
82+
83+
outdated=()
84+
85+
for entry in "${entries[@]}"; do
86+
pkg_json="$entry/package.json"
87+
[[ -f "$pkg_json" ]] || continue
88+
89+
# Installed version is the value under .dependencies (first key)
90+
pkg_name=$(python3 -c "import json,sys; d=json.load(open('$pkg_json')); print(list(d.get('dependencies',{}).keys())[0])" 2>/dev/null) || continue
91+
installed=$(python3 -c "import json,sys; d=json.load(open('$pkg_json')); print(list(d.get('dependencies',{}).values())[0])" 2>/dev/null) || continue
92+
93+
latest=$(npm view "$pkg_name" version 2>/dev/null) || { echo " ⚠ could not fetch npm version for $pkg_name"; continue; }
94+
95+
if [[ "$installed" == "$latest" ]]; then
96+
echo " ✓ $pkg_name $installed (up to date)"
97+
else
98+
echo " ✗ $pkg_name $installed → $latest"
99+
outdated+=("$entry")
100+
fi
101+
done
102+
103+
if [[ ${#outdated[@]} -eq 0 ]]; then
104+
echo ""
105+
echo "All plugins are up to date."
106+
exit 0
107+
fi
108+
109+
echo ""
110+
111+
if [[ "$MODE" == "--check" ]]; then
112+
echo "${#outdated[@]} plugin(s) have updates available. Run opencode-plugins-refresh to upgrade."
113+
exit 1
114+
fi
115+
116+
if [[ "$MODE" != "--force" ]]; then
117+
read -r -p "Clear cache for ${#outdated[@]} outdated plugin(s) and restart opencode to upgrade? [y/N] " confirm
118+
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
119+
fi
120+
121+
for entry in "${outdated[@]}"; do
122+
echo " rm -rf $entry"
123+
rm -rf "$entry"
124+
done
125+
126+
echo ""
127+
echo "Done. Restart opencode to pull the latest versions."

0 commit comments

Comments
 (0)