@smart-tools/plugin-sdk
v1.0.2
Published
The contract between the app and a plugin: the `plugin.json` schema, the types a plugin author writes against, the `useSignal` hook, the build that emits a loadable plugin folder, and fake `WebCtx` / `ServerCtx` for a plugin's own tests.
Readme
@smart-tools/plugin-sdk
The contract between the app and a plugin: the plugin.json schema, the types a plugin author
writes against, the useSignal hook, the build that emits a loadable plugin folder, and fake
WebCtx / ServerCtx for a plugin's own tests.
A plugin is a FOLDER — plugin.json plus up to a server entry, a web entry and a stylesheet —
dropped into ~/.ru-code/plugins/<id>/ or shipped inside the app's release payload. It resolves no
bare specifier on its own, so this package is how the two sides agree on what crosses.
The seams
What a plugin may export, and the one thing that runs the other way.
| seam | half | what it contributes |
| --------------------------- | ------ | ---------------------------------------------------------------------------------- |
| pages | web | a whole page on its own route, with a sidebar entry |
| panels | web | the right-hand slot, or a tab of the thread panel |
| composer.items | web | rows in the /, $ and # menus |
| background | web | an always-mounted, invisible component |
| activate / deactivate | both | lifecycle, both optional |
| rpc | server | the methods your web half calls with ctx.invoke |
| migrations | server | schema steps for your own SQLite, applied once, in order |
| session | server | take part in the provider session lifecycle (fingerprint / provision) |
| ctx.publish / ctx.state | both | the one SERVER→WEB path: a live value, current in every tab that loaded the plugin |
The last row is not an export — it is a pair of ctx members — and it is here because it is the only
path that starts on the server: ctx.publish("name", value) on one side, ctx.state("name") — a
Signal — on the other. The host keeps the last value, forwards only a change, and hands a tab that
opens or reconnects the current one; the plugin writes no loop, no re-read and no timer. Publish the
summary, fetch the detail with your own rpc: see docs/authoring.md §7.
Install
pnpm add -D @smart-tools/plugin-sdk @smart-tools/plugin-devThe SDK is a peer-light dependency (react, effect); @smart-tools/plugin-dev carries the dev
loop — the playground and the Playwright fixture — and the app never installs it.
Entry points
| import | what it is |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| @smart-tools/plugin-sdk/host | every type you write against, plus defineWebPlugin / defineServerPlugin / isPluginRpcError. Zero runtime imports |
| @smart-tools/plugin-sdk/react | useSignal — the web half only |
| @smart-tools/plugin-sdk/build | pluginBuildConfig for your tsdown.config.ts |
| @smart-tools/plugin-sdk/testing | makeFakeWebCtx / makeFakeServerCtx |
| @smart-tools/plugin-sdk/contracts | the wire and manifest schemas — the HOST imports these |
Docs
docs/authoring.md— writing a plugin: the folder, the manifest, every seam with a worked example,ctx, the build, the dev loop, testing, caps and failure posture. Start here.docs/api.md— the contract reference: every export, every ctx member with its exact rule, every error reason, the host's RPC table, every cap and budget with its value.docs/architecture.md— the engine, for a developer maintaining or extending the host.
Copy plugin-demo to start — it exports every seam exactly once and is meant to be read.
Rule 42: a commit that changes a behaviour those docs describe changes the sentence in the same commit.
