From 55837a270db927161435e9fb37e601e775a18d8f Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 9 Aug 2026 00:18:25 +0000 Subject: [PATCH] fix(plugin): bump both plugins to 0.2.0 so installs actually update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code only pulls a new copy of a plugin when the plugin's own version moves: "If set, users only receive updates when you bump this field." https://code.claude.com/docs/en/plugins-reference#version-management Both plugins have sat at 0.1.0 since they were created, through every change since — including v0.29.2, which rewrote every command file to carry the namespaced names. An existing install has been serving the old copy the whole time, so the surface people were told to use is the surface they did not get. Shipping a moshcode release does not carry plugin edits; only this number does. Both go to 0.2.0. The content they describe changed; the version now says so. To stop it happening again, `version` joins the catalog next to `commands` — the thing an editor is already touching when they change a plugin — with the mechanism spelled out above it, and a test pinning the catalog to the manifest that actually ships. Verified the guard fails when the two disagree rather than merely passing today. The README now documents `claude plugin update @moshcode`, which is the step users need and the one nothing mentioned: `moshcode upgrade` updates the CLI and leaves installed plugins exactly where they were. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 8 ++++++++ plugins/crypto/.claude-plugin/plugin.json | 2 +- plugins/stocks/.claude-plugin/plugin.json | 2 +- src/plugins.mjs | 12 ++++++++++++ test/plugins.test.mjs | 11 +++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91abf4b..2370631 100644 --- a/README.md +++ b/README.md @@ -388,6 +388,14 @@ score that must not be ranked against an equity's. Restart the engine after installing either; a newly installed plugin is not live in a session that is already running. +Updating an installed plugin is its own step, and upgrading moshcode does not do +it: an engine only pulls a new copy when the *plugin's* own version moves. + +```sh +claude plugin update stocks@moshcode +claude plugin update crypto@moshcode +``` + Plugin commands are namespaced `/:` — always, not only when two plugins collide — so it is `/stocks:signals AAPL`, and a bare `/signals` answers `Unknown command`. Typing `/` and picking from the menu inserts the right form. diff --git a/plugins/crypto/.claude-plugin/plugin.json b/plugins/crypto/.claude-plugin/plugin.json index 8b31c56..68f715e 100644 --- a/plugins/crypto/.claude-plugin/plugin.json +++ b/plugins/crypto/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "crypto", "description": "Crypto market data slash commands backed by advis0r.com: live prices, technical scores, order books, OHLCV history and sparklines across Alpaca's US crypto venue.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "moshcoder", "url": "https://moshcode.sh" diff --git a/plugins/stocks/.claude-plugin/plugin.json b/plugins/stocks/.claude-plugin/plugin.json index 26bd2ac..4470ade 100644 --- a/plugins/stocks/.claude-plugin/plugin.json +++ b/plugins/stocks/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "stocks", "description": "Equity research slash commands backed by advis0r.com: scored reports, extracted signals, transcript search, company-name lookup, and ranked watchlists.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "moshcoder", "url": "https://moshcode.sh" diff --git a/src/plugins.mjs b/src/plugins.mjs index 3c51f48..817bb2f 100644 --- a/src/plugins.mjs +++ b/src/plugins.mjs @@ -39,16 +39,28 @@ export function marketplaceSource(env = process.env) { * `example` exists because the invitation printed after an install has to be * runnable. It used to append a hardcoded "NVDA" to whatever came first in * `commands`, which told anyone installing the crypto plugin to try a stock. + * + * `version` mirrors each plugin's own plugin.json, and it is not decoration: + * "If set, users only receive updates when you bump this field." + * https://code.claude.com/docs/en/plugins-reference#version-management + * + * So editing a plugin's commands and shipping a moshcode release is *not* + * enough — an existing install keeps serving the old copy until this number + * moves. Both plugins sat at 0.1.0 through v0.29.2, which rewrote every command + * file. Change a plugin's contents, bump its version, in the same commit. + * A test fails when this list and the manifests disagree. */ export const PLUGINS = [ { name: "stocks", + version: "0.2.0", description: "equity research slash commands backed by advis0r.com", commands: ["/stocks:stocks", "/stocks:signals", "/stocks:research", "/stocks:lookup", "/stocks:reports", "/stocks:discover"], example: "/stocks:stocks NVDA", }, { name: "crypto", + version: "0.2.0", description: "crypto market data slash commands backed by advis0r.com", commands: ["/crypto:crypto", "/crypto:quote", "/crypto:book", "/crypto:bars", "/crypto:spark", "/crypto:pairs", "/crypto:coin"], example: "/crypto:crypto BTC", diff --git a/test/plugins.test.mjs b/test/plugins.test.mjs index 9a808f0..59e729a 100644 --- a/test/plugins.test.mjs +++ b/test/plugins.test.mjs @@ -143,6 +143,17 @@ test("every plugin the marketplace lists exists, with a manifest and its command const plugin = JSON.parse(fs.readFileSync(new URL(".claude-plugin/plugin.json", dir), "utf8")); assert.equal(plugin.name, entry.name, `${entry.name}'s manifest disagrees with the marketplace`); + // An engine only pulls a new copy of a plugin when its own version moves — + // a moshcode release does not carry plugin edits to existing installs on its + // own. Both plugins sat at 0.1.0 through v0.29.2, which rewrote every + // command file, so this pins the catalog to the manifest that ships. + assert.match(plugin.version ?? "", /^\d+\.\d+\.\d+$/, `${entry.name} has no usable plugin.json version`); + assert.equal( + PLUGINS.find((p) => p.name === entry.name)?.version, + plugin.version, + `${entry.name}: the catalog and plugin.json disagree on the version`, + ); + const catalog = PLUGINS.find((p) => p.name === entry.name); const files = fs.readdirSync(new URL("commands/", dir)).filter((f) => f.endsWith(".md")); // Namespaced, because that is how Claude Code invokes them. Comparing bare