Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.6.1] — 2026-07-27 (patch)

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

## [0.6.1] — 2026-07-24

- **Fixed: reasoning/thinking variants showed as meaningless numbered entries for
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ Drop `@latest` (`"@stablekernel/opencode-cursor"`) or pin a version
The stale-version check is skipped when the `CI` or `NO_UPDATE_NOTIFIER`
environment variable is set.

To keep the plugin up to date easily, install the `opencode-plugins-refresh` helper (offered by
the one-line installer, or install manually — see [Keeping the plugin up to date](#keeping-the-plugin-up-to-date)).

The plugin injects the `provider` block automatically. If you need explicit control:

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

## Keeping the plugin up to date

opencode pins `@latest` plugins on first install and never auto-updates them. When the installed
version falls behind the latest release, the plugin shows a warning once every 24 hours at startup.
The warning tells you what to do:

- If `opencode-plugins-refresh` is on your `PATH`, the warning says `run: opencode-plugins-refresh`.
- Otherwise it shows the raw `rm -rf` command and suggests re-running the installer to get the
helper script.

### opencode-plugins-refresh

`opencode-plugins-refresh` is a shell script that checks all `@latest` plugin caches for updates
by comparing pinned versions against npm, and optionally clears outdated caches so opencode
re-fetches the latest on next launch.

```bash
opencode-plugins-refresh # check for updates, prompt to clear cache
opencode-plugins-refresh --check # check only, exit 1 if outdated
opencode-plugins-refresh --force # clear all outdated caches without prompting
```

The `--check` flag exits with code `1` when any cache is outdated, making it suitable for CI jobs
or cron checks.

The one-line installer offers to install `opencode-plugins-refresh` to `~/.local/bin` (step 4).
To install it manually at any time:

```bash
curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh \
-o ~/.local/bin/opencode-plugins-refresh && chmod +x ~/.local/bin/opencode-plugins-refresh
```

Make sure `~/.local/bin` is on your `PATH`.

## Authenticate

```bash
Expand Down
27 changes: 27 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -394,11 +394,38 @@ else
info "Set it with ${DIM}export CURSOR_API_KEY=\"key_...\"${RESET} or run ${DIM}opencode auth login${RESET} (choose \"Cursor\")."
fi

# ---- 4. opencode-plugins-refresh ---------------------------------------------
step "Installing opencode-plugins-refresh helper (optional)"
LOCAL_BIN="$HOME/.local/bin"
mkdir -p "$LOCAL_BIN"
SCRIPT_URL="https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh"
if have_tty; then
printf 'Install opencode-plugins-refresh to %s? [y/N] ' "$LOCAL_BIN"
read -r REPLY <"$TTY" || REPLY=""
case "$REPLY" in
[yY]*)
if curl -fsSL "$SCRIPT_URL" -o "$LOCAL_BIN/opencode-plugins-refresh" 2>/dev/null; then
chmod +x "$LOCAL_BIN/opencode-plugins-refresh"
ok "Installed opencode-plugins-refresh → ${DIM}${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
info "Make sure ${DIM}${LOCAL_BIN}${RESET} is on your PATH."
else
err "Failed to download opencode-plugins-refresh from ${SCRIPT_URL}"
warn "You can install it manually later by re-running this installer."
fi
;;
*) info "Skipped. Install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}" ;;
esac
else
info "Non-interactive install — skipping opencode-plugins-refresh."
info "To install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
fi

# ---- done --------------------------------------------------------------------
step "Done"
ok "opencode-cursor is installed."
info "Next:"
info " 1. ${DIM}Ensure CURSOR_API_KEY is set, or run: opencode auth login${RESET}"
info " 2. ${DIM}Restart opencode, then run: opencode models${RESET} (lists cursor/* models)"
info " 3. ${DIM}Run: opencode-plugins-refresh --check${RESET} (check for plugin updates)"
info ""
info "Docs: ${DIM}${REPO_URL}#readme${RESET}"
127 changes: 127 additions & 0 deletions scripts/opencode-plugins-refresh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# Check and/or refresh opencode's @latest plugin cache.
# Compares each cached plugin version against npm and optionally clears
# outdated entries so opencode re-fetches the latest on next launch.
#
# Usage:
# opencode-plugins-refresh # check for updates, prompt to clear cache
# opencode-plugins-refresh --check # check only, no changes
# opencode-plugins-refresh --force # clear all @latest caches without prompting
# opencode-plugins-refresh --package <name> # check/update one specific plugin
# opencode-plugins-refresh --package <name> --check # check only for that plugin
# opencode-plugins-refresh --package <name> --force # force-clear that plugin's cache

set -euo pipefail

CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/opencode/packages"
MODE=""
PACKAGE=""

# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--check|--force)
MODE="$1"
shift
;;
--package)
[[ $# -ge 2 ]] || { echo "Error: --package requires a package name argument."; exit 1; }
PACKAGE="$2"
shift 2
;;
--help|-h)
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "Unknown argument: $1"
echo "Usage: $(basename "$0") [--package <name>] [--check|--force]"
exit 1
;;
esac
done

if [[ ! -d "$CACHE_DIR" ]]; then
echo "No opencode plugin cache found at $CACHE_DIR — nothing to do."
exit 0
fi

# Build the list of entries to check
entries=()

if [[ -n "$PACKAGE" ]]; then
# Build the cache path for the given package.
# Most plugins: $CACHE_DIR/my-plugin@latest or $CACHE_DIR/@scope/name@latest
# Some scoped plugins omit the @latest suffix: $CACHE_DIR/@scope/name
pkg_cache_path="$CACHE_DIR/${PACKAGE}@latest"
if [[ ! -d "$pkg_cache_path" ]]; then
pkg_cache_path="$CACHE_DIR/${PACKAGE}"
fi
if [[ ! -d "$pkg_cache_path" ]]; then
echo "Plugin '$PACKAGE' not found in cache — nothing to do."
exit 0
fi
entries=("$pkg_cache_path")
else
# Collect all @latest cache dirs (top-level and scoped).
# Also include scoped packages that lack the @latest suffix (e.g. @stablekernel/opencode-cursor).
while IFS= read -r line; do entries+=("$line"); done < <(
find "$CACHE_DIR" -mindepth 1 -maxdepth 1 -name '*@latest' -print
find "$CACHE_DIR" -mindepth 2 -maxdepth 2 -name '*@latest' -print
# Scoped packages without @latest suffix: @scope/pkg dirs that contain a package.json
find "$CACHE_DIR" -mindepth 2 -maxdepth 2 -not -name '*@latest' -type d -print | while read -r d; do
[[ -f "$d/package.json" ]] && echo "$d"
done
)
fi

if [[ ${#entries[@]} -eq 0 ]]; then
echo "No @latest plugin caches found."
exit 0
fi

outdated=()

for entry in "${entries[@]}"; do
pkg_json="$entry/package.json"
[[ -f "$pkg_json" ]] || continue

# Installed version is the value under .dependencies (first key)
pkg_name=$(python3 -c "import json,sys; d=json.load(open('$pkg_json')); print(list(d.get('dependencies',{}).keys())[0])" 2>/dev/null) || continue
installed=$(python3 -c "import json,sys; d=json.load(open('$pkg_json')); print(list(d.get('dependencies',{}).values())[0])" 2>/dev/null) || continue

latest=$(npm view "$pkg_name" version 2>/dev/null) || { echo " ⚠ could not fetch npm version for $pkg_name"; continue; }

if [[ "$installed" == "$latest" ]]; then
echo " ✓ $pkg_name $installed (up to date)"
else
echo " ✗ $pkg_name $installed → $latest"
outdated+=("$entry")
fi
done

if [[ ${#outdated[@]} -eq 0 ]]; then
echo ""
echo "All plugins are up to date."
exit 0
fi

echo ""

if [[ "$MODE" == "--check" ]]; then
echo "${#outdated[@]} plugin(s) have updates available. Run opencode-plugins-refresh to upgrade."
exit 1
fi

if [[ "$MODE" != "--force" ]]; then
read -r -p "Clear cache for ${#outdated[@]} outdated plugin(s) and restart opencode to upgrade? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
fi

for entry in "${outdated[@]}"; do
echo " rm -rf $entry"
rm -rf "$entry"
done

echo ""
echo "Done. Restart opencode to pull the latest versions."
Loading