standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
# Getting started

This page takes a UI plugin and a companion daemon from nothing to a packed
bundle. Every source file it refers to is a compiled example in the SDK's
`examples/` directory; the scaffolds `standard-plugin new` writes are those
examples with your plugin's id.

## Tools

- Rust stable with the `wasm32-wasip2` target:
  `rustup target add wasm32-wasip2`.
- The CLI: `cargo install standard-plugin-cli --locked`. It installs
  `standard-plugin` and `cargo-standard-plugin`, so
  `cargo standard-plugin ...` works too.
- The SDK: every scaffold depends on `standard-plugin-sdk = "0.1"`; a
  daemon that uses WASI files, sockets or HTTP writes
  `{ version = "0.1", features = ["wasi"] }`
  ([daemon plugins]daemon-plugins.md).

Install the CLI from the same release line as the SDK a plugin depends
on, so its WIT package matches the SDK's. Never commit a `Cargo.lock`
resolved with a local path patch
([agent guide](agent-guide.md#14-lockfiles-and-a-local-sdk-patch)).
- Optional: [`jco`]https://github.com/bytecodealliance/jco
  (`npm install -g @bytecodealliance/jco`) to inspect the component's
  JavaScript form. Viewers do not run it. Without it `build` says it
  skipped that step.

`standard-plugin new --sdk-path <dir>` points a scaffold at a local SDK
checkout instead of the published crate.

## A UI plugin

```sh
standard-plugin new my-card --kind ui
cd my-card
```

The scaffold is one crate:

| File | What it is |
| --- | --- |
| `Cargo.toml` | A `cdylib` depending on `standard-plugin-sdk`, with size-tuned release settings |
| `src/lib.rs` | The plugin: [`examples/clock_card.rs`]../examples/clock_card.rs |
| `bundle/standard-plugin.json` | The [manifest]manifest.md: one `sidebar.card` surface in the cell model, no grants |
| `.gitignore` | Ignores `target/` and what `build` writes into `bundle/` |
| `AGENTS.md`, `CLAUDE.md` | Where a coding agent finds these guides for the SDK version in use |

Read `src/lib.rs` top to bottom; it is short:

- The crate is `#![no_std]`. A UI plugin runs inside every viewer with no
  WASI, and `std` would import it; `alloc` (`String`, `Vec`, `format!`) is
  available through `standard_plugin::prelude`.
- `#[standard_plugin::ui]` on the type exports it as the component's
  `ui-plugin` world. The type implements `UiPlugin`: `activate` creates the
  `Surface<Cells>` named in the manifest, `frame` paints and commits, and
  `event` hears about resizes.
- `frame` repaints only when the second changes and asks for its next
  frame at the next second with `frame.wake_at(...)`. The viewer calls
  `frame` only when a frame is due *and* the card is visible, so the card
  costs nothing while hidden. See [rendering]rendering.md.

Build, check and pack:

```sh
standard-plugin build   # cargo build --release --target wasm32-wasip2, into bundle/, then check
standard-plugin check   # again, without building
standard-plugin pack    # target/standard-plugin/my-card-0.1.0.tar and its sha256
standard-plugin install # the same, then `standard plugin install` on your account
```

`check` parses the manifest with the same code the viewer's host uses,
checks every grant has a reason, and checks the component imports nothing
outside the `ui-plugin` world; see [grants](grants.md) for its warnings.

`install` uploads the package to your account's plugin store, lists its
grants with their reasons and asks before it installs; every viewer signed
in to the account then runs the card ([publishing](publishing.md)). To try
a build in one development viewer without installing it:

```sh
STANDARD_PLUGIN_DIRS=$PWD/bundle standard
```

To test it without a viewer, drive it with `standard_plugin::testing` from
an ordinary `#[test]`; [`tests/testing_harness.rs`](../tests/testing_harness.rs)
is a complete example.

## A companion daemon

A UI plugin is side-effect free: it runs once per open viewer, so anything
it did would happen once per viewer. Work that must happen once belongs to
a *companion*: a daemon plugin with the same id that the UI half asks
through calls and that publishes results the UI half reads. [Working
together](working-together.md) explains the pattern.

```sh
standard-plugin new builds --kind companion
cd builds
```

The scaffold is a Cargo workspace with two crates, one plugin id:

| Path | What it is |
| --- | --- |
| `ui/src/lib.rs` | The UI half: [`examples/together_ui.rs`]../examples/together_ui.rs |
| `ui/bundle/standard-plugin.json` | Kind `ui`, grant `call:builds` (with its reason) |
| `companion/src/lib.rs` | The daemon half: [`examples/together_companion.rs`]../examples/together_companion.rs |
| `companion/bundle/standard-plugin.json` | Kind `companion`, grant `process.exec:make` |

How the halves cooperate:

1. The companion publishes the build status as a durable value,
   `builds.status`, and announces each change with the event
   `builds.changed`. Both are in the plugin's own namespace, so neither
   needs a grant.
2. The UI half reads `builds.status` in `activate`, listens to
   `builds.*`, and repaints its card when `builds.changed` arrives.
3. A click on the card is an effect, so the UI half acts only when
   `view::is_driving()` says the user is controlling this viewer, and then
   inside `claims().once(...)` so exactly one request goes out. It calls
   the companion's `rebuild` method (`calls().call(..., Target::Singleton)`,
   grant `call:builds`).
4. The companion's `call` handler answers at once and spawns the build as
   a task on its executor, which runs when the host next drives the
   plugin: it runs `make` (grant `process.exec:make`) and publishes the
   new status, which step 2 shows in every viewer.

Build, check and pack from the workspace root; the CLI finds both halves:

```sh
standard-plugin build
standard-plugin pack
```

Both halves build, check, pack and install as one package, and both run
under `standard_plugin::testing`. Once installed, `standardd` runs the
companion on the machines its placement names
([daemon plugins](daemon-plugins.md#where-it-runs)), and the UI half's
calls reach it there.

## Next

- [The manifest]manifest.md and [grants]grants.md: what a plugin asks for.
- [Rendering]rendering.md: cells, pixels, the shared buffer, the helpers.
- [Working together]working-together.md: values, events, calls, live;
  many viewers; companions.