# Publishing

The registry at workbenches.dev stores immutable Workbench versions per organization. Every organization Workbench is internal until it is published: members and organization keys see it, nobody else does, and anyone else gets a plain 404. Publishing is the one explicit act that makes a stored version public, and it always goes through review.

A public GitHub repository with packages under `.workbenches/<name>/` is already installable without the registry, with `wb add https://github.com/owner/repo --name <name>`. Use the registry for versions, internal sharing, review, and a stable `org/name`.

## Before publishing

1. The package passes `wb validate` and `wb smoke` on every declared runtime, and has been trialed on real tasks.
2. The manifest `version` is higher than the last stored version. Bump it for any content change.
3. The package directory holds only what should ship. `wb push` and `wb publish` upload the whole directory with no ignore rules: make sure no `.env`, credential file, private notes, or build output sits inside it. Limits: 256 files, 10 MiB total, 2 MiB per file, no symlinks.
4. For public versions: nothing private. No internal project names, customer code, or copied proprietary text.

## Sign in

```sh
wb login                 # browser approval; the first login becomes the default
wb login --org acme      # connect acme and make it the default
wb org list              # held organizations, default and expiry
wb org use acme          # change the default
wb whoami                # default organization, user, scopes, key expiry
wb logout --org acme     # revoke that key and forget it
```

`wb login` runs a browser approval that connects exactly one organization and stores a key for it in `~/.workbench/credentials.json` (mode `0600`). Every account has a personal organization. `--org` must match the organization approved in the browser, or nothing is stored. `wb login --no-browser` does not open the browser. Keys expire; `wb login --org <org>` refreshes one. `wb logout` without a flag signs out of the default organization.

`wb push`, `wb publish`, `wb unpublish`, and `wb image push` use the default organization, or the one named with `--org <slug>`.

## Push: share with your organization

```sh
wb push .#core                 # stores acme/core@<version>, internal
wb push .#core --as core-tools # store under another registry name
wb push my-alias               # a saved alias works too
```

`wb push [source] [--org <slug>] [--as <name>]` uploads the package as a new immutable version. `source` is a local package reference (`.#name`, `/path#name`, default `.`) or a saved alias. The Workbench is created as internal when missing. Output:

```text
Pushed acme/ios-expert@1.2.0 (internal)
```

- Each push needs a manifest version greater than the latest stored one. Pushing the same version with identical bytes again changes nothing.
- A public Workbench takes new versions only through `wb publish`. The registry rejects a push to one with a conflict, and the CLI prints its message unchanged.
- Pushing a new Workbench past the plan's private Workbench cap is refused with `plan_limit`.

Teammates save it after their own `wb login --org acme`:

```sh
wb add acme/core
```

Saved internal Workbenches show as `internal` in `wb list --saved` and `wb view`.

## Publish: submit for public review

```sh
wb publish acme/core            # submit the latest stored version
wb publish .#core --org acme    # submit a local package directly
```

`wb publish <org/name | source> [--org <slug>] [--version <semver>]` submits for public review and prints the submission ID, status, and dashboard URL.

- With `org/name`, it submits the latest stored version. `--version` is accepted only when it names that latest version.
- With a local source, it submits the package directly, without storing an internal version first. This is how a public Workbench takes a new version.
- `approved` means public now. Anything else means it is waiting for review. A pending submission is not public.

Reviewers approve an exact digest, request changes with feedback, or reject with feedback. Approval publishes those bytes and labels the version `human_reviewed` or `trusted_publisher`. Rejection leaves the Workbench internal and the version stored. If a newer version is pushed while a review is open, approval fails and the newer version must be submitted. Follow submissions in the dashboard. Review validates manifests, referenced files, paths, and size; it does not execute submitted code or certify security.

For a new registry identity, the organization slug and the manifest's `name` form the published coordinate. The manifest `name` must be a URL-safe slug. A local alias or directory name does not choose the registry name.

### From GitHub

The dashboard's sources page connects a GitHub repository, lists its packages, and submits the versions you select. Repository pushes never publish automatically. Publishing files from a private repository requires acknowledging that they become publicly downloadable after approval.

### Cloud runs

Any Workbench can be published, including a local-only one. Cloud runs and the hosted MCP server offer only sandboxed Workbenches: those whose runtimes include `docker` or `e2b` with an image, or `daytona`. See [Running on workbenches.dev](/docs/cloud/runs).

## Unpublish

```sh
wb unpublish acme/core
```

`wb unpublish <org/name> [--org <slug>]` flips a public Workbench back to internal at once and withdraws its open submissions. `--org`, when given, must match the reference. Copies already downloaded stay with their clients.

## Publish an image

`daytona` needs a published image. `e2b` can build from the package's Dockerfile, but a published image avoids a template build on each new tag, and `docker` consumers benefit from one instead of building locally.

```sh
wb login --org acme
docker build --platform linux/amd64 -t core-local .workbenches/core
wb image push core-local --org acme --as core --tag 1.2.0
```

That publishes `images.workbenches.dev/acme/core:1.2.0`. Reference it from the manifest:

```yaml
runtimes:
    docker:
        image: images.workbenches.dev/acme/core:1.2.0
    daytona:
        class: linux
        image: images.workbenches.dev/acme/core:1.2.0
```

`wb image push <image> --as <name> [--org <slug>] [--tag <tag>] [--client <executable>]` exports the local image, skips blobs already in the registry, uploads missing blobs in bounded chunks, and publishes the original OCI manifest. The source image can have any local name. Progress goes to stderr; on success a terminal shows `Pushed image · <ref>`. `--client` selects an OCI client that supports the Docker `image save` interface.

- Use a versioned tag, never `latest`. Never overwrite a tag a released version references. Bump the image tag and the Workbench version together.
- Sandbox providers run `linux/amd64`. Build with `--platform linux/amd64` on Apple silicon.
- Prefer `wb image push` over a direct `docker push`, which is subject to the registry's per-request body limit.

Standard OCI clients can log in too:

```sh
wb image login --org acme
docker tag core-local images.workbenches.dev/acme/core:1.2.0
docker push images.workbenches.dev/acme/core:1.2.0
```

`wb image login` takes `--org` and `--client` (default `docker`).

Images are private until a public Workbench references them. A repository is pullable anonymously only while at least one public, approved version in that organization names an image in it. Otherwise a pull needs a key of the owning organization. See [Images](/docs/api/images).

## Errors

| Error                                                                                | Fix                                                                                          |
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `Sign in first with wb login`                                                        | Run `wb login`.                                                                              |
| `Not signed in to organization X. Held: a, b. Run wb login --org X`                  | Connect that organization.                                                                   |
| `Your Workbench CLI login for X has expired. Run wb login --org X again.`            | Refresh the login.                                                                           |
| `--publisher was replaced by --org. Use --org <slug>`                                | Use `--org`.                                                                                 |
| `Workbench package exceeds 256 files`, `exceeds 10485760 bytes`, `file is too large` | Remove build output, assets, and anything not needed at run time from the package directory. |
| `Only the latest version can be published for now`                                   | Publish the local package directly: `wb publish .#<name> --org <org>`.                       |
| A version conflict                                                                   | Bump `version` in `workbench.yml` and push or publish again.                                 |
| A push to a public Workbench is refused                                              | Public Workbenches take new versions only through `wb publish`.                              |
| A 404 for a publisher you hold no key for                                            | `wb login --org <publisher>`.                                                                |
