#18545 · @LanternCX · opened Mar 21, 2026 at 5:33 PM UTC · last updated Mar 21, 2026 at 5:35 PM UTC

feat(cli): add manual plugin update command

appfeat
47
+40105 files

Score breakdown

Impact

8.0

Clarity

8.0

Urgency

4.0

Ease Of Review

7.0

Guidelines

8.0

Readiness

7.0

Size

0.0

Trust

5.0

Traction

0.0

Summary

This PR introduces a new opencode plugin update CLI command to allow users to manually refresh npm plugins declared in their configuration. The command classifies and handles various plugin specifier types, ensuring only updatable npm packages are refreshed. It includes new tests and documentation.

Open in GitHub

Description

Issue for this PR

Closes #18544

Type of change

  • [ ] Bug fix
  • [x] New feature
  • [ ] Refactor / code improvement
  • [x] Documentation

What does this PR do?

This PR adds a new opencode plugin update command so users can manually refresh npm plugins declared in config.

Changes included:

  • Adds a plugin update CLI subcommand that classifies plugin specifiers and handles each category explicitly: updatable npm packages are updated, while pinned versions, local file:// entries, ignored plugins, and unsupported specifiers are skipped.
  • Adds test coverage for classification, successful updates, no-updatable-plugin cases, unchanged-version (current) cases, and install-failure handling.
  • Updates CLI and plugin docs to describe how opencode plugin update works and what it does not update.

This gives users a clear manual refresh path for plugin cache updates (without waiting for startup behavior) and provides explicit updated/current/skipped/failed results.

How did you verify your code works?

  • Added and reviewed tests in packages/opencode/test/cli/plugin-update.test.ts.
  • Ran the following checks locally in packages/opencode:
    • bun test test/cli/plugin-update.test.ts
    • bun typecheck
  • Results:
    • bun test test/cli/plugin-update.test.ts: 5 passed, 0 failed
    • bun typecheck: passed

Screenshots / recordings

N/A (CLI and docs changes)

Checklist

  • [x] I have tested my changes locally
  • [x] I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

Linked Issues

#18544 [FEATURE]: Add a manual `opencode plugin update` command

View issue

Comments

No comments.

Changed Files

packages/opencode/src/cli/cmd/plugin.ts

+1890
@@ -0,0 +1,189 @@
import path from "path"
import { cmd } from "./cmd"
import { ConfigPaths } from "../../config/paths"
import { Config } from "../../config/config"
import { Instance } from "../../project/instance"
import { BunProc } from "../../bun"
import { UI } from "../ui"
import { Global } from "../../global"
import { Flag } from "../../flag/flag"
const ignored = ["opencode-openai-codex-auth", "opencode-copilot-auth"]
const pkg = /^(?:@[a-z0-9][a-z0-9-._~]*\/)?[a-z0-9][a-z0-9-._~]*$/i
function split(spec: string) {
const at = spec.lastIndexOf("@")
if (at > 0) return { pkg: spec.slice(0, at), version: spec.slice(at + 1) }
return { pkg: spec, version: null }
}
function ignoredPlugin(spec: string) {
return ignored.some((item) => spec.includes(item))
}
function unsupported(spec: string) {
return (
spec.startsWith("npm:") ||
spec.includes("@npm:") ||
spec.startsWith("workspace:") ||
spec.includes("@workspace:") ||
spec.startsWith("link:") ||
spec.includes("@link:") ||
spec.startsWith("patch:") ||
spec.includes("@patch:") ||
spec.startsWith("github:") ||
spec.includes("@github:") ||
spec.sta

packages/opencode/src/index.ts

+20
@@ -30,6 +30,7 @@ import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
import { SessionCommand } from "./cli/cmd/session"
import { DbCommand } from "./cli/cmd/db"
import { PluginCommand } from "./cli/cmd/plugin"
import path from "path"
import { Global } from "./global"
import { JsonMigration } from "./storage/json-migration"
@@ -132,6 +133,7 @@ let cli = yargs(hideBin(process.argv))
.command(DebugCommand)
.command(ConsoleCommand)
.command(ProvidersCommand)
.command(PluginCommand)
.command(AgentCommand)
.command(UpgradeCommand)
.command(UninstallCommand)

packages/opencode/test/cli/plugin-update.test.ts

+1740
@@ -0,0 +1,174 @@
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import fs from "fs/promises"
import os from "os"
import path from "path"
const seen = {
install: [] as Array<{ pkg: string; version: string }>,
load: 0,
out: [] as string[],
}
mock.module("../../src/project/instance", () => ({
Instance: {
provide: async (input: { directory: string; fn: () => Promise<unknown> | unknown }) => input.fn(),
directory: process.cwd(),
worktree: process.cwd(),
},
}))
mock.module("../../src/config/config", () => ({
Config: {
getGlobal: async () => ({ plugin: ["foo", "@scope/bar@1.2.3", "file:///tmp/plugin.ts", "npm:baz@latest"] }),
readFile: async () => undefined,
deduplicatePlugins: (list: string[]) => Array.from(new Set(list)),
},
}))
mock.module("../../src/config/paths", () => ({
ConfigPaths: {
projectFiles: async () => [],
fileInDirectory: () => [],
parseText: async (text: string) => JSON.parse(text),
},
}))
mock.module("../../src/bun", () => ({
BunProc: {
install: async (pkg: string, version: string) => {
seen.install.push({ pkg, version })
return

packages/web/src/content/docs/cli.mdx

+240
@@ -276,6 +276,30 @@ opencode mcp debug <name>
---
### plugin
Manage configured npm plugins.
```bash
opencode plugin [command]
```
---
#### update
Refresh npm plugins declared in your config.
```bash
opencode plugin update
```
This only updates npm plugins listed in config. It does not load or update local plugins from `plugin/` or `plugins/` directories, and it does not upgrade opencode itself.
Plugins pinned to an explicit version are skipped. Unsupported specifiers are also skipped.
---
### models
List all available models from configured providers.

packages/web/src/content/docs/plugins.mdx

+120
@@ -51,6 +51,18 @@ Browse available plugins in the [ecosystem](/docs/ecosystem#plugins).
---
### Refresh the cache
To manually refresh the npm plugin cache, run:
```bash
opencode plugin update
```
This only refreshes npm plugins declared in config. It does not touch local `plugin/` or `plugins/` directories, and plugins pinned to an explicit version or using an unsupported specifier are skipped.
---
### Load order
Plugins are loaded from all sources and all hooks run in sequence. The load order is: