15a1bbcee0
Repository sits at the OpenCode config root rather than inside skills/ or
plugins/. OpenCode scans exactly those two subdirectories, and a .git
directory inside either one is picked up as a plugin and fails to load:
loading plugin id=.../plugins/.git entrypoint=.../plugins/.git/index
failed to load plugin ... AggregateError: 2 errors building .../plugins/.git/index
The watcher.ignore config option does not help: it only suppresses filesystem
update events, not plugin discovery, and the "-" disable prefix only applies to
plugins listed in opencode.json(c), not auto-discovered ones.
.gitignore admits only skills/ and plugins/ (plus itself). Personal config
(opencode.jsonc, cli.json, service.json) and node_modules stay local.
skills/opencode-plugins V2-only plugin authoring reference and 7 templates,
transcribed from the V2 docs with the URL on every file
plugins/searxng-search searxng_search tool over a self-hosted SearXNG JSON API
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
// RPC implementation + calling — template
|
|
//
|
|
// Docs: https://opencode.ai/v2/docs/build/plugins/rpc/
|
|
//
|
|
// Two files. The contract is importable without loading the implementation, so
|
|
// other plugins and clients can depend on it.
|
|
//
|
|
// Needs templates/rpc.ts copied next to this file as src/rpc.ts, so the
|
|
// doc's "./rpc.js" specifier resolves. An Effect plugin would import the
|
|
// Effect-dialect contract instead — see rpc-effect.ts.
|
|
|
|
//
|
|
// The docs use "@opencode/plugin" and "@opencode/plugin/effect". Install the
|
|
// package at your OpenCode version and restart the service first — see the
|
|
// PREREQUISITE block in promise-plugin.ts.
|
|
import { Plugin } from "@opencode/plugin"
|
|
import { Acme } from "./rpc.js"
|
|
|
|
export default Plugin.define({
|
|
id: "acme-plugin",
|
|
|
|
async setup(ctx) {
|
|
const registration = await ctx.rpc.register(Acme, {
|
|
search: async (input, context) => {
|
|
// JSON Schema input is `unknown` — narrow it.
|
|
const { query } = input as { query: string }
|
|
|
|
// `signal` cancels with the request; `context.error` declares a failure.
|
|
const text = await findText(query, { signal: context.signal })
|
|
if (!text) return context.error("not_found", "Result not found", { query })
|
|
|
|
return { text }
|
|
},
|
|
})
|
|
|
|
// The same plugin can call its own RPC once registered.
|
|
const acme = ctx.rpc(Acme)
|
|
// Raw JSON Schema output arrives as `unknown` in the Promise flavor; the
|
|
// docs say to narrow it.
|
|
const result = (await acme.search({ query: "hello" })) as { text: string }
|
|
|
|
// Emit to subscribers.
|
|
await registration.events.emit("updated", { itemID: "item-1", text: result.text })
|
|
|
|
// ---------------------------------------------------------------- events
|
|
// Subscribe with the LOCAL name. The received `type` is prefixed
|
|
// ("rpc.acme.updated"). Every event carries `data` and `location`.
|
|
//
|
|
// Subscriptions are LIVE ONLY: a disconnected subscriber misses events.
|
|
// Plugin unload closes them.
|
|
//
|
|
// const unsubscribe = acme.events.on("updated", (event) => {
|
|
// console.log(event.type, event.location.directory, event.data.text)
|
|
// })
|
|
// unsubscribe()
|
|
//
|
|
// for await (const event of acme.events.subscribe("updated")) {
|
|
// console.log(event.data.text)
|
|
// }
|
|
|
|
return () => {
|
|
void registration.dispose()
|
|
}
|
|
},
|
|
})
|
|
|
|
// Calling from a client over HTTP:
|
|
// import { OpenCode } from "@opencode/client"
|
|
// import { Acme } from "opencode-acme-plugin/rpc"
|
|
// const client = OpenCode.make({ baseUrl: "http://localhost:4096" })
|
|
// const acme = client.rpc(Acme)
|
|
// const result = await acme.search({ query: "hello" })
|
|
|
|
// Calling from another plugin (a TUI plugin uses `context.client`):
|
|
// import { Plugin } from "@opencode/plugin/tui"
|
|
// const acme = context.client.rpc(Acme)
|
|
// const result = await acme.search({ query: "hello" })
|
|
|
|
declare function findText(query: string, options: { signal: AbortSignal }): Promise<string | undefined>
|