# Publish a browser game to Rosebud: instructions for coding agents

Use these steps when a person asks you to put their browser game online with Rosebud's free game hosting. Run them in order. Every `rosebud` command except `--help` and `--version` prints one JSON object: results on stdout, errors on stderr.

Human-readable guide: [publishing guide](https://rosebud.ai/developers/agent-publishing/docs). Endpoint details: [HTTP reference](http-reference.md).

## 1. Prepare the build

- Build the game if the project has a build step. You will upload the output folder (for example `./dist`), not the source.
- No build step? Copy the files the game needs (`index.html` and everything it loads) into a new folder such as `./dist`, and publish that. Don't publish the project root: the CLI keeps its state in `.rosebud/` there, and hidden files such as `.gitignore` can't be uploaded. Copy the files again before each `rosebud update`.
- The folder must have `index.html` at its top level and run from static files. Rosebud does not run a build or install packages.
- HTML, JS, CSS and JSON files can go anywhere in the folder.
- Preserve UTF-8 `.txt` notices (including `.TXT`) and extensionless `LICENSE`, `LICENCE`, `NOTICE`, `COPYING`, `COPYRIGHT` and `AUTHORS` at their original paths, with unchanged contents. Their filenames may keep their original case. Never remove license notices to satisfy the allowlist.
- Put every image, audio file, font, model and WASM file inside `assets/`, with lowercase names using letters, numbers, underscores and single hyphens; start and end names with a letter or number, and use dots only for extensions (for example `assets/player-ship.png`). Update the references in your code to match.
- Supported media: PNG, JPEG, WebP, SVG; MP3, WAV, OGG; WOFF, WOFF2, TTF, OTF; WASM; GLB, glTF and `.bin` buffers.
- Limits: 10 MiB zipped, 50 MiB unzipped, 500 entries, including folders.
- Everything you upload is public. Keep `.env` files, credentials, `node_modules` and source maps out of the build.

## 2. Install the CLI

```sh
npm install --global @rosebudai/cli
```

It needs Node.js 22 or later. If you can't install globally, replace `rosebud` in every command below with `npx --yes @rosebudai/cli`.

## 3. Publish once

Run this from the project root, not from inside the build folder:

```sh
rosebud publish --directory ./dist --title 'Moon Garden' --description 'Collect five glowing seeds.'
```

- `--title` and `--description` are optional. Include them: they appear on the play page. Use the game's real name and one plain sentence about it.
- `--thumbnail ./cover.png` is optional: a PNG or JPEG gameplay screenshot, at most 2 MiB.
- `--agent-client` is optional. Set it to `claude-code`, `codex`, `cursor`, `gemini-cli` or `other` to say which tool published the game. `rosebud connect` accepts it too.
- Use `--zip ./game.zip` instead of `--directory` if you already have a ZIP.

The result includes `play_url`, `claim_url` and `claim_expires_at`. The one-hour claim window starts now. The CLI saves the game's handle in `.rosebud/project.json`; if the project uses git, add `.rosebud/` to `.gitignore`.

Each `publish` creates a new game, so don't publish again to retry an error. If a publish exits with code 4, the game may already exist: tell the person instead of publishing again.

## 4. Check the game

Open `play_url` in a browser and confirm the game loads and responds to input. A successful upload means the files were accepted, not that the game works. If you can't open a browser, fetch `play_url` to confirm the page exists, then say so when you hand off and ask the person to check that the game runs. The game loads in the browser, so fetching the page doesn't prove it works.

If the game doesn't start, fix the build and publish it again as a new game with its own state file, for example `--state .rosebud/fixed.json`, and pass that `--state` to every later command. Hand off only the new game's links. Nobody can claim the broken upload without its link, so it's deleted after an hour.

## 5. Hand off to the person

Unless the person has said they don't want you to publish updates, run:

```sh
rosebud connect
```

Give them the `approval_url` it prints. Opening it claims the game and lets you publish updates, in one step. It expires after 30 minutes.

Also give them the `claim_url` from step 3 as a backup. It keeps working until `claim_expires_at`, so they can still keep the game if the approval link has expired. Once the game is claimed, the claim link stops working, but the approval link still works for the account that claimed it until it expires. Later, `rosebud connect` creates a new approval link for the owner.

If they don't want you to publish updates, give them only the `claim_url`.

Tell them:

1. The play link. It's public and they can share it.
2. The approval link and the claim link. They're private: anyone with either can claim the game.
3. The deadline from `claim_expires_at`, in their time zone if you know it, otherwise in UTC. The unclaimed game becomes unavailable at that time and is permanently deleted by background cleanup.
4. What happens next: they open the link, sign in or create a free Rosebud account, and select **Claim game and allow updates** (or **Claim game**).

Don't copy private links into commits, issues, shared logs, chat channels or the game itself.

## 6. Publish updates in later sessions

```sh
rosebud status
rosebud update --directory ./dist
```

- `update` works only when `status` reports `"status": "active"`. If it reports `pending`, `expired` or `revoked`, run `rosebud connect` and ask the person to approve the new link.
- If `content_version` in `status` differs from `saved_content_version`, the game was changed on Rosebud since your last release. Don't overwrite it. Tell the person, bring their changes into your local source, and only then run `rosebud update --directory ./dist --expected-version <content_version>`.
- If an update exits with code 4, run the identical command again. It won't publish twice.
- Omitted `--title`, `--description` and `--thumbnail` keep their current values.

## If the claim deadline passes

The game is gone and can't be restored. Publish the same local build as a new game with a new state file, then hand off again:

```sh
rosebud publish --directory ./dist --state .rosebud/new-project.json --title 'Moon Garden'
```

Pass that `--state` to every later command for the new game. It has a new link.

## Rules

- When an update fails, run `rosebud status` before doing anything else. Don't create a second game to work around an error.
- Run commands one at a time, always against the same state file.
- Don't edit files in `.rosebud/` or `~/.config/rosebud/credentials`. Set `ROSEBUD_CREDENTIALS_DIR` to keep credentials in another folder.

## Exit codes and common errors

| Exit | Meaning                                                   | What to do                                 |
| ---- | --------------------------------------------------------- | ------------------------------------------ |
| 0    | Success                                                   | Continue                                   |
| 2    | Bad command or build                                      | Fix the input and run the command again    |
| 3    | Rosebud rejected the request; read `error.code`           | Fix the cause below, then retry            |
| 4    | The outcome is unknown, for example after a lost response | Follow step 3 or step 6 above; don't guess |

| `error.code`                                        | Fix                                                                                                    |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `missing_index`                                     | Put `index.html` at the top of the build folder, not in a subfolder                                    |
| `state_in_build`                                    | Publish the build folder, not the project root (see step 1)                                            |
| `unsafe_path`                                       | Remove the hidden or oddly named file `error.message` names, or publish a copy of the game in `./dist` |
| `media_path`, `noncanonical_path`                   | Move media into `assets/` and rename it as `error.message` says                                        |
| `unsupported_file`, `invalid_media`                 | Correct the named file; keep license notices as supported UTF-8 text                                   |
| `compressed_limit`, `expanded_limit`, `entry_limit` | Shrink or remove assets until the build fits the limits                                                |
| `game_expired`                                      | See "If the claim deadline passes"                                                                     |
| `connection_pending`, `connection_expired`          | Run `rosebud connect` and ask the person to approve                                                    |
| `version_conflict`                                  | Reconcile as described in step 6                                                                       |
| `project_busy`                                      | Rosebud is still generating changes to this game; wait, then retry                                     |

Bundlers are the usual cause of media errors. Vite adds mixed-case hashes to imported assets (`assets/logo-BdF3x9.png`) and copies its `public/` folder to the top of the build, outside `assets/`. For Vite, keep media file names lowercase, move `public/` media under `public/assets/`, and set `build.rollupOptions.output.assetFileNames` to `'assets/[name].[ext]'`.
