standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
# Publishing and installing

A plugin is installed on your account, not on a machine: every viewer
signed in to the account runs its UI half, and daemons run its daemon half.
The account keeps the package and records which version each plugin runs.

## The package

`standard-plugin pack` writes one deterministic tarball for a plugin or a
companion pair and prints its SHA-256, the package's identity on the
account:

```text
manifest.json                  the account manifest: id, version, entries, grants
ui/standard-plugin.json        the UI half's runtime manifest
ui/<module>.wasm               its component
ui/browser/...                 the jco build, when built; viewers ignore it
daemon/standard-plugin.json    the companion's runtime manifest
daemon/<module>.wasm
```

`manifest.json` names each half's component (`ui.entry`, `daemon.entry`)
and carries the grants of both halves with their reasons: the set the user
approves. `daemon.placement` (and `daemon.failover`) come from the daemon
half's `daemon` section ([manifest](manifest.md#where-a-daemon-runs)). The
same bundle always packs to the same bytes. A package is at most 8 MiB.

## Publishing

A plugin reaches users from one of four sources. Each is recorded on the
account with the plugin, so later versions are found and installed the
same way (see "Updates and approval" below).

### GitHub Releases

Attach the packed `.tar` to a GitHub Release:

- A repository with one plugin tags releases `v<version>` (or
  `<version>`) and attaches `<id>-<version>.tar`. Users install it with
  `owner/repo`.
- A repository with several plugins tags each release `<id>@<version>`
  (or `<id>-v<version>`) and attaches that plugin's `<id>-<version>.tar`.
  Users install one with `owner/repo#<id>`, or with `owner/repo`: the
  install lists the repository's plugins (each id's newest published
  version, and the first line of its release notes) and asks which one.
- Any asset named `*.standard-plugin.tar` is accepted too.

Drafts and pre-releases are skipped unless a user names the tag
(`owner/repo#<id>@<tag>`). When GitHub publishes an asset's SHA-256
(`digest`), the download must match it. Lookups use GitHub's REST API
without credentials; a `GITHUB_TOKEN` in the environment is sent to
`api.github.com` only (for private repositories or rate limits) and is
never stored.

A private repository installs too. When the installing viewer cannot read
it, one of the account's machines that is signed in to GitHub (a
`GITHUB_TOKEN` or `GH_TOKEN` in its environment, or the GitHub CLI) reads
it instead, and the install dialog names that machine while it looks. The
token never leaves that machine.

A repository can release with a workflow: a script tags `<id>@<version>`
from the plugin's manifest, and pushing the tag builds, packs and attaches
`<id>-<version>.tar`.

### npm

Publish an npm package that contains the packed `.tar` and names it in
`package.json`:

```json
{
  "name": "@standardagents/foobar",
  "version": "1.2.0",
  "files": ["dist/foobar-1.2.0.tar"],
  "standardPlugin": { "package": "dist/foobar-1.2.0.tar" }
}
```

`standardPlugin.package` is a path inside the package, relative to its
root. A `standardPlugin` without `package` is a runtime v1 plugin and is
refused. Installs resolve the `latest` dist-tag, or the dist-tag, version
or range the user names (`npm:@scope/name@^1.2`, `name@next`), and the
tarball must match its `dist.integrity` (`sha512`). The version's
publisher (`_npmUser`) is shown as the publisher.

### A link

Any `https://` link to a `.tar` (the packed plugin), a `.tgz`/`.tar.gz`
(a gzip of it, or an npm-style tarball as above), or a `.zip` holding the
packed `.tar` or a built plugin directory (packed at install). npm tarball
links (`https://registry.npmjs.org/…/-/x.tgz`) are links too. Updates
recheck the link with a conditional request, so an unchanged file is not
downloaded again.

### A local file

A built plugin directory or a packed `.tar` on the installing machine.
The account records its absolute path; there are no automatic updates.

### A file on another machine

The path can be on any machine of your account: pick the machine in the
install dialog, or run `standard plugin install <path> --from-machine
<machine>`. That machine reads the path, verifies the package and uploads
it to the account; only the package's summary (id, name, version, grants)
comes back, never the file. `~` is the home directory of that machine's
user, and a relative path is refused. Only a built plugin directory or a
packed `.tar` is read. It has no automatic updates;
`standard plugin update <id>` asks that machine to read the path again (it
must be online).

### Limits

Downloads are https only (redirects included), at most 32 MiB and 90
seconds. Archives unpack to at most 64 MiB and 4,096 files; an entry with
an absolute path or `..` refuses the archive, and links are skipped, so
nothing lands outside it. Every source ends in the same verification as a
local package: the manifest, the package's SHA-256, and its grants.

## Installing

```sh
standard plugin install acme/standard-plugins#weather
standard plugin install @acme/weather
standard plugin install npm:@acme/weather@^1.2
standard plugin install https://example.com/weather-1.2.0.tar
standard plugin install ./my-card            # a built plugin directory
standard plugin install my-card-0.1.0.tar
standard plugin install ~/plugins/my-card.tar --from-machine studio
standard plugin install acme/standard-plugins        # lists them, asks which
standard plugin install acme/standard-plugins#weather --from-machine studio
standard-plugin install                      # check, pack, then `standard plugin install .`
```

A GitHub repository of several plugins without `#<id>` lists its plugins
and asks for a number; with `--yes` or without a terminal it refuses and
names each `owner/repo#<id>` to rerun with. `--from-machine` also takes a
GitHub source: only that machine is asked to resolve it.

`standard plugin install` resolves the source, lists every grant with its
reason (high-risk grants first: `machine.full`, `network.full`, every
`process.exec:` and `fetch:`), asks y/N (`--yes` approves without asking),
uploads the package, and installs it with its source. When the account
already has a plugin with that id it updates it, approves the grants you
just saw, and records the new source. Without a terminal to ask and
without `--yes`, it lists the grants and stops, saying so; review them and
rerun with `--yes`.

A singleton daemon runs on one machine, which the install pins:
`--machine <id or name>` names it; without it the command uses the
account's only machine or asks which one (`--yes` with several machines
refuses and asks for `--machine`). Installing an installed singleton again
with `--machine` moves it there; the Plugins view pins it too (P, then a
machine and Enter).

```sh
standard plugin install ./sync --machine studio
```

In a viewer, **Install plugin…** in the command palette (or I in the
Plugins view) runs the same install in one card: the source (with a
machine picker and a file browser for paths), then the consent screen,
which lists the plugin's permissions with their reasons, high risk first,
then the install, which ends by saying where the plugin appears. The
browser viewer installs from a file on one of the account's machines;
GitHub, npm and link sources install from a native viewer or the CLI.

## Updates and approval

A plugin installed from GitHub, npm or a link updates from it
automatically unless its auto-update is off (`standard plugin source <id>
--auto-update off`, or U in the Plugins view). Nothing polls. A viewer
checks when it starts and when its Plugins view opens, at most once an
hour per plugin, and of several viewers starting together one checks and
installs. `standard plugin update [id] [--all]` checks now, whatever the
hour or the switch. What the source said, or why it failed, is shown in
the Plugins view under the plugin, never as a popup.

```sh
standard plugin update                 # every plugin with a source
standard plugin update quota-meters
standard plugin source quota-meters    # where it updates from and the last check
standard plugin source quota-meters --auto-update off
standard plugin uninstall quota-meters # asks first; --yes to skip the question
```

`standard plugin uninstall <id>` removes the plugin from the account, the
same as X in the Plugins view: it stops in every viewer and on every
machine. It asks for a confirmation at the terminal; with no terminal to
ask on (a script, a pipe) it refuses unless `--yes` is given.

An update that keeps the approved grants, or drops some, runs at once:
every viewer starts the new version beside the old one and swaps to it
once it has activated. An update that adds a grant waits for approval: the
Plugins view (command palette, `Plugins`) shows a card with the added
grants and their reasons; Enter approves exactly the update's grants, D
keeps the installed version. A newer version a check found (with
auto-update off) shows as available; Enter installs it through the install
dialog.