# Publishing

## The path from code to installs

```
Save draft  →  Publish x.y.z  →  (private: only your account)  →  List in Browse  →  anyone can install
                     ↑                                                    ↓
                  next version  ←───────────  installs on "Latest" follow (with consent for new access)
```

1. **Create** the plugin. **Plugins → Created → Upload a plugin** takes a `.zip`: the readme's `name` (and
   optional `slug`) creates it for you. **Write in the browser** asks for a name, an **address** (the slug) and a
   one-line description. The slug (lowercase letters, digits, hyphens and underscores, up to 64 characters, not
   starting with `agentparley-` or `mcp_`) is your plugin's permanent identity; tool names are prefixed with it.
2. **Draft** (optional): **Save** (⌘S) in the editor builds and validates exactly like a publish but takes no
   version number. Your saved draft reopens on any device. Switch your own install to it with **Use my draft** on the **Try it** tab to test on your account.
3. **Publish** a version: **Publish…** in the editor (choose patch, minor or major; the first is `1.0.0`), or
   upload a `.zip` — on the Created page, or **Working on your own computer?** on the plugin's page. A zip's readme
   `version:` decides the number (see Versions below). The editor only sends text files, so a plugin with
   models, images or several script files shows **Upload a .zip…** in place of **Publish…**. A plugin with a gallery can still publish from the editor, which warns first that the gallery will be removed.
4. **List in Browse** (in the **⋯** menu at the top of the plugin's page) when you're ready for everyone: requires at least one published version.

## What a publish does

In one step, all or nothing:

1. Checks every file fits the [package format](plugin-package-format.md) and requires a `readme.md`.
2. Bundles your code with esbuild into one readable script.
3. Runs it once to discover its capabilities and manifest; derives permissions from its `host.*` calls.
4. Validates the manifest, tools, commands, skills and companion assets.
5. Stores the version. A crash half-way leaves no half-visible version.

A readme-only package is refused too (*"version declares no capabilities"*): it needs at least one command, skill,
code capability or companion/portrait folder.

Any problem fails the publish with a message naming the file, key or line: *"tool 'Lookup' must match
^[a-z][a-z0-9_]{0,63}$"*, *"unknown globalThis.manifest key 'egres'"*, *"the code calls host.fetch but declares no
egress hosts"*, *"a decision provider must implement modelProvider.noul, choose and score together"*.

If the platform is busy bundling other plugins you may get a *retry shortly* response — just publish again.

## Versions

- A version is **`major.minor.patch`**, digits only (`1.0.0`, `1.4.12`). The first publish from the editor is `1.0.0`.
- **Versions are immutable.** Publishing the same number twice is refused. Fix forward with a new version.
- Installs on **Latest** follow the highest published version.

Use the numbers the usual way: patch for fixes, minor for additions, major for anything that breaks users — a
renamed setting, a removed tool.

The `version:` key in `readme.md` (`<major>.<minor>` only, default `1.0`) is used whenever you upload a zip to
`POST /plugins/upload` (the console's zip uploads) and by the official-plugin pipeline. The platform picks the patch
automatically: unchanged files publish nothing, changed files publish the next patch, a raised `major.minor`
publishes `<major>.<minor>.0`, and a lowered one is refused. When you publish from the editor's **Publish…** or
through `POST /plugins/{id}/versions` or `…/versions/zip`, you choose the number and this key is ignored.

## Visibility

| State | Who can find and install it |
| --- | --- |
| **Private** (default) | only your account — Browse shows it to you with a **Private** tag |
| **Listed in Browse** | every AgentParley account |

**Remove from Browse** takes it off the catalog: accounts that already installed it keep working on their version
and see the optional message you write ("Why you're taking it down"). You can list it again later.

## How installs follow your updates

Each install has a version policy:

| Policy | Behaviour |
| --- | --- |
| **Latest** (default) | moves to your newest version within about five minutes |
| **Pinned** | stays on one version until the user changes it |
| **Draft** | your own account only: follows your latest draft |

**New access waits for consent.** If a new version asks for anything the owner hasn't agreed to — a permission,
an egress host, an agent in `manifest.agents`, a schedule in `manifest.schedules`, a `failClosed` hook, or
`channelSenderPolicy: "anyone"` — installs stay on the old version and show **Update waiting for your
OK**, listing exactly what the new version adds. Nothing from the new version runs until they accept. A version
that asks for the same or less moves silently.

On every version change your plugin receives `events.pluginUpdated` — see
[Lifecycle events](plugin-lifecycle-events.md).

## Yanking

Pull a bad version with **yank** (on the version in the console, or the API). A yanked version stops being offered:
installs on **Latest** move to the highest remaining version they have already agreed to, and an install pinned to it
has no version to run until its owner picks another. You can't yank a plugin's last remaining published version.
A plugin that has ever published a version, even a yanked one, can't be deleted; only a plugin that never published
can. To take a published plugin out of sight, use **Remove from Browse**.

## Official plugins

Plugins with the **Official** badge — Brave Search, Caveman, DC Portraits, DevOps on-call, Discord, Google
Workspace, Inbox Triage, Microsoft 365, Morning Briefing, My Dude, Ponytail, Sales Lead Crew, Wiki Web and Yuzuki —
are published by AgentParley from its own repository as `agentparley-<name>`, through the same bundling, validation
and SDK described here. Community slugs can't start with `agentparley-`.

## Checklist before you list

- [ ] The readme explains setup, every setting, and what the plugin reaches.
- [ ] `npx tsc -p .` passes against the downloaded SDK types.
- [ ] You tested the published version on your own account, including a fresh install.
- [ ] Lifecycle handlers are idempotent; middleware is fast; tool descriptions say *when* to use the tool.
- [ ] You call `host.*` literally (no aliases), so the permission list is complete.
- [ ] Nothing secret is stored in live-connection `state` or logged with `host.log`.
