Skip to main content

Module publishing

Module publishing 

Source
Expand description

§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:

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). 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 when you install with standard plugin install. The token never leaves that machine. The plugin store reads a source with the installing viewer’s own access only.

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:

{
  "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.

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: 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

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; Plugin settings… in a viewer’s command palette pins it too (P, then a machine and Enter).

standard plugin install ./sync --machine studio

In a native viewer, Plugins in the command palette opens the plugin store (standard plugin store opens it in any terminal). A plugin’s page lists its permissions with their reasons, high risk first, and previews its surfaces; Install asks once, then installs. Install plugin… opens the store on “Install from a source”, which takes any GitHub, npm, link or file source the viewer’s machine can read. The browser viewer installs nothing; use 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 the switch on its page in the plugin store). Nothing polls. A viewer checks when it starts and when its plugin store 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 plugin’s page in the store, never as a popup.

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 Uninstall in the plugin store: 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 plugin store lists it under Updates, and its page shows the added grants and their reasons; you approve exactly the update’s grants or keep the installed version. A newer version a check found (with auto-update off) shows as available and installs from its page.