# Build, Install & Iterate
URL: /docs/building-apps/build-install-lifecycle
Markdown: /docs/building-apps/build-install-lifecycle.md
Description: Move an app from source to running: build, pack, install, and update.

A Rome app lives in a permanent source tree (tracked in git) and is run by the
daemon from a packed artifact. Two separate tools move an app through its life:

* The **`rome` CLI** (shipped by `@rome-os/app-web-sdk`) builds the app on your
  machine, runs the web bundle with live reload, bumps the manifest version, and
  publishes to the Rome App Store.
* The **`system:app_management` action** drives the daemon: it builds, packs, and
  installs in one call, uninstalls, and enables/disables apps.

You can't swap one for the other. `rome build`/`rome dev` are for local
feedback; the daemon does its own build during `system:app_management { op: "install" }`.
Don't mix up either of these with the Rome monorepo's own `pnpm` scripts — those
are not part of app development.

## The `rome` CLI [#the-rome-cli]

The CLI has a small set of commands. Running `rome`, `rome -h`, `rome --help`,
or `rome help` prints usage.

```text
rome dev        Start the dev server with HMR
rome build      Build the app web bundle for production
rome upgrade    Bump app.yaml version by major, minor, or patch

rome login      Log in to the Rome App Store and store credentials
rome whoami     Show the currently logged-in account
rome publish    Package a Rome app directory and upload it
```

`dev` and `build` read `app.yaml` (`web.displayName`, `web.navLabel`,
`web.entry`) and write `dist/` with a `manifest.json` plus `assets/`. Run
`rome <command> --help` for the options of each command.

### `rome build` [#rome-build]

`rome build` makes the production build from the current folder. It sets
`NODE_ENV=production` (if it isn't set), clears the backend output folders under
`dist/`, runs the rslib build that compiles the backend (TypeScript) and bundles
the web UI, then copies backend assets and the app icon into the output.

```bash
# from <app-root>
rome build
```

The result is the `dist/` tree the daemon would otherwise make — backend output
plus the web bundle under `dist/web/`. You normally don't need to run this by
hand: the daemon runs the same build during install. Run it on your machine only
when you want to see build errors before installing.

### `rome dev` (HMR) [#rome-dev-hmr]

`rome dev` runs the web bundle in watch mode so you can work on it locally with
hot reload. It sets `NODE_ENV=development` (if it isn't set), clears backend
output, copies backend assets and the app icon, then starts the rslib build with
`watch: true`.

```bash
# from <app-root>
rome dev
```

Use this when working on the React UI and you want instant feedback in the
browser. Backend, action, and DB changes still ship through a `system:app_management`
install.

### Customizing the build (`rslib.config.ts`) [#customizing-the-build-rslibconfigts]

The build is [rslib](https://rslib.rs) under the hood, and the SDK generates
the whole config — most apps never touch it. When you do need to (an extra
bundler plugin, a custom `define`, an asset rule), drop an `rslib.config.ts`
(or `.js` / `.mjs`) at the app root. `rome build`, `rome dev`, and the daemon's
install build all load it and **merge it over the generated base config**, so
you only declare the delta:

```ts
// <app-root>/rslib.config.ts
import { defineConfig } from "@rome-os/app-web-sdk/config";

export default defineConfig({
  source: {
    define: { "import.meta.env.MY_FLAG": JSON.stringify(true) },
  },
});
```

The `@rome-os/app-web-sdk/config` subpath re-exports `defineConfig`,
`mergeRslibConfig`, `loadConfig`, and the `RslibConfig` type from
`@rslib/core`, so the app doesn't need its own rslib dependency.

> **Watch out:** the merged config still has to produce the layout the daemon
> expects (backend output plus the web bundle under `dist/web/`). Add to the
> config; don't redefine `lib` entries or output paths.

### `rome upgrade` (version bump) [#rome-upgrade-version-bump]

`rome upgrade` bumps the SemVer `version` field in `app.yaml`.

```bash
rome upgrade <major|minor|patch> [directory]
```

* The release part is required and must be one of `major`, `minor`, or
  `patch`.
* The optional `[directory]` can be the app folder or any folder inside it; the
  command walks up to find `app.yaml`. It defaults to the current folder.

What each bump does:

| Release | `1.4.2` becomes |
| ------- | --------------- |
| `major` | `2.0.0`         |
| `minor` | `1.5.0`         |
| `patch` | `1.4.3`         |

The current `version` must be valid SemVer (e.g. `1.2.3`); the edit leaves the
rest of the manifest as-is. On success it prints the manifest path and the
`previous -> new` change.

```bash
$ rome upgrade patch
Updated /path/to/app.yaml: 1.4.2 -> 1.4.3
```

### App Store: `login`, `whoami`, `publish` [#app-store-login-whoami-publish]

These commands talk to the Rome App Store over HTTP. Only `rome login` takes a
`--host <url>` flag (falling back to the `ROME_STORE_HOST` environment
variable, then a built-in default). `rome whoami` and `rome publish` resolve
the host themselves: with `ROME_TOKEN` set they use `ROME_STORE_HOST` (or the
default), otherwise they use the host saved when you logged in.

**`rome login`** signs you in and saves a session token.

```bash
rome login --host <url> [--email <email>] [--password <pw>] [--password-stdin]
```

If you leave out email and password, you'll be asked for them. `--password`
works but is not safe; use the prompt or `--password-stdin` instead. On success
the session token is written to `~/.rome/store-token.json` with mode `0600`.

```bash
$ rome login
Email: me@example.com
Password:
Logged in as me@example.com. Saved to /home/me/.rome/store-token.json
```

**`rome whoami`** prints the account tied to the current credentials: host,
email, slug, where the auth came from (`ROME_TOKEN` env var or a 30-day session,
with the time left), and the publishing handles you have. If no credentials are
found, it tells you to set `ROME_TOKEN` (made in Settings → CLI / API token) or
run `rome login`.

```bash
$ rome whoami
host:   https://store.example.com
email:  me@example.com
slug:   me
auth:   session (expires in 29d)
handles: @me
```

**`rome publish`** packages an app folder and uploads it.

```bash
rome publish <directory> [--dry-run] [--out <file>]
                         [--exclude <pattern>] [--no-default-exclude]
```

The folder must contain `app.yaml`; the manifest's `id` and `version` are
required. The app is packaged as a single-rooted gzip tarball that comes out the
same every time (mtime/uid/gid are stripped, so the same source gives the same
content hash). By default it leaves out `node_modules`, `.git`, and `.DS_Store`;
add more with `--exclude` (repeatable), or drop the defaults with
`--no-default-exclude`.

* `--dry-run` packages and computes the SHA-256 hash without uploading.
* `--out <file>` also writes the tarball to disk.

To upload, you must be logged in (or have `ROME_TOKEN` set).

```bash
$ rome publish ./my-app --dry-run
Packaged my-app@1.4.3 — 81234 bytes, sha256 9f86d0...
Dry run — skipping upload.
```

## `system:app_management` lifecycle [#systemapp_management-lifecycle]

The `system:app_management` action is how the daemon creates, installs,
uninstalls, and turns apps on and off. It is what you use while iterating — not
the `rome` CLI build commands.

### Source-mode install (build, pack, install in one step) [#source-mode-install-build-pack-install-in-one-step]

A `source`-mode install hands the daemon the app's source repo. In one call the
daemon runs the workspace's own `pnpm install` + `pnpm build` (the same as
`rome build` — backend TypeScript to `dist/`, web bundle to `dist/web/`), packs
the result into `<app-root>/.rome/artifact`, and installs the packed artifact,
hot-swapping in the new code.

```jsonc
{
  "op": "install",
  "source": { "mode": "source", "path": "<absolute app-root>" }
}
```

Key rules:

* **`source` is required on every install.** The daemon does not guess it from
  the lockfile or remember it between calls; pass the same
  `{ mode: "source", path: "<absolute app-root>" }` on the first install and
  every one after.
* `spec.source` points at the **source repo**, not the artifact. Pointing
  `mode: "source"` at `<app-root>/.rome/artifact` (or `mode: "bundle"` at the
  repo root) is the wrong shape and fails with `ARTIFACT_INVALID`, which names
  the exact source to pass — follow it exactly.
* Every install rebuilds and repacks, so edits to YAML alone, migrations alone,
  or the backend alone ship through the same single call — nothing extra is
  needed.
* If the daemon's build fails ("App build failed in …"), the version already
  installed keeps running. Run
  `pnpm --dir "<app-root>" install && pnpm --dir "<app-root>" run build` to see
  it, fix it, and re-install.

So the loop is: edit under `<app-root>/src/` → `git add -A && git commit` →
`system:app_management { op: "install", source: … }` → check it against the running
app → refresh `/apps` or `/apps/<appId>`. Never install uncommitted code — the
commit is what makes the change easy to review and undo.

### The daemon-managed artifact [#the-daemon-managed-artifact]

`<app-root>/.rome/artifact` is the packed build output the daemon can load. The
daemon makes it during install, and you **never edit or commit it by hand**. The
template's `.gitignore` leaves out `.rome/` and `dist/`, so build and pack
output stays out of git.

### Uninstall and purge [#uninstall-and-purge]

```jsonc
{ "op": "uninstall", "appId": "<appId>", "purge": false }
```

* Default (`purge: false`) removes the app's deployment folder
  (`deployment.yaml`, the per-version install folders, and the `active` symlink)
  but keeps the app's DB tables and data, so a later reinstall brings the state
  back.
* `purge: true` wipes everything the app owns, including its data.

### Enable / disable [#enable--disable]

To stop or restart an app without reinstalling, toggle `spec.enabled`:

```jsonc
{ "op": "set_enabled", "appId": "<appId>", "enabled": false }
```

The reconciler settles by loading or unloading the app's artifacts via
hot-swap. First-party apps that ship with Rome support only enable/disable, not
install/uninstall.

## Finding what already exists [#finding-what-already-exists]

Before writing new code, look up what's already there. The harness gives you
messaging, scheduling, agent calls, and other apps' actions; reusing them beats
writing your own clients. Your session has a live catalog.

### `search_actions` / `read_action` [#search_actions--read_action]

* `search_actions { query }` finds registered actions by name, description, or
  argument text.
* `read_action { action_name }` returns a per-argument summary (name, type,
  required, enums, description) plus metadata (`sideEffects`,
  `requiresApproval`).

The catalog is live: actions from an app you just installed show up right away.
`read_action` does **not** expand nested argument shapes — for complex inputs,
check the exact shape against the owning app's docs or source instead of
guessing from memory.

The catalog and `appContext.runAction(canonicalId, args)` are served by the same action
registry, so `read_action` is the right place to start for any `runAction` call:
base the canonical `<app-id>:<local-name>` ID and arguments on its summary before
you make the call.

### `search_skills` [#search_skills]

* `search_skills { query }` and `read_skill { skill_name }` bring up the how-to
  docs shipped by installed apps. Use them to find a set workflow before making
  one up.

(For event-driven features, the `system:search_event_catalog` action finds event types
that can be emitted and their payload schemas — the fields a routine
`trigger.filter` can match.)

### Prefer existing actions over your own clients [#prefer-existing-actions-over-your-own-clients]

If an existing action does the job — sending a message, scheduling a routine,
summoning an agent, a Composio tool call — call it via `appContext.runAction`.
Don't build your own HTTP client, notifier, or scheduler inside the app. The
catalog covers actions, skills, and events; the rest of the
`@rome-os/app-runtime` SDK (`db`, `log`, `runAction`, settings) is the
in-process API documented with the SDK.