# Publishing to the registry

Community plugins ship through the s2script registry on this site: one identity, one version, dual artifacts (`.s2sp` for operators + types tarball for authors). Publish is open; review is additive — new versions start **unreviewed** with a public disclaimer.

The CLI is **`s2s`**, shipped in **`@s2script/sdk`**. Use `s2s …` when the package is installed, or `npx @s2script/sdk …` otherwise.

## Prerequisites

1. Create an account at [/signup](https://s2script.com/signup)
2. Claim a scope under [Organizations](https://s2script.com/account/orgs) (e.g. `@acme`) — one scope per org
3. Authorize the CLI ([CLI login](https://s2script.com/cli/authorize)) or mint a [deploy token](https://s2script.com/account/tokens)

Unscoped names (`rtv`) are owned by the first user who deploys them — no org required.

## Authenticate

### Local (device login)

```bash
s2s login
# or: npx @s2script/sdk login
```

The CLI calls `POST /api/v1/auth/device/start`, prints a short code, and opens [/cli/authorize](https://s2script.com/cli/authorize). Approve in the browser; the CLI's next `POST /api/v1/auth/device/poll` returns a `s2s_…` deploy token, which it stores in `~/.s2script/credentials.json`.

Paste path (headless boxes):

```bash
s2s login --token s2s_…
```

### CI

```yaml
env:
  S2SCRIPT_TOKEN: ${{ secrets.S2SCRIPT_TOKEN }}
run: npx @s2script/sdk deploy --ci
```

`--ci` never prompts; missing `S2SCRIPT_TOKEN` exits non-zero.

## package.json

```json
{
	"name": "@edge/heal-zones",
	"version": "1.0.0",
	"types": "api.d.ts",
	"s2script": {
		"apiVersion": "1.x",
		"publishes": { "@edge/heal-zones": "1.0.0" },
		"pluginDependencies": { "@s2script/cs2": "^1.0.0" }
	}
}
```

If `s2script.publishes` is set, `"types"` must point at an existing `.d.ts`. The CLI packs that file into a types-only tarball; the registry rejects uploads that omit it (`400 publishes_requires_types`). Runtime-only plugins omit `publishes` and skip types.

## Deploy

```bash
s2s deploy
```

1. Runs the publishes ⇒ types gate
2. Builds the `.s2sp`
3. Packs types when needed
4. Reads `README.md` beside package.json, if there is one (SDK 0.28.0 and later)
5. `POST /api/v1/deploy` with `Authorization: Bearer s2s_…`: one `application/octet-stream` zip holding `manifest.json`, `plugin.s2sp`, and optionally `types.tgz` and `README.md`. The manifest also carries package.json's `repository` field.

Success returns `{ name, version, reviewState: "unreviewed" }`. Manage versions, yank, descriptions and the README from the [publisher dashboard](https://s2script.com/account/packages).

## Package page

Each package has a public page at `/plugins/<name>`. Below the description, a plugin's page has two tabs: **README** and **Config**. Link straight to the Config tab with `/plugins/<name>?tab=config`.

**README.** Markdown that explains what the plugin does, its commands, permissions and setup. Keep it as `README.md` next to package.json: since SDK 0.28.0, `s2s deploy` sends it, and each deploy that carries one replaces the page's README. You can also write or edit it on the package's page in the [publisher dashboard](https://s2script.com/account/packages), which has a preview. A deploy without a README keeps the current one, so an edit made on the site stays until your next deploy that has one.

The README renders like these docs, with tables, highlighted code fences and blockquotes. Raw HTML shows as text, and links must be `https:`, `http:`, `mailto:` or on this site. Images must be `https:`. The limit is 100,000 characters.

**Config.** Built from the [`s2script.config`](https://s2script.com/docs/concepts/config) block in your package.json, as published in the newest non-yanked version. You don't write it separately. Each option gets a row with its key, type, default, range or allowed values, and description. Below the table are the block as declared in package.json and the config file the runtime writes on first load. Options with a `group` are listed under that heading. A `label` shows under the key.

**Source.** A link under the package name to where its code can be browsed. It comes from package.json's standard `repository` field, which `s2s deploy` sends since SDK 0.28.0. Any npm form works, including `{ "url": …, "directory": … }`, which links into a monorepo directory on GitHub, GitLab and Codeberg. You can also set it on the package's page in the [publisher dashboard](https://s2script.com/account/packages): an https URL, or `owner/repo` for GitHub. A deploy without `repository` keeps the current link.

> [!IMPORTANT]
> Mark secrets such as API keys and webhook URLs `"sensitive": true`. The page then hides their default. Any other default is public.

## Operator & author consumers

| Who              | Command                        | Artifact        |
| ---------------- | ------------------------------ | --------------- |
| Dependent author | `s2s add @edge/heal-zones@^1`  | types only      |
| Server operator  | `s2s install @edge/heal-zones` | `.s2sp` closure |

Types are also available via the npm-compatible surface at `/npm/*` (never serves `.s2sp`). `s2s add` for a plugin dependency downloads **types only** (registry metadata + types artifact), not the producer binary — operators still `s2s install` the `.s2sp` closure. Protocol 2 producers stamp host API **3.x**; see [inter-plugin interfaces](https://s2script.com/docs/concepts/interfaces#api-3).

## Yank

From the dashboard, or:

```bash
curl -X POST https://s2script.com/api/v1/yank \
  -H "Authorization: Bearer $S2SCRIPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"@edge/heal-zones","version":"1.0.0"}'
```

Yanked versions are hidden from default resolve; lockfile pins can still warn.

## Reserved

`@s2script/*` is first-party only. Community tokens cannot deploy into that scope.

## Next

- [Authoring plugins](https://s2script.com/docs/authoring) — scaffold, build, plugin shape
- [Inter-plugin interfaces](https://s2script.com/docs/concepts/interfaces) — `publishes` / producer-as-import
- [Plugin catalog](https://s2script.com/plugins) — public package pages + disclaimer
