packages/plugin-sdk/README.md
Rama main · réplica de lectura del repositorio original.
# @multica/plugin-sdk
What a Multica plugin surface imports.
```js
import { multica } from "@multica/plugin-sdk";
const ctx = await multica.context.get();
const issue = await multica.issue.get();
await multica.issue.comment({ body: "hello" });
const note = await multica.storage.user.get("note");
multica.ui.resize(320);
```
## What a surface is
**One** script in a sandboxed iframe.
Bundle this SDK and everything else your surface needs into a single file. There
is no module graph: you publish an artifact, Multica stores it and serves your
entry inside a generated document on its dedicated plugin-content origin. There
is no path back to the author's server and no top-level module graph for a bare
`import` to resolve against. Publishing refuses an entry that has one rather
than letting it fail later in a reader's browser.
The frame is mounted with `sandbox="allow-scripts"` and **not**
`allow-same-origin`, so it has an opaque origin. Consequences worth knowing
before you write one:
- **No browser storage.** `localStorage`, `sessionStorage` and cookies all throw
or are empty. Use `multica.storage` — it is server-side, scoped per workspace
or per member, and survives the frame.
- **`Origin: null` on your own requests.** If your surface calls your backend
directly, that backend must accept a null origin in CORS.
- **A CSP you did not write.** Multica generates the response and derives
`connect-src` from the `net:` scopes in your manifest. Declare every host you
intend to reach; with no `net:` scope your surface cannot issue a network
request at all, including back to your own origin, which is no longer in the
policy now that Multica serves your code. `net:` is an exact host, so declare
`net:api.example.com` separately from `net:example.com`.
## Publishing
Zip the manifest with every file it names and upload it in **Settings →
Plugins**. You need no server of your own for the frontend; hook endpoints and
MCP servers are still yours to run.
A published version is immutable. Installing binds a workspace to one version,
and publishing a new one changes nothing there until an administrator upgrades —
so what they approved on the consent screen is what their browsers run.
## What you can do, and what bounds it
Every call becomes a message to the host, which performs it on the signed-in
user's own session. Two limits apply at once:
1. the scopes the workspace admin granted your plugin, and
2. what that particular user could already do themselves.
So a member without access to an issue gets a 404 through your surface too, and
a scope the admin declined is a 403 that names it. Errors are
`MulticaPluginError` with a `status` mirroring HTTP.
A comment you post is authored by **the user**, recorded as having been made
through your plugin. It does not run `@mention` trigger dispatch — a surface
cannot start agent runs as a side effect of posting text.
## Theme
The host pushes design tokens over the private port and again on every theme switch, and
the SDK writes them as custom properties on `:root`. Use `var(--foreground)`,
`var(--background)`, `var(--border)`, `var(--radius)` and friends and your
surface will look native without shipping a stylesheet.
`multica.ui.onThemeChange(fn)` if you need to react in JS.
## Sizing
The frame does not auto-size. Call `multica.ui.resize(px)` after your content
settles; the host clamps the value.