# workers-cli

`workers` is the command-line tool for publishing workers to the [iii worker registry](https://workers.iii.dev).
Point it at a worker project and it uploads your pre-built artifacts, registers a new immutable version with its
README, metadata and skills, and makes it installable by anyone running iii.

```sh
workers login
workers publish --hosting registry --artifacts ./dist --skills ./skills
```

## Install

```sh
curl -fsSL https://workers.iii.dev/install/workers-cli/latest.sh | sh
```

The installer detects your OS and CPU architecture, downloads the matching archive, verifies its SHA-256 checksum
and places the `workers` binary in `~/.local/bin`. If that directory is not on your `PATH`, it tells you how to add
it.

- **Custom location:** set `INSTALL_DIR`, e.g. `curl -fsSL … | INSTALL_DIR="$HOME/bin" sh`.
- **Pin a version:** replace `latest` with a release tag (`next`) or an exact version, e.g.
  `https://workers.iii.dev/install/workers-cli/0.1.0.sh`.

Supported platforms:

| OS | Targets |
| --- | --- |
| macOS | `aarch64-apple-darwin` (Apple Silicon), `x86_64-apple-darwin` (Intel) |
| Linux | `x86_64-unknown-linux-musl` (static, any distribution), `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `armv7-unknown-linux-gnueabihf` |
| Windows | `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc` (run the installer from Git Bash, MSYS2 or Cygwin) |

Check the installation with `workers --version`. Run `workers --help` or `workers publish --help` for the built-in
reference.

### Build from source

With a stable Rust toolchain, from the CLI's source directory:

```sh
cargo install --path .    # installs `workers` into ~/.cargo/bin
```

## How it works

The registry does not store your files: it stores **where they live** and their SHA-256 digests. Every
`workers publish` run goes through the same steps:

1. **Reads your project.** `iii.worker.yaml` provides the worker's name, type and metadata; `README.md` becomes the
   worker's page; the version comes from `--version` or from your language manifest.
2. **Collects the artifacts** and computes the SHA-256 and size of each one locally, before anything leaves your
   machine.
3. **Uploads the artifacts** to the host chosen with `--hosting`: the registry's own CDN or a GitHub release in your
   repository.
4. **Publishes the version.** The registry records the name, version, release tag, README, metadata and, for each
   artifact, its URL and digest.
5. **Attaches your skills** (optional): Markdown documentation for that version, sent right after the version is
   published.

Once published, the worker appears on [workers.iii.dev](https://workers.iii.dev) and anyone can add it to their
project:

```sh
iii trigger compose::add worker=<name>
```

**Versions are immutable.** Re-running a publish is always safe: if the version already exists with identical
artifacts, the run is a no-op (`already published with identical contents`); if the artifacts differ, the CLI stops
and asks you to bump the version. A CI job that failed halfway can simply be retried.

## Authenticate

```sh
workers login
```

Prompts for your registry API key (input is hidden) and saves it to `~/.iii/workers-api-key`
(`%USERPROFILE%\.iii\workers-api-key` on Windows), with `0600` permissions on macOS and Linux.

In CI, skip `login` and set `WORKERS_API_KEY` instead; it takes precedence over the saved key. The key is only
needed to talk to the registry, so `--dry-run` works without it.

## Prepare your worker

A typical project looks like this:

```text
my-worker/
├── iii.worker.yaml     # worker manifest
├── Cargo.toml          # or package.json / pyproject.toml, provides the version
├── README.md           # shown on the worker's registry page
├── skills/             # optional Markdown skills
│   └── SKILL.md
└── dist/               # pre-built artifacts
```

### `iii.worker.yaml`

```yaml
iii: v1
name: bridge                 # worker name on the registry
language: rust
deploy: binary               # binary | bundle | executable
manifest: Cargo.toml         # file the version is read from
bin: bridge                  # artifact file name prefix (defaults to `name`)
description: Bridge two engines.
license: Apache-2.0
repo: https://github.com/acme/bridge
tags: [bridge, remote]
experimental: false
config:                      # default configuration, shown on the worker's page
  url: ws://0.0.0.0:49134
dependencies:                # other workers, by semver range or release tag
  configuration: "latest"
```

| Field | Required | Description |
| --- | --- | --- |
| `name` | yes | The worker's name on the registry. |
| `deploy` | yes | `binary`, `bundle` or `executable`; see [Artifacts](#artifacts). |
| `manifest` | no | Language manifest, next to `iii.worker.yaml`, that holds the version: `Cargo.toml`, `package.json` or `pyproject.toml`. |
| `bin` | no | Prefix of the binary artifact file names. Defaults to `name`. |
| `repo` | no | Link to the source repository. When omitted, it is derived from `--github-repo` if given. |
| `tags` | no | Search tags. When omitted, the tags already set on the registry are kept. |
| `description`, `license`, `config`, `dependencies`, `experimental` | no | Sent to the registry as-is. |

### Version

The version is resolved in this order:

1. `--version X.Y.Z`;
2. the file named by `manifest:`: the `version` field of a JSON file (`package.json`), or `[package].version` /
   `[project].version` of a TOML file (`Cargo.toml`, `pyproject.toml`).

If neither is available, `publish` stops and asks for `--version`.

### Artifacts

`--artifacts` is required, and what it points to depends on `deploy`:

| `deploy` | `--artifacts` points to | What gets published |
| --- | --- | --- |
| `binary` | a folder | One archive per platform, named `<bin>-<target-triple>.tar.gz` (or `.zip`, typically for Windows). Files that don't follow this pattern are ignored. |
| `bundle` | a `.tar.gz` file | A single archive for every platform, e.g. a Node.js or Python worker. |
| `executable` | a folder | Same rules as `binary`. For standalone programs, like this CLI, that users install with `curl -fsSL https://workers.iii.dev/install/<name>/latest.sh \| sh` instead of adding them to a project. |

Example `dist/` folder for a binary worker named `bridge`:

```text
dist/
├── bridge-aarch64-apple-darwin.tar.gz
├── bridge-x86_64-apple-darwin.tar.gz
├── bridge-x86_64-unknown-linux-gnu.tar.gz
└── bridge-x86_64-pc-windows-msvc.zip
```

### Skills

`--skills` points to a folder of Markdown files, read recursively (empty files are skipped). `SKILL.md` at the root
is the overview; every other file keeps its relative path under `skills/`:

| Local file | Published as |
| --- | --- |
| `skills/SKILL.md` | `SKILL.md` |
| `skills/guides/setup.md` | `skills/guides/setup.md` |

## Publish

### Choose where the artifacts are hosted

#### Registry hosting (`--hosting registry`)

```sh
workers publish --hosting registry --artifacts ./dist --skills ./skills
```

The files are uploaded straight to the registry and served from
`https://cdn.iii.cloud/workers/<name>/<version>/<asset>`. Only your API key is needed.

- The CLI opens a release for the version, uploads each artifact to a short-lived signed URL and then publishes.
  Transient upload failures are retried automatically, and an interrupted run resumes the same release the next time.
- Nothing becomes public until the publish succeeds: the registry first checks the presence, size and digest of every
  upload.
- Binary archives keep their `<bin>-<target-triple>` names; a bundle is always published as
  `<name>-<version>.tar.gz`, whatever its local file name.
- `--github-repo` is optional here; when given, it only fills the `repo` link if `iii.worker.yaml` has none.

#### GitHub hosting (`--hosting github`, the default)

```sh
export GITHUB_TOKEN=...   # token with `contents: write` on the repository
workers publish --artifacts ./dist --github-repo owner/repo
```

The CLI uploads the artifacts to the GitHub release `v<version>` of `--github-repo` (creating it if missing), then
registers the version with URLs that point at those release assets. It needs `GITHUB_TOKEN` or `GH_TOKEN`.

- An asset already on the release with the same digest is skipped; one with the same name but different content is
  an error.
- GitHub Enterprise is supported through `GITHUB_SERVER_URL` and `GITHUB_API_URL`.

### Release tags

By default, a version is published under the `latest` tag: it is **stable**, installed by default and matched by
semver ranges such as `^1.0.0`. Any other tag (`next`, `beta`, `rc`, …) publishes a **candidate**, which is only
installed when requested explicitly by tag or exact version; semver ranges ignore it.

```sh
workers publish --hosting registry --artifacts ./dist --tag next

# users opt in explicitly:
iii trigger compose::add worker=my-worker@next
```

### Preview with `--dry-run`

`--dry-run` prints the worker, version, hosting target, every artifact with its digest and size, and the exact JSON
payload that would be sent, without uploading or publishing anything and without an API key. Use it to check the
manifest, the version and the artifact names before spending a version number.

```sh
workers publish --hosting registry --artifacts ./dist --dry-run
```

### Flags

| Flag | Env | Default | Description |
| --- | --- | --- | --- |
| `--manifest <path>` | | `iii.worker.yaml` | Worker manifest. |
| `--readme <path>` | | `README.md` | Markdown shown on the worker's page. Must exist. |
| `--skills <dir>` | | | Folder of Markdown skills. |
| `--artifacts <path>` | | **required** | Folder (`binary`, `executable`) or `.tar.gz` file (`bundle`). |
| `--hosting <github\|registry>` | `WORKERS_HOSTING` | `github` | Where the artifacts are hosted. |
| `--github-repo <owner/repo>` | `GITHUB_REPOSITORY` | | Repository whose `v<version>` release hosts the artifacts. Required with `--hosting github`. |
| `--version <x.y.z>` | | read from `manifest:` | Version to publish. |
| `--tag <tag>` | | `latest` | Release tag. Anything other than `latest` publishes a candidate. |
| `--registry <url>` | `WORKERS_REGISTRY_URL` | `https://api.workers.iii.dev` | Registry API base URL. |
| `--dry-run` | | | Print the payload; upload and publish nothing. |

## GitHub Actions

GitHub Actions sets `GITHUB_REPOSITORY` for you, so `--github-repo` can be omitted:

```yaml
- name: Publish to the iii registry
  env:
    WORKERS_API_KEY: ${{ secrets.WORKERS_API_KEY }}
    GITHUB_TOKEN: ${{ github.token }}   # only needed for --hosting github
  run: |
    curl -fsSL https://workers.iii.dev/install/workers-cli/latest.sh | sh
    ~/.local/bin/workers publish --artifacts dist --skills skills
```

With GitHub hosting, the job also needs `permissions: contents: write` to create the release and upload its assets.

## Troubleshooting

| Message | Fix |
| --- | --- |
| ``no API key: run `workers login` or set WORKERS_API_KEY`` | Run `workers login` or export `WORKERS_API_KEY`. |
| `--hosting github needs --github-repo (or GITHUB_REPOSITORY)` | Pass `--github-repo owner/repo`, or use `--hosting registry`. |
| `set GITHUB_TOKEN (or GH_TOKEN) to upload artifacts to the GitHub release` | Export a token with `contents: write` on the repository. |
| ``no `<bin>-<target-triple>.tar.gz\|.zip` files in <dir>`` | The files in `--artifacts` don't follow the naming pattern, or `bin:` doesn't match their prefix. |
| `bundle workers need --artifacts to be the .tar.gz archive` | With `deploy: bundle`, point `--artifacts` at the archive, not at a folder. |
| ``deploy `x` is not supported (only binary, bundle and executable)`` | Fix `deploy:` in `iii.worker.yaml`. |
| ``iii.worker.yaml has no `manifest:` to read the version from; pass --version`` | Add `manifest:` to `iii.worker.yaml` or pass `--version`. |
| ``no string `version` in <file>; pass --version`` | Add a `version` to your language manifest or pass `--version`. |
| `… is already published with different contents; bump the version instead of overwriting` | Versions are immutable: bump the version and publish again. |
| `release vX already has a different <asset>; bump the version instead of overwriting` | Same as above, with GitHub hosting. |

## License

Apache-2.0
