mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-26 21:42:06 +00:00
docs: expand plugin guides
This commit is contained in:
parent
22c63833d2
commit
0cdd711abf
14 changed files with 2823 additions and 517 deletions
|
|
@ -138,16 +138,20 @@ V2 config uses more ergonomic shapes, but conversion is optional. When the user
|
|||
requests conversion, inspect the complete configuration, preserve behavior and
|
||||
unrelated settings, and apply only the relevant migrations from the guide. For
|
||||
plugin migrations, fetch and follow both the migration guide and the full
|
||||
[plugins guide](https://opencode.ai/v2/docs/build/plugins). If non-API V1
|
||||
[plugins guide](https://opencode.ai/v2/docs/build/plugins/overview). If non-API V1
|
||||
functionality fails in V2, use the `report` skill to file it as a compatibility
|
||||
bug.
|
||||
|
||||
## [Plugins](https://opencode.ai/v2/docs/build/plugins)
|
||||
## [Plugins](https://opencode.ai/v2/docs/build/plugins/overview)
|
||||
|
||||
For questions about creating, configuring, loading, publishing, or migrating
|
||||
plugins, fetch the full [plugins guide](https://opencode.ai/v2/docs/build/plugins)
|
||||
before answering. This includes questions about the Effect plugin API, hooks,
|
||||
transforms, tools, plugin context capabilities, and package entrypoints.
|
||||
plugins, fetch the full [plugins guide](https://opencode.ai/v2/docs/build/plugins/overview)
|
||||
before answering. For Effect-native plugins, also fetch the
|
||||
[Effect plugin guide](https://opencode.ai/v2/docs/build/plugins/effect). For
|
||||
terminal UI extensions, fetch the
|
||||
[CLI plugin guide](https://opencode.ai/v2/docs/build/plugins/cli). These guides
|
||||
cover hooks, transforms, tools, plugin context capabilities, and package
|
||||
entrypoints.
|
||||
|
||||
## [Service](https://opencode.ai/v2/docs/troubleshooting#check-the-background-service)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
## Local development
|
||||
|
||||
- Run `bun dev` from this package and use the local URL printed by Astro.
|
||||
- Do not run `bun typecheck`, `bun run build`, or another Astro process while the dev server is running. They share the Vite dependency cache and can break the active dev server. Leave validation to the user when the dev server is active.
|
||||
|
||||
## Validation
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
title: "Build"
|
||||
title: "Intro"
|
||||
---
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="Extend OpenCode" href="/build/plugins">
|
||||
<Card title="Extend OpenCode" href="/build/plugins/overview">
|
||||
Build plugins that add tools, integrations, commands, agents, and custom behavior while keeping the rest of OpenCode
|
||||
intact.
|
||||
</Card>
|
||||
|
|
@ -16,7 +16,3 @@ title: "Build"
|
|||
around it.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Callout type="warning">
|
||||
The plugin API, client, and SDK are still being finalized during beta and may change before OpenCode 2.0 is stable.
|
||||
</Callout>
|
||||
|
|
|
|||
|
|
@ -1,494 +0,0 @@
|
|||
---
|
||||
title: "Plugins"
|
||||
---
|
||||
|
||||
Plugins extend OpenCode in-process. They can transform agents, models, commands,
|
||||
integrations, references, skills, and tools; intercept model requests and tool
|
||||
execution; and call a subset of the V2 client.
|
||||
|
||||
<Callout type="warning">
|
||||
The V2 plugin API is beta. Entrypoints, hooks, draft shapes, and configuration may change before the stable release.
|
||||
Use the `/v2` exports described on this page.
|
||||
</Callout>
|
||||
|
||||
## Load plugins
|
||||
|
||||
Plugins can be loaded from npm packages, explicit local paths, or config
|
||||
directories. Each module must have one default export containing a unique
|
||||
plugin `id` and a `setup` function.
|
||||
|
||||
### Configuration
|
||||
|
||||
Add ordered entries to the `plugins` field in `opencode.json(c)`:
|
||||
|
||||
```jsonc title="opencode.jsonc"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugins": [
|
||||
"opencode-acme-plugin@1.2.0",
|
||||
"@acme/opencode-plugin",
|
||||
"./plugins/local.ts",
|
||||
{
|
||||
"package": "./plugins/reviewer.ts",
|
||||
"options": {
|
||||
"agent": "reviewer",
|
||||
"strict": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
A string is either a package specifier or a local path. Local paths must start
|
||||
with `./` or `../` and resolve relative to the configuration file containing
|
||||
the entry. Absolute paths and `file://` URLs are also supported. Both scoped
|
||||
packages and versioned package specifiers are supported.
|
||||
|
||||
Use the object form to pass JSON configuration to the plugin. OpenCode passes
|
||||
`options` unchanged as `ctx.options`; omitted options become an empty object.
|
||||
The plugin owns validation and defaults for its options.
|
||||
|
||||
See [Config](/config#locations) for configuration locations and precedence.
|
||||
Entries from all applicable files are processed from lowest to highest
|
||||
precedence rather than replacing the entire array.
|
||||
|
||||
### Local discovery
|
||||
|
||||
OpenCode automatically scans this directory in every discovered OpenCode config
|
||||
directory:
|
||||
|
||||
```text
|
||||
.opencode/plugins/
|
||||
```
|
||||
|
||||
The equivalent global directory is `~/.config/opencode/plugins/`. Direct `.ts`
|
||||
and `.js` children are loaded. An immediate child directory is also loaded as a
|
||||
package when OpenCode can resolve a string `exports`, `module`, or `main`
|
||||
entrypoint, or an `index.ts` or `index.js` file.
|
||||
|
||||
A `plugins/` directory beside a project-root `opencode.json` is not discovered
|
||||
automatically. Put it under `.opencode/`, or add its file explicitly with a
|
||||
relative config entry.
|
||||
|
||||
### Enable and disable
|
||||
|
||||
A string beginning with `-` disables plugins by their exported `id`. `*`
|
||||
matches every ID, and a suffix of `.*` matches an ID prefix. Directives are
|
||||
applied in order:
|
||||
|
||||
```jsonc title="opencode.jsonc"
|
||||
{
|
||||
"plugins": ["./plugins/reviewer.ts", "-acme.reviewer", "-opencode.provider.*", "opencode.provider.openai"],
|
||||
}
|
||||
```
|
||||
|
||||
Package specifiers and local paths locate plugin modules; they are not disable
|
||||
selectors. Use the `id` from the plugin's default export to disable it. A later
|
||||
ID entry re-enables a loaded or built-in plugin. Explicit config directives run
|
||||
after local auto-discovery, so they can disable discovered plugins by ID.
|
||||
|
||||
User plugins are activated in configured order between OpenCode's internal
|
||||
plugin phases. Hooks run sequentially in registration order, and later hooks
|
||||
observe earlier mutations. Do not depend on the internal phase ordering while
|
||||
the API is beta.
|
||||
|
||||
### Installation and dependencies
|
||||
|
||||
OpenCode installs bare package entries and their production dependencies into
|
||||
an isolated cache. Package installation does not run lifecycle scripts.
|
||||
Published packages should expose their plugin entrypoint and include every
|
||||
runtime import in `dependencies`.
|
||||
|
||||
Install a package plugin globally with the CLI:
|
||||
|
||||
```sh
|
||||
opencode2 plugin add opencode-acme-plugin@1.2.0
|
||||
```
|
||||
|
||||
This installs and inspects the package before changing configuration. Packages
|
||||
with a server entrypoint are added to global `opencode.json(c)`. Packages that
|
||||
only expose `./tui` are added to global `cli.json` instead.
|
||||
|
||||
The command accepts npm registry package names with an optional version,
|
||||
dist-tag, or semver range. Configure local paths directly instead; Git, tarball,
|
||||
and npm alias targets are not accepted by `plugin add`.
|
||||
|
||||
List configured and active plugins, or remove a package from both global server
|
||||
and TUI configuration:
|
||||
|
||||
```sh
|
||||
opencode2 plugin list
|
||||
opencode2 plugin list --builtin
|
||||
opencode2 plugin remove opencode-acme-plugin@1.2.0
|
||||
```
|
||||
|
||||
Built-in server plugins are hidden from the default list. Removing a plugin
|
||||
keeps its package cache available for later reuse.
|
||||
|
||||
Local files and local package directories are imported directly. OpenCode does
|
||||
**not** install their dependencies. Install dependencies in a `package.json`
|
||||
visible from the plugin file, for example:
|
||||
|
||||
```sh
|
||||
cd .opencode
|
||||
bun add @opencode-ai/plugin@beta
|
||||
```
|
||||
|
||||
Match the plugin package version to the OpenCode release you target.
|
||||
|
||||
Configuration and discovered plugin files under watched config directories are
|
||||
reloaded when they change. Reloading replaces the active plugin generation and
|
||||
releases its scoped registrations. Restart OpenCode after changing an npm
|
||||
package version or a local dependency when no watched file changed.
|
||||
|
||||
## Create a plugin
|
||||
|
||||
Export the result of `Plugin.define` as the module default:
|
||||
|
||||
```ts title=".opencode/plugins/reviewer.ts"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.reviewer",
|
||||
setup: async (ctx) => {
|
||||
const description =
|
||||
typeof ctx.options.description === "string" ? ctx.options.description : "Reviews code for regressions"
|
||||
|
||||
await ctx.agent.transform((agents) => {
|
||||
agents.update("reviewer", (agent) => {
|
||||
agent.description = description
|
||||
agent.mode = "subagent"
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`setup` runs each time the plugin is activated. Register long-lived behavior
|
||||
during setup; do not wait there on an infinite event stream. It may return a
|
||||
synchronous or asynchronous cleanup function. OpenCode awaits that cleanup
|
||||
when the plugin is disabled, reloaded, or shut down:
|
||||
|
||||
```ts
|
||||
setup: async (ctx) => {
|
||||
const controller = new AbortController()
|
||||
const task = synchronize(ctx, controller.signal)
|
||||
|
||||
return async () => {
|
||||
controller.abort()
|
||||
await task
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Hook registrations are released automatically with the same plugin scope. Use
|
||||
the returned cleanup for resources the plugin owns, such as timers, watchers,
|
||||
connections, and background tasks.
|
||||
|
||||
### Context
|
||||
|
||||
The plugin context is essentially an [OpenCode server client](/build/client).
|
||||
Its read and action methods use the same inputs and responses as the client. It
|
||||
adds plugin-only methods for transforms, runtime hooks, reloads, registrations,
|
||||
and plugin options.
|
||||
|
||||
| Capability | Available operations |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `ctx.agent` | `list`, `get`, `transform`, `reload` |
|
||||
| `ctx.catalog.provider` | `list`, `get` |
|
||||
| `ctx.catalog.model` | `list`, `get`, `default` |
|
||||
| `ctx.catalog` | `transform`, `reload` |
|
||||
| `ctx.command` | `list`, `transform`, `reload` |
|
||||
| `ctx.integration` | `list`, `get`, `connect`, `attempt`, `transform`, `reload`, and connection lookup/resolution |
|
||||
| `ctx.plugin` | `list` currently active plugin IDs |
|
||||
| `ctx.reference` | `list`, `transform`, `reload` |
|
||||
| `ctx.session` | `create`, `get`, `prompt`, `command`, `rename`, `synthetic`, `interrupt`, `wait`, and `hook` |
|
||||
| `ctx.skill` | `list`, `transform`, `reload` |
|
||||
| `ctx.tool` | `transform` and `hook` |
|
||||
| `ctx.aisdk` | `hook` |
|
||||
| `ctx.event` | `subscribe` to the current public server event stream |
|
||||
| `ctx.options` | Readonly options from the matching config object |
|
||||
|
||||
### Transform hooks
|
||||
|
||||
Transform hooks let a plugin modify how OpenCode is configured. Use them to add
|
||||
or remove definitions, override settings, choose defaults, and provide tools or
|
||||
other sources.
|
||||
|
||||
| Transform | Draft operations |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `agent.transform` | `list`, `get`, `default`, `update`, `remove` |
|
||||
| `catalog.transform` | Provider `list`, `get`, `update`, `remove`; model `get`, `update`, `remove`; default model `get`, `set` |
|
||||
| `command.transform` | `list`, `get`, `update`, `remove` |
|
||||
| `integration.transform` | Integration `list`, `get`, `update`, `remove`; method `list`, `update`, `remove` |
|
||||
| `reference.transform` | `add`, `remove`, `list` |
|
||||
| `skill.transform` | `source`, `list` |
|
||||
| `tool.transform` | `add` |
|
||||
|
||||
Here's an example that keeps models synced from a remote source:
|
||||
|
||||
```js title=".opencode/plugins/remote-models.js"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.remote-models",
|
||||
setup: async (ctx) => {
|
||||
let models = []
|
||||
|
||||
await ctx.catalog.transform((catalog) => {
|
||||
for (const model of models) {
|
||||
catalog.model.update(model.providerID, model.id, (draft) => Object.assign(draft, model))
|
||||
}
|
||||
})
|
||||
|
||||
const refresh = async () => {
|
||||
const response = await fetch("https://example.com/opencode/models.json", {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
})
|
||||
if (!response.ok) return
|
||||
models = await response.json()
|
||||
await ctx.catalog.reload()
|
||||
}
|
||||
|
||||
await refresh()
|
||||
const timer = setInterval(() => void refresh().catch(console.error), 60_000)
|
||||
return () => clearInterval(timer)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`ctx.catalog.reload()` replays every catalog transform to derive the new
|
||||
catalog. Each plugin's logic remains composed with the others, so a later
|
||||
plugin can still modify models added by an earlier one. The catalog updates
|
||||
without restarting OpenCode.
|
||||
|
||||
### Runtime hooks
|
||||
|
||||
Runtime hooks intercept live operations:
|
||||
|
||||
| Hook | Mutable fields |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `ctx.aisdk.hook("sdk", callback)` | `sdk`, after inspecting `model`, `package`, and `options` |
|
||||
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
||||
| `ctx.session.hook("context", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
||||
| `ctx.session.hook("http.request", callback)` | `request`, immediately before provider dispatch |
|
||||
| `ctx.session.hook("http.response", callback)` | `response`, immediately after the provider responds |
|
||||
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
||||
| `ctx.tool.hook("execute.after", callback)` | Terminal `result` on success or `error` on failure |
|
||||
|
||||
HTTP hooks can modify requests and responses. They apply to native models; AI
|
||||
SDK models do not currently pass through these hooks. Request and response
|
||||
bodies are one-shot streams. Use `clone()` when you intentionally need a
|
||||
separate reader, but be aware that its slower branch may buffer data. To inspect
|
||||
or modify chunks while preserving streaming, replace the body with one piped
|
||||
through a `TransformStream`.
|
||||
|
||||
```ts
|
||||
await ctx.session.hook("http.request", (event) => {
|
||||
event.request.headers.set("x-session-id", event.sessionID)
|
||||
})
|
||||
|
||||
await ctx.session.hook("http.response", (event) => {
|
||||
event.response = new Response(event.response.body, {
|
||||
status: event.response.status,
|
||||
headers: { ...Object.fromEntries(event.response.headers), "x-plugin": "enabled" },
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
For example, remove a tool from selected model requests and normalize another
|
||||
tool's input:
|
||||
|
||||
```ts title=".opencode/plugins/guards.ts"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.guards",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.hook("context", (event) => {
|
||||
delete event.tools.write
|
||||
})
|
||||
|
||||
await ctx.tool.hook("execute.before", (event) => {
|
||||
if (event.tool !== "lookup" || typeof event.input !== "object" || event.input === null) return
|
||||
event.input = { ...event.input, source: "plugin" }
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
A hook failure fails the operation it intercepts. Keep runtime hooks fast and
|
||||
handle expected errors inside the callback.
|
||||
|
||||
## Examples
|
||||
|
||||
### Add a tool
|
||||
|
||||
Register a structural tool definition with a name and registration options.
|
||||
Define its input with JSON Schema and use an async executor:
|
||||
|
||||
```js title=".opencode/plugins/greeting.js"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.greeting",
|
||||
setup: async (ctx) => {
|
||||
await ctx.tool.transform((tools) => {
|
||||
tools.add("greeting", {
|
||||
description: "Create a greeting",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
required: ["name"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
output: {
|
||||
type: "object",
|
||||
properties: { greeting: { type: "string" } },
|
||||
required: ["greeting"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
execute: async ({ name }) => {
|
||||
const text = `Hello, ${name}!`
|
||||
return {
|
||||
output: { greeting: text },
|
||||
content: text,
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Unsupported characters in tool names are normalized to underscores. Namespace
|
||||
segments must begin with a letter, contain at most 64 letters, digits,
|
||||
underscores, or hyphens, and are joined with dots. Pass the optional third
|
||||
argument to `tools.add` to configure the registration with
|
||||
`{ namespace, codemode }`:
|
||||
|
||||
- `namespace` prefixes and groups the exposed tool name.
|
||||
- `codemode` defaults to `true` and makes the tool available through the
|
||||
`execute` CodeMode tool. Set `codemode: false` to expose it directly to the
|
||||
provider.
|
||||
|
||||
The executor receives a second context argument containing `id`, `sessionID`,
|
||||
`agent`, `messageID`, and `progress`. A tool with `output`
|
||||
must return `output`; Effect and Standard Schema codecs validate it, while raw
|
||||
JSON Schema definitions enforce JSON compatibility only. A tool
|
||||
without `output` returns model-visible `content` instead.
|
||||
|
||||
### Add a command
|
||||
|
||||
```js title=".opencode/plugins/review-command.js"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.review-command",
|
||||
setup: async (ctx) => {
|
||||
await ctx.command.transform((commands) => {
|
||||
commands.update("review", (command) => {
|
||||
command.description = "Review the current changes"
|
||||
command.template = "Review the current changes for correctness and missing tests."
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Set the default model
|
||||
|
||||
```js title=".opencode/plugins/default-model.js"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.default-model",
|
||||
setup: async (ctx) => {
|
||||
await ctx.catalog.transform((catalog) => {
|
||||
catalog.model.default.set("anthropic", "claude-sonnet-4-5")
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Publish a package
|
||||
|
||||
A package plugin uses the same default export as a local plugin. A minimal
|
||||
manifest is:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "opencode-acme-plugin",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./tui": "./src/tui.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "beta"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Packages with a TUI entrypoint should set `tui: true` on their server plugin
|
||||
definition. A locally connected TUI loads the package's `./tui` export from the
|
||||
existing OpenCode package cache. A TUI connected to a remote server skips it
|
||||
when that package is not installed locally.
|
||||
|
||||
Use versions compatible with the OpenCode release you target and test the
|
||||
installed package, not only a workspace-linked copy. Because the plugin API is
|
||||
beta, publish compatible plugin updates when V2 entrypoints or contracts
|
||||
change.
|
||||
|
||||
## Verify loading
|
||||
|
||||
List active plugin IDs through the V2 API:
|
||||
|
||||
```sh
|
||||
opencode2 api get /api/plugin
|
||||
```
|
||||
|
||||
If a plugin is absent, check the server log described in
|
||||
[Troubleshooting](/troubleshooting#read-logs). Invalid modules and setup failures are
|
||||
logged; one failing package does not prevent unrelated valid packages from
|
||||
being resolved.
|
||||
|
||||
## Effect
|
||||
|
||||
OpenCode provides a first-class Effect API for plugins through the
|
||||
`@opencode-ai/plugin/effect` entrypoint. Install `effect` alongside the
|
||||
plugin package and export an `effect` function instead of `setup`:
|
||||
|
||||
```sh
|
||||
bun add @opencode-ai/plugin@beta effect
|
||||
```
|
||||
|
||||
```ts title=".opencode/plugins/reviewer-effect.ts"
|
||||
import { Plugin } from "@opencode-ai/plugin/effect"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.reviewer-effect",
|
||||
effect: (ctx) =>
|
||||
Effect.gen(function* () {
|
||||
yield* ctx.agent.transform((agents) => {
|
||||
agents.update("reviewer", (agent) => {
|
||||
agent.description = "Reviews code for regressions"
|
||||
agent.mode = "subagent"
|
||||
})
|
||||
})
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Context operations return Effects. The plugin effect is scoped, so finalizers,
|
||||
fibers, and registrations are released when the plugin reloads or unloads.
|
||||
OpenCode does not expose its private Core services to the plugin; use the
|
||||
capabilities on `ctx`.
|
||||
|
||||
Typed tools can use `Schema` from `effect`. Effect and Promise plugins use the
|
||||
same `tools.add(name, tool, options?)` registration shape. Effect executors
|
||||
return an Effect and may fail with the typed tool failure channel.
|
||||
454
packages/www/src/docs/content/build/plugins/cli.mdx
Normal file
454
packages/www/src/docs/content/build/plugins/cli.mdx
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
---
|
||||
title: "CLI"
|
||||
---
|
||||
|
||||
CLI plugins extend the terminal with commands, routes, slots, Markdown renderers, notifications, and local state.
|
||||
|
||||
```ts title="src/tui.ts"
|
||||
import { Plugin } from "@opencode-ai/plugin/tui"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.cli",
|
||||
setup(context) {
|
||||
context.ui.toast.show({ message: "CLI plugin loaded", variant: "success" })
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
`setup` receives configuration, app metadata, the current location, the OpenCode client, cached data, theme tokens, the
|
||||
OpenTUI renderer, and the UI APIs documented below.
|
||||
|
||||
```ts
|
||||
setup(context) {
|
||||
const compact = context.options.compact === true
|
||||
const location = context.location ?? context.data.location.default()
|
||||
const version = context.app.version
|
||||
const channel = context.app.channel
|
||||
const client = context.client
|
||||
const renderer = context.renderer
|
||||
const theme = context.theme
|
||||
}
|
||||
```
|
||||
|
||||
Return a cleanup function for resources owned by the plugin.
|
||||
|
||||
```ts
|
||||
setup(context) {
|
||||
const stop = context.data.on("session.execution.succeeded", () => {})
|
||||
return () => stop()
|
||||
}
|
||||
```
|
||||
|
||||
## Client
|
||||
|
||||
`context.client` is the generated OpenCode client and can call the connected server, including a remote server.
|
||||
|
||||
```ts
|
||||
const response = await context.client.plugin.list({
|
||||
location: context.location ?? context.data.location.default(),
|
||||
})
|
||||
const plugins = response.data
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
Use `data.on` for one typed event or `data.listen` for every server event; both return an unsubscribe function.
|
||||
|
||||
```ts
|
||||
const stopPermission = context.data.on("permission.asked", (event) => {
|
||||
context.ui.toast.show({ message: `Permission ${event.data.id}` })
|
||||
})
|
||||
const stopAll = context.data.listen(({ details }) => console.log(details.type))
|
||||
|
||||
return () => {
|
||||
stopPermission()
|
||||
stopAll()
|
||||
}
|
||||
```
|
||||
|
||||
## Sessions
|
||||
|
||||
Session data exposes list, lookup, hierarchy, cost, status, synchronization, and invalidation.
|
||||
|
||||
```ts
|
||||
const sessions = context.data.session.list()
|
||||
const session = context.data.session.get(sessionID)
|
||||
const rootID = context.data.session.root(sessionID)
|
||||
const familyIDs = context.data.session.family(sessionID)
|
||||
const cost = context.data.session.cost(sessionID)
|
||||
const status = context.data.session.status(sessionID)
|
||||
await context.data.session.sync(sessionID)
|
||||
context.data.session.invalidate(sessionID)
|
||||
```
|
||||
|
||||
Pending inbox items and messages have list, lookup, sync, and invalidate APIs.
|
||||
|
||||
```ts
|
||||
await context.data.session.pending.sync(sessionID)
|
||||
const pending = context.data.session.pending.list(sessionID)
|
||||
context.data.session.pending.invalidate(sessionID)
|
||||
|
||||
await context.data.session.message.sync(sessionID)
|
||||
const messages = context.data.session.message.list(sessionID)
|
||||
const message = context.data.session.message.get(sessionID, messageID)
|
||||
context.data.session.message.invalidate(sessionID)
|
||||
```
|
||||
|
||||
Permission requests can be read and refreshed for a session.
|
||||
|
||||
```ts
|
||||
await context.data.session.permission.sync(sessionID)
|
||||
const requests = context.data.session.permission.list(sessionID) ?? []
|
||||
context.data.session.permission.invalidate(sessionID)
|
||||
```
|
||||
|
||||
Forms can be listed, refreshed, replied to, or cancelled at a location.
|
||||
|
||||
```ts
|
||||
import type { FormCancelInput, FormReplyInput } from "@opencode-ai/client"
|
||||
|
||||
async function handleForm(reply: FormReplyInput, cancel: FormCancelInput) {
|
||||
const location = context.location
|
||||
await context.data.session.form.sync(sessionID, location)
|
||||
const forms = context.data.session.form.list(sessionID, location) ?? []
|
||||
await context.data.session.form.reply(reply, location)
|
||||
await context.data.session.form.cancel(cancel, location)
|
||||
context.data.session.form.invalidate(sessionID, location)
|
||||
}
|
||||
```
|
||||
|
||||
## Projects and shells
|
||||
|
||||
Projects and saved permissions support list, lookup, sync, and invalidate operations.
|
||||
|
||||
```ts
|
||||
await context.data.project.sync()
|
||||
const projects = context.data.project.list()
|
||||
const project = context.data.project.get(projectID)
|
||||
context.data.project.invalidate()
|
||||
|
||||
await context.data.project.permission.sync(projectID)
|
||||
const saved = context.data.project.permission.list(projectID) ?? []
|
||||
context.data.project.permission.invalidate(projectID)
|
||||
```
|
||||
|
||||
Shell data supports location-scoped list, lookup, sync, and invalidate operations.
|
||||
|
||||
```ts
|
||||
await context.data.shell.sync(context.location)
|
||||
const shells = context.data.shell.list(context.location)
|
||||
const shell = context.data.shell.get(shellID)
|
||||
context.data.shell.invalidate(context.location)
|
||||
```
|
||||
|
||||
## Location data
|
||||
|
||||
Location state exposes the default location and refresh controls.
|
||||
|
||||
```ts
|
||||
const location = context.data.location.default()
|
||||
await context.data.location.sync(location)
|
||||
context.data.location.invalidate(location)
|
||||
```
|
||||
|
||||
Version-control state exposes repository information at a location.
|
||||
|
||||
```ts
|
||||
await context.data.location.vcs.sync(context.location)
|
||||
const vcs = context.data.location.vcs.info(context.location)
|
||||
const branch = vcs?.branch.current
|
||||
context.data.location.vcs.invalidate(context.location)
|
||||
```
|
||||
|
||||
Agents, commands, integrations, models, providers, references, skills, and MCP data share `list`, `sync`, and
|
||||
`invalidate` methods.
|
||||
|
||||
```ts
|
||||
const location = context.location
|
||||
await Promise.all([
|
||||
context.data.location.agent.sync(location),
|
||||
context.data.location.command.sync(location),
|
||||
context.data.location.integration.sync(location),
|
||||
context.data.location.model.sync(location),
|
||||
context.data.location.provider.sync(location),
|
||||
context.data.location.reference.sync(location),
|
||||
context.data.location.skill.sync(location),
|
||||
context.data.location.mcp.server.sync(location),
|
||||
context.data.location.mcp.resource.sync(location),
|
||||
])
|
||||
|
||||
const agents = context.data.location.agent.list(location) ?? []
|
||||
const commands = context.data.location.command.list(location) ?? []
|
||||
const integrations = context.data.location.integration.list(location) ?? []
|
||||
const models = context.data.location.model.list(location) ?? []
|
||||
const providers = context.data.location.provider.list(location) ?? []
|
||||
const references = context.data.location.reference.list(location) ?? []
|
||||
const skills = context.data.location.skill.list(location) ?? []
|
||||
const servers = context.data.location.mcp.server.list(location) ?? []
|
||||
const resources = context.data.location.mcp.resource.list(location) ?? []
|
||||
|
||||
context.data.location.model.invalidate(location)
|
||||
```
|
||||
|
||||
## Attention
|
||||
|
||||
Attention requests can show a system notification, play a configured sound, or do both based on terminal focus.
|
||||
|
||||
```ts
|
||||
const result = await context.attention.notify({
|
||||
title: "OpenCode",
|
||||
message: "Session done",
|
||||
notification: { when: "blurred" },
|
||||
sound: { name: "done", volume: 0.5, when: "always" },
|
||||
})
|
||||
console.log(result.ok, result.notification, result.sound, result.skipped)
|
||||
```
|
||||
|
||||
## Theme and renderer
|
||||
|
||||
Use semantic theme tokens with OpenTUI elements and pass `context.renderer` to renderer-specific helpers.
|
||||
|
||||
```tsx
|
||||
const Status = () => <text fg={context.theme.text.default}>Ready</text>
|
||||
const renderer = context.renderer
|
||||
```
|
||||
|
||||
## Solid components
|
||||
|
||||
Use `usePlugin` to access the current context inside JSX rendered by a route, dialog, or slot.
|
||||
|
||||
```tsx
|
||||
import { usePlugin } from "@opencode-ai/plugin/tui"
|
||||
|
||||
function Status() {
|
||||
const context = usePlugin()
|
||||
return <text fg={context.theme.text.default}>{context.app.version}</text>
|
||||
}
|
||||
```
|
||||
|
||||
## Markdown
|
||||
|
||||
Register a fenced-code renderer by language; the returned function unregisters it.
|
||||
|
||||
```ts
|
||||
const unregister = context.markdown.registerCodeBlockRenderer(
|
||||
"acme",
|
||||
(_token, render) => render.defaultRender(),
|
||||
)
|
||||
return unregister
|
||||
```
|
||||
|
||||
## Commands and keymaps
|
||||
|
||||
Register palette, slash, and keyboard commands in a reactive keymap layer.
|
||||
|
||||
```ts
|
||||
context.keymap.layer(() => ({
|
||||
mode: "global",
|
||||
priority: 10,
|
||||
commands: [
|
||||
{
|
||||
id: "acme.status",
|
||||
title: "Show Acme status",
|
||||
group: "Acme",
|
||||
bind: "ctrl+g",
|
||||
palette: true,
|
||||
slash: { name: "acme", aliases: ["status"], arguments: true },
|
||||
enabled: () => true,
|
||||
suggested: true,
|
||||
run: async (input) => context.ui.toast.show({ message: input ?? "Ready" }),
|
||||
},
|
||||
],
|
||||
bindings: ["acme.status"],
|
||||
}))
|
||||
```
|
||||
|
||||
A layer may target one OpenTUI renderable and can return `false` from a command to continue keyboard dispatch.
|
||||
|
||||
```ts
|
||||
context.keymap.layer(() => ({
|
||||
target: () => panel,
|
||||
commands: [{ bind: "escape", run: (_input, event) => (event ? false : undefined) }],
|
||||
}))
|
||||
```
|
||||
|
||||
Dispatch commands, inspect shortcuts and command state, or push a temporary input mode.
|
||||
|
||||
```ts
|
||||
context.keymap.dispatch("acme.status", "verbose")
|
||||
const shortcuts = context.keymap.shortcuts("acme.status")
|
||||
const commands = context.keymap.commands()
|
||||
const pending = context.keymap.pending()
|
||||
const active = context.keymap.active()
|
||||
const currentMode = context.keymap.mode.current()
|
||||
const popMode = context.keymap.mode.push("acme-search")
|
||||
popMode()
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
Durable storage persists JSON across restarts and synchronizes across TUI instances.
|
||||
|
||||
```ts
|
||||
const [settings, updateSettings] = context.storage.store("settings", {
|
||||
initial: { compact: false },
|
||||
})
|
||||
await updateSettings((draft) => {
|
||||
draft.compact = true
|
||||
})
|
||||
```
|
||||
|
||||
Memory storage survives plugin reloads but is discarded when the TUI exits.
|
||||
|
||||
```ts
|
||||
const [state, updateState] = context.storage.memory("state", {
|
||||
initial: { count: 0 },
|
||||
})
|
||||
updateState((draft) => {
|
||||
draft.count++
|
||||
})
|
||||
```
|
||||
|
||||
## Dialogs and toasts
|
||||
|
||||
Use promise-based dialogs for alerts, confirmations, text input, and selection.
|
||||
|
||||
```ts
|
||||
await context.ui.dialog.alert({ title: "Acme", message: "Ready" })
|
||||
const confirmed = await context.ui.dialog.confirm({
|
||||
title: "Continue?",
|
||||
message: "Run the Acme action?",
|
||||
label: { confirm: "Run", cancel: "Cancel" },
|
||||
})
|
||||
const name = await context.ui.dialog.prompt({ title: "Name", placeholder: "release" })
|
||||
const mode = await context.ui.dialog.select({
|
||||
title: "Mode",
|
||||
current: "safe",
|
||||
options: [
|
||||
{ title: "Safe", value: "safe", description: "Ask before changes" },
|
||||
{ title: "Fast", value: "fast", disabled: false, category: "Advanced" },
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
Custom JSX dialogs can set their size and close themselves.
|
||||
|
||||
```tsx
|
||||
context.ui.dialog.set({ size: "large", centered: true })
|
||||
context.ui.dialog.show(() => <box><text>Acme</text></box>, () => console.log("closed"))
|
||||
context.ui.dialog.clear()
|
||||
```
|
||||
|
||||
Toasts support title, message, variant, and duration.
|
||||
|
||||
```ts
|
||||
context.ui.toast.show({
|
||||
title: "Acme",
|
||||
message: "Saved",
|
||||
variant: "success",
|
||||
duration: 3000,
|
||||
})
|
||||
```
|
||||
|
||||
## Routes and tabs
|
||||
|
||||
Register a JSX route, inspect the current route, and navigate to home, a session, or the plugin page.
|
||||
|
||||
```tsx
|
||||
const unregister = context.ui.router.register({
|
||||
name: "dashboard",
|
||||
render: ({ data }) => <text>{String(data?.title ?? "Acme")}</text>,
|
||||
})
|
||||
const current = context.ui.router.current()
|
||||
context.ui.router.navigate({ type: "plugin", name: "dashboard", data: { title: "Status" } })
|
||||
context.ui.router.navigate({ type: "session", sessionID })
|
||||
context.ui.router.navigate({ type: "home" })
|
||||
return unregister
|
||||
```
|
||||
|
||||
Tabs can be listed, opened, focused, and closed when session tabs are enabled.
|
||||
|
||||
```ts
|
||||
if (context.ui.tabs.enabled()) {
|
||||
context.ui.tabs.open(sessionID)
|
||||
const tabs = context.ui.tabs.list()
|
||||
context.ui.tabs.focus(sessionID)
|
||||
context.ui.tabs.close(sessionID)
|
||||
context.ui.tabs.close()
|
||||
}
|
||||
```
|
||||
|
||||
## Slots
|
||||
|
||||
Slots insert or replace JSX at `app`, `home.footer`, `prompt.footer`, `prompt.footer.status`, `prompt.footer.file`,
|
||||
`session.composer.top`, `sidebar.content`, or `sidebar.footer`.
|
||||
|
||||
```tsx
|
||||
return context.ui.slot({
|
||||
append: "sidebar.content",
|
||||
render: ({ sessionID }) => <text>{context.data.session.get(sessionID)?.title}</text>,
|
||||
})
|
||||
```
|
||||
|
||||
Use `prepend`, `append`, `before`, `after`, or `replace` for placement.
|
||||
|
||||
```tsx
|
||||
context.ui.slot({ prepend: "home.footer", render: () => <text>Before footer content</text> })
|
||||
context.ui.slot({ append: "home.footer", render: () => <text>After footer content</text> })
|
||||
context.ui.slot({ before: "home.footer", render: () => <text>Before footer slot</text> })
|
||||
context.ui.slot({ after: "home.footer", render: () => <text>After footer slot</text> })
|
||||
context.ui.slot({ replace: "home.footer", render: () => <text>New footer</text> })
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
Format filesystem paths for display, including home-directory abbreviation.
|
||||
|
||||
```ts
|
||||
const displayPath = context.ui.format.path(context.location?.directory ?? "/home/me/project")
|
||||
```
|
||||
|
||||
## Publish and load
|
||||
|
||||
Expose the CLI plugin through `./tui`; add OpenTUI peers when the plugin renders JSX.
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"name": "opencode-acme-plugin",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./tui": "./src/tui.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "beta"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.5.8",
|
||||
"@opentui/solid": ">=0.5.8",
|
||||
"solid-js": ">=1.9.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Set `tui: true` on the [main plugin](/build/plugins/overview) for automatic loading.
|
||||
|
||||
```ts title="src/index.ts"
|
||||
import { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
export default Plugin.define({
|
||||
id: "acme.server",
|
||||
tui: true,
|
||||
setup() {},
|
||||
})
|
||||
```
|
||||
|
||||
Configure a CLI-only package in [`cli.json`](/cli/plugins) so it remains active against remote servers.
|
||||
|
||||
```json title="cli.json"
|
||||
{
|
||||
"plugins": ["opencode-acme-plugin"]
|
||||
}
|
||||
```
|
||||
1182
packages/www/src/docs/content/build/plugins/effect.mdx
Normal file
1182
packages/www/src/docs/content/build/plugins/effect.mdx
Normal file
File diff suppressed because it is too large
Load diff
1029
packages/www/src/docs/content/build/plugins/overview.mdx
Normal file
1029
packages/www/src/docs/content/build/plugins/overview.mdx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -70,7 +70,7 @@ await using opencode = await OpenCode.create({ plugins: [plugin] })
|
|||
|
||||
Call `await opencode.plugin(plugin)` to register another plugin after startup.
|
||||
|
||||
See the [Plugins guide](/build/plugins) for the plugin context and available
|
||||
See the [Plugins guide](/build/plugins/overview) for the plugin context and available
|
||||
hooks.
|
||||
|
||||
## Workerd
|
||||
|
|
|
|||
|
|
@ -2,11 +2,25 @@
|
|||
title: "Plugins"
|
||||
---
|
||||
|
||||
Add plugins to `cli.json`:
|
||||
Plugins configured in `opencode.json(c)` that expose a TUI component are loaded automatically by the CLI. To learn how
|
||||
to build plugins, see [Building plugins](/build/plugins/overview). You do not need to add the same package to `cli.json`. The CLI
|
||||
gets the active plugin list from the connected OpenCode server, so this also works when the server is remote.
|
||||
|
||||
Use `cli.json` for CLI-only plugins. These plugins run locally in the terminal and remain active when the CLI connects
|
||||
to a remote server:
|
||||
|
||||
```json title="cli.json"
|
||||
{
|
||||
"plugins": ["opencode.example", "./plugins/status.ts"]
|
||||
"plugins": [
|
||||
"opencode.example",
|
||||
"opencode.example@1.0.0",
|
||||
"@example/opencode-tui",
|
||||
"@example/opencode-tui@1.0.0",
|
||||
"./plugins/status.ts",
|
||||
"../plugins/status.ts",
|
||||
"/home/user/plugins/status.ts",
|
||||
"file:///home/user/plugins/status.ts"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -33,7 +47,10 @@ Pass plugin options with the object form:
|
|||
}
|
||||
```
|
||||
|
||||
`package` accepts a package name, an absolute path, a `file://` URL, or a path relative to `cli.json`.
|
||||
|
||||
OpenCode also discovers JavaScript and TypeScript plugins from `plugins/tui` under the global config directory and project
|
||||
`.opencode` directories.
|
||||
|
||||
```text title="Plugin discovery paths"
|
||||
<global-config>/plugins/tui/status.ts
|
||||
<project>/.opencode/plugins/tui/status.ts
|
||||
```
|
||||
|
|
|
|||
|
|
@ -434,7 +434,7 @@ accepts options.
|
|||
}
|
||||
```
|
||||
|
||||
See the [plugins guide](/build/plugins) for plugin development and configuration.
|
||||
See the [plugins guide](/plugins) for plugin loading and configuration.
|
||||
|
||||
### Providers
|
||||
|
||||
|
|
|
|||
|
|
@ -44,5 +44,6 @@ plan that grants you access to the best open source models.
|
|||
|
||||
## Customize
|
||||
|
||||
Make OpenCode your own by editing the [OpenCode config](/config), [connecting MCP servers](/mcp-servers), or [creating
|
||||
commands](/commands). For terminal interface themes and keybindings, see [CLI configuration](/cli/config).
|
||||
Make OpenCode your own by editing the [OpenCode config](/config), [loading plugins](/plugins), [connecting MCP
|
||||
servers](/mcp-servers), or [creating commands](/commands). For terminal interface themes and keybindings, see [CLI
|
||||
configuration](/cli/config).
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ plugin API is still being finalized during beta, and detailed plugin migration g
|
|||
ready.
|
||||
|
||||
Once the V2 plugin API is finalized, OpenCode should be able to migrate the majority of V1 plugins while keeping related
|
||||
local modules and dependencies together. See the current beta [Plugins guide](/build/plugins).
|
||||
local modules and dependencies together. See the current beta [Plugins guide](/build/plugins/overview).
|
||||
|
||||
## Server API and clients
|
||||
|
||||
|
|
|
|||
107
packages/www/src/docs/content/plugins.mdx
Normal file
107
packages/www/src/docs/content/plugins.mdx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
title: "Plugins"
|
||||
---
|
||||
|
||||
Load published packages, versioned packages, scoped packages, local files, or configured plugins from `opencode.json(c)`.
|
||||
|
||||
```jsonc title="opencode.jsonc"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugins": [
|
||||
"opencode-acme-plugin",
|
||||
"opencode-acme-plugin@1.2.0",
|
||||
"@acme/opencode-plugin",
|
||||
"./plugins/local.ts",
|
||||
"../shared/plugin.ts",
|
||||
"/absolute/path/plugin.ts",
|
||||
"file:///home/me/plugins/local.ts",
|
||||
{
|
||||
"package": "@acme/opencode-plugin",
|
||||
"options": {
|
||||
"agent": "reviewer",
|
||||
"strict": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Relative paths resolve from the config file containing the entry. Plugin arrays from applicable config files are applied
|
||||
from lowest to highest precedence instead of replacing one another.
|
||||
|
||||
```text
|
||||
~/.config/opencode/opencode.jsonc
|
||||
./opencode.jsonc
|
||||
./.opencode/opencode.jsonc
|
||||
```
|
||||
|
||||
OpenCode also loads direct `.ts` and `.js` files and immediate plugin package directories from every discovered
|
||||
`.opencode/plugins/` directory.
|
||||
|
||||
```text
|
||||
.opencode/
|
||||
└── plugins/
|
||||
├── concise.ts
|
||||
├── reviewer.js
|
||||
└── acme-package/
|
||||
```
|
||||
|
||||
Global plugins use the same discovery layout under the OpenCode config directory.
|
||||
|
||||
```text
|
||||
~/.config/opencode/plugins/
|
||||
```
|
||||
|
||||
A `plugins/` directory beside a project-root `opencode.json(c)` is not discovered automatically; configure its files
|
||||
explicitly or move it under `.opencode/`.
|
||||
|
||||
```jsonc title="opencode.jsonc"
|
||||
{
|
||||
"plugins": ["./plugins/local.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
Plugin entries are processed in order. Prefix an ID or wildcard with `-` to disable it, use `*` for every plugin, and
|
||||
use `.*` to match an ID prefix. A later ID re-enables a plugin.
|
||||
|
||||
```jsonc title="opencode.jsonc"
|
||||
{
|
||||
"plugins": ["*", "-opencode.provider.*", "opencode.provider.openai", "-acme.reviewer"]
|
||||
}
|
||||
```
|
||||
|
||||
Install, inspect, list, or remove global package plugins with the CLI.
|
||||
|
||||
```sh
|
||||
opencode2 plugin add opencode-acme-plugin@1.2.0
|
||||
opencode2 plugin list
|
||||
opencode2 plugin list --builtin
|
||||
opencode2 plugin remove opencode-acme-plugin@1.2.0
|
||||
```
|
||||
|
||||
Package installation accepts npm names with versions, tags, or ranges. Configure local paths directly instead of using
|
||||
Git, tarball, or npm alias targets with `plugin add`.
|
||||
|
||||
```sh
|
||||
opencode2 plugin add @acme/opencode-plugin@beta
|
||||
```
|
||||
|
||||
Changes under watched config directories reload automatically. Restart OpenCode after changing an installed package
|
||||
version or an unwatched dependency.
|
||||
|
||||
```sh
|
||||
touch .opencode/plugins/concise.ts
|
||||
opencode2 service restart
|
||||
```
|
||||
|
||||
CLI-only plugins are configured separately and remain active when connected to a remote server.
|
||||
|
||||
```json title="cli.json"
|
||||
{
|
||||
"plugins": ["opencode-acme-cli"]
|
||||
}
|
||||
```
|
||||
|
||||
<Card title="Build a plugin" href="/build/plugins/overview">
|
||||
Create plugins that add tools, hooks, integrations, commands, agents, and other behavior.
|
||||
</Card>
|
||||
|
|
@ -36,6 +36,7 @@ export const docsSections: DocsSection[] = [
|
|||
{ title: "Skills", slug: "skills" },
|
||||
{ title: "Themes", slug: "themes" },
|
||||
{ title: "Commands", slug: "commands" },
|
||||
{ title: "Plugins", slug: "plugins" },
|
||||
{ title: "Providers", slug: "providers" },
|
||||
{ title: "Snapshots", slug: "snapshots" },
|
||||
{ title: "Compaction", slug: "compaction" },
|
||||
|
|
@ -63,7 +64,6 @@ export const docsSections: DocsSection[] = [
|
|||
landingSlug: "cli",
|
||||
groups: [
|
||||
{
|
||||
title: "Intro",
|
||||
items: [
|
||||
{ title: "Intro", slug: "cli" },
|
||||
{ title: "Config", slug: "cli/config" },
|
||||
|
|
@ -87,10 +87,19 @@ export const docsSections: DocsSection[] = [
|
|||
title: "Build",
|
||||
landingSlug: "build",
|
||||
groups: [
|
||||
{
|
||||
items: [{ title: "Build", slug: "build" }],
|
||||
},
|
||||
{
|
||||
title: "Plugins",
|
||||
items: [
|
||||
{ title: "Overview", slug: "build/plugins/overview" },
|
||||
{ title: "Effect", slug: "build/plugins/effect" },
|
||||
{ title: "CLI", slug: "build/plugins/cli" },
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{ title: "Build", slug: "build" },
|
||||
{ title: "Plugins", slug: "build/plugins" },
|
||||
{ title: "Client", slug: "build/client" },
|
||||
{ title: "SDK", slug: "build/sdk" },
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue