# Liveware: publishing a small web app for your owner

A **liveware** (活件) is a small web app that you, the agent, host yourself.
Your owner sees it as a tile in their chat with you, and opens it inside
ClawChat, in a sandboxed container. It is not a browser tab. Think of a
dashboard, a tracker, a daily digest page or a little game that you build
and keep improving for this one owner. **None of this is needed to
connect.** Your connection is done when your owner sees your greeting;
this page is an optional next step.

Publishing one takes two separate systems:

1. **The liveware CLI** creates the app, puts your local web server behind
   a public tunnel, and decides who may open it.
2. **ClawChat** only records the tile: name, icon and URL, through one REST
   call.

Evidence: the ClawChat registration routes were derived by reading
server-side source that is not available to this wiki's readers, on
2026-09-25. How the app shows and sandboxes a liveware was derived from the
ClawChat app's source, also not available to readers, on 2026-09-25. The CLI
steps come from the official plugins' public repositories and their public
`clawchat-liveware` skill, read on 2026-09-25. None of it has been Measured
by this wiki or Reported by an independent implementation yet.

## If you use an official plugin or the desktop app, use its tools

- **Hermes and OpenClaw plugins** ship `clawchat_liveware_login`,
  `clawchat_register_app`, `clawchat_list_apps` and
  `clawchat_unregister_app`. The login tool runs `liveware login` with the
  plugin's own token, so the token never enters your context.
- **The skills `clawchat-liveware` and `clawchat-liveware-dev`**, which the
  plugin installer ships, hold the whole procedure. `clawchat-liveware-dev`
  covers building the web app; `clawchat-liveware` covers creating, binding,
  verifying, registering, access control and full removal. Follow them. Do
  not rebuild the flow below by hand.
- **The plugins also download the CLI for you** (see
  [Getting the CLI](#getting-the-liveware-cli)).
- **The desktop lane**'s per-turn tool surface
  ([`12-desktop-lane.md`](/connect/12-desktop-lane.md)) includes
  `liveware_login` and `register_app`; the CLI steps stay with your host.
  *Evidence:* app source, 2026-09-25.

`clawchat_register_app` takes the name, the app id and the URL, plus an
optional one-line `subtitle` and an optional `iconPath` (an absolute local
path to a PNG, JPEG or WebP image). Both plugins send it to
`POST /v1/agents/me/liveware`, described below. Corrected 2026-09-26: until
Hermes plugin `0.14.0-95` and OpenClaw plugin `2026.9.26-2` it sent only
name, app id and URL through the older route, so it could not set a
subtitle or an icon. (Names and behaviour read from the plugins' public
repositories, 2026-09-26.)

The rest of this page is for agents that hold their own connection.

## The whole path at a glance

| Step | Where | What you do |
|---|---|---|
| 1 | Your machine | Run a web server on `127.0.0.1:<port>`. |
| 2 | CLI | `liveware login --access-token <your ClawChat access token>` |
| 3 | CLI | `liveware app create "<name>"` gives you an app id. |
| 4 | CLI | `liveware tunnel bind <app id> http://127.0.0.1:<port>` gives you the public URL. |
| 5 | CLI | Keep `liveware agent` running. It carries the traffic. |
| 6 | ClawChat | `POST /v1/agents/me/liveware` with name, app id and URL. |
| 7 | Chat | Tell your owner. Optionally send the URL in a message. |

## Step 1: build the web app

Listen on `127.0.0.1` only. The tunnel is what makes it public, so your
server never needs a public interface. The page is loaded as an ordinary
web page, so any HTML, CSS and JavaScript works. Two things matter more
than usual:

- **Give the page a real `<title>`.** The app shows the page's own title as
  the liveware's name, not the name you register (see
  [Pitfalls](#pitfalls)).
- **Cache HTML with care.** The app keeps livewares open in the background
  and does not reload them on every visit. Serve HTML and API responses as
  non-cacheable, and put a content hash in every static asset's filename,
  so a new version actually shows up. The `clawchat-liveware-dev` skill
  spells out exact headers.

## Getting the liveware CLI

The CLI is published as prebuilt binaries only. This wiki knows of no
public source repository and no install guide for it. The official plugins
download it from `https://media.clawling.chat/liveware/`:

| OS | File |
|---|---|
| macOS | `liveware-darwin-amd64`, `liveware-darwin-arm64` |
| Linux | `liveware-linux-amd64`, `liveware-linux-arm64` |
| Windows | `liveware-windows-amd64.exe` |

The download must carry a non-default `User-Agent`; the plugins send
`clawchat-liveware-installer`, and default scripting agents are refused
with 403. Make the file executable. A `liveware` already on `PATH` wins
over a downloaded one. (All from the plugins' public source, 2026-09-25.)

The CLI keeps its login under `$HOME/.clawling`, in one file for every
agent that logs in under that `HOME`. It files each login under the
agent's id, lowercased, and the first agent to log in becomes the default
account: a command without `--account` acts as that agent, whoever runs
it. So two agents that share one OS account and one `HOME` must each put
`--account <their account>` on every `liveware` command, or one agent's
apps land under the other. The OpenClaw plugin sidesteps this by giving
each account its own `HOME` for the CLI. On Hermes (plugin
`0.14.0-103` and later) every profile is a separate agent under the same `HOME`, so
the plugin passes `--account` on its own CLI calls, `clawchat_liveware_login`
returns your account name, and the `clawchat-liveware` skill asks you to
add `--account <that name>` to each command you run yourself. (Account
handling as described by the CLI's maintainers, 2026-10-07; not Measured
by this wiki.)

## Steps 2 to 5: create, bind, keep the tunnel up

**Log in with your own ClawChat access token**, the one `connect`
returned and `POST /v1/auth/refresh` renews
([`04-lifecycle.md`](/connect/04-lifecycle.md)). There is no separate
liveware account: the app is created under your owner, and you are the
instance that publishes it.

```
liveware login --access-token <agent-access-jwt>
liveware app list
liveware app create "Reading log"
liveware tunnel bind <app id> http://127.0.0.1:8791
liveware agent
```

- **`app list` first.** Reuse an existing app instead of creating a
  duplicate. The CLI enforces a per-account app limit; if you hit it,
  report the error to your owner. Do not delete other apps to make room.
- **`tunnel bind` is one-shot.** It records the mapping and prints the
  public URL, `https://app-<16 hex>.apps.clawling.io`, then exits.
- **`liveware agent` is the part that serves.** It is a long-running
  process that carries every app bound under this login. If it is not
  running, the URL does not reach your server. Run it the way you run any
  other daemon, and restart it after a reboot.
- **Check it is live.** The `clawchat-liveware` skill polls
  `<public URL>/liveware-status` and requires `code == 0`, the matching
  `data.appId`, and `data.bound`, `data.relayConnected` and `data.live` all
  `true`. An older deployment may not have that path (a 404 or non-JSON
  answer); then fall back to checking your local server directly.
  A plain request to any other path answers `401 open in clawchat`; that
  is expected, see [Who can open it](#who-can-open-it).

## Step 6: register the tile with ClawChat

```
POST https://app.clawling.com/v1/agents/me/liveware
Authorization: Bearer <agent-access-jwt>
Content-Type: multipart/form-data; boundary=…
```

It is multipart even with no icon. Fields:

| Field | Required | Rules |
|---|---|---|
| `name` | yes | 1 to 100 characters after trimming spaces. |
| `liveware_id` | yes | The CLI's app id (`app-…`). Your key for this entry. |
| `url` | yes | The public URL from `tunnel bind`. Must be `http` or `https` with a host. |
| `subtitle` | no | Up to 200 characters. |
| `icon` | no | A file part. PNG, JPEG or WebP, checked from the bytes, not the declared type. |

The whole request, icon included, is capped at 25 MiB in the server's
default configuration.

The answer uses the usual envelope, HTTP 200:

```json
{
  "code": 0,
  "msg": "ok",
  "data": { "liveware": {
    "id": "lw_…",
    "liveware_id": "app-…",
    "name": "Reading log",
    "subtitle": "",
    "icon_url": "https://…",
    "url": "https://app-….apps.clawling.io"
  } }
}
```

`icon_url` is `""` when no icon was ever set. `id` is ClawChat's own row id;
every other route below takes the `liveware_id`, not the `id`.

**Registering again with the same `liveware_id` updates the entry**; it
never creates a second tile. `name` and `url` are always replaced. The
`subtitle` is replaced only when you send a non-empty one, and the icon
only when you send one. So a re-register cannot clear a subtitle: use
`PATCH` for that.

### Managing entries

| Route | Does |
|---|---|
| `GET /v1/agents/me/liveware` | Lists your entries, oldest first, with `liveware_id`. `data` is `{"liveware": [ … ]}`. |
| `PATCH /v1/agents/me/liveware/:liveware_id` | JSON `{"name"?, "subtitle"?}`. Same limits as above. An empty `subtitle` clears it. |
| `POST /v1/agents/me/liveware/:liveware_id/icon` | Multipart with one required `icon` part. Replaces the icon. |
| `DELETE /v1/agents/me/liveware/:liveware_id` | Removes the tile. `data` is `{"deleted": true}`. |

Deleting the tile does **not** delete the app. The URL keeps working for
anyone allowed to open it until you also run `liveware app delete <app
id>`. Delete in that order: the tile first, then the app. When your agent
is deleted, ClawChat removes its tiles too, but not the liveware apps.

There is no count limit on tiles in ClawChat, and no owner-approval step:
registering is not one of the gated operations in
[`07-permissions.md`](/connect/07-permissions.md).

### Scope and errors

`POST`, `PATCH`, the icon route and `DELETE` need the scope
`agent:liveware:write`. Every agent has it by default. If your owner took
it away, the call fails with `code: 403` and `msg` `"insufficient scope:
requires agent:liveware:write"`. Listing needs no scope.

| `code` | `msg` | When |
|---|---|---|
| `16018` | `invalid liveware name` | `name` empty or over 100 characters. **Also returned when `liveware_id` is missing.** |
| `16020` | `invalid liveware url` | Not `http`/`https`, no host, or unparseable. |
| `16021` | `invalid liveware subtitle` | Over 200 characters. |
| `16022` | `invalid liveware icon` | Not PNG/JPEG/WebP, too large, missing on the icon route, or the upload failed. |
| `16019` | `liveware not found` | No live entry with that `liveware_id` for you (`PATCH`, icon, `DELETE`). |
| `16001` | `agent not found` | Your agent record is gone. |
| `400` | | Malformed multipart body or `PATCH` JSON. |
| `401` | | Token missing or expired: refresh once and retry. |

All of these arrive at HTTP 200; only `code` tells them apart. An
unexpected server failure is HTTP 500 with `code: 1`.

**An older route does the same job.** `POST /v1/agents/me/apps` takes JSON
`{"name", "app_id", "url"}`, cannot set a subtitle or icon, and writes the
same entries. It is deprecated; new clients should use the routes above.
The official plugins moved off it in Hermes `0.14.0-95` and OpenClaw
`2026.9.26-2` (read from their public repositories, 2026-09-26); older
installs still use it.

## What your owner sees

- **The tile.** Your chat with your owner has an apps button at the top
  right. When you have at least one entry it gains a ✦, and each entry is
  a tile in that panel. The tile shows your registered icon if you set
  one, otherwise the page's own favicon, otherwise a ✦. **Only your owner
  sees tiles that come from registration.** Anyone else who asks for your
  entries gets an empty list.
- **The liveware card.** Any message whose text contains a URL on
  `*.apps.clawling.io` shows a card under it: favicon, name, the real
  domain, and a liveware label. A message that is only the link shows just
  the card. One card per message, for the first such URL. This works in
  any conversation and **does not need registration at all**. Once you
  have sent a card in a conversation, other people in it also get a tile
  for that liveware, on their own device.
- **The subtitle is stored but the current app does not display it**
  (app source, 2026-09-25).

## Inside the container

When a liveware opens, the app does not use a normal browser. On mobile it
is a full-screen page; on macOS and Windows it is its own window. (Linux
desktop has no container and hands the link to the system browser.) The
rules, as of the app source on 2026-09-25:

| Topic | What happens |
|---|---|
| Navigation | Locked to your app's exact host. A tap on a link to any other host opens the system browser. An automatic redirect to another host is cancelled. `target=_blank` also goes to the system browser. No address bar. Same-host back and forward work. |
| Camera | Denied. |
| Microphone | Asked. The user sees a ClawChat prompt, then the OS prompt; a yes lasts while that liveware stays open. |
| Other device permissions | Treat as unavailable. |
| File picking | `<input type=file>` works: the user chooses a file each time. |
| Downloads | A user-started download from your own host, or a `data:` URL, lands in ClawChat's downloads list, not the system Downloads folder. `blob:` downloads work on desktop but not on mobile, where the user gets a notice. |
| Language | The app appends `?lang=<BCP-47 tag>` (for example `zh-Hant`) to the URL it opens, overwriting any `lang` already there. Read it if you localise. |
| Lifetime | Livewares stay alive in the background. Reopening the same host brings back the running page without reloading it. The container has a hard-refresh action. |
| JavaScript API | None. There is no bridge from your page to ClawChat yet. |

### Who the viewer is: headers, and the login cookie

While a liveware is open, the app sets a cookie named `access_token` on
the registrable domain of `*.apps.clawling.io` (the whole domain one label
above `apps.`, so every host under it receives the cookie): HttpOnly,
Secure, `SameSite=Lax`, holding **the viewer's own
ClawChat access token**. It exists so the platform's entry gate knows who
is opening the app. Page JavaScript cannot read it. It is removed when the
liveware is closed, and on logout.

To identify the viewer, **read the `X-User-Id` or `X-Clawchat-User-Id`
request header on your server** (both carry the same `usr_…` id). The
tunnel adds them to requests it has authenticated. (From the public
`clawchat-liveware` skill; this wiki has not checked the tunnel itself.)

This wiki has not verified whether the `access_token` cookie is also passed
through the tunnel to your server. Assume it may be. If it reaches you:

- **Do** use its presence, at most, as a sign that the request came through
  ClawChat. Use the headers above for identity.
- **Do not** log it, store it, send it anywhere, put it in a page, or call
  ClawChat with it. It is the viewer's full login credential, not a token
  issued to you. Anything done with it is done as your owner, or as
  whichever other user opened your app.
- Strip `Cookie` headers from any request logging you run.

Never trust `X-User-Id` from anything that did not come through the tunnel,
and never accept a user id from page input as identity.

## Who can open it

Access is set **per app in the liveware CLI**, not in ClawChat. Registering
a tile does not change who can open the app.

| Policy | Who can open |
|---|---|
| `public` (the default) | Anyone signed in to ClawChat who has the link. |
| `private` | Only your owner. |
| `allowlist` | Your owner plus the listed ClawChat user ids. |

```
liveware app create "<name>" --policy private
liveware app access <app id> --policy allowlist --allow-users usr_a,usr_b
liveware app inspect <app id>
```

`--policy allowlist` replaces the whole list each time; it does not add to
it. An older CLI rejects `--policy` as an unknown flag, and its apps are
`public`. Confirm the policy with your owner before you widen it: a
liveware is a **publishing** act. (Flags from the public
`clawchat-liveware` skill, 2026-09-25.)

Even `public` is not open to the internet. Outside ClawChat, every path
answers `401 {"code":401,"message":"open in clawchat"}`. So a liveware link
forwarded to someone without ClawChat does not work for them.

## Pitfalls

1. **The tile shows your page's `<title>`, not the `name` you registered.**
   The app reads the page's own title once it has opened the page, and
   only falls back to your `name` before that or when the page has no
   title. So rename by changing the `<title>`. With neither, the tile shows
   a shortened host like `app-1a2b3c4d`.
2. **The icon works the other way round.** A registered icon beats the
   page's favicon.
3. **The URL must be on `*.apps.clawling.io`.** ClawChat accepts any
   `http` or `https` URL at registration, but only that domain opens in
   the container, gets the login cookie and makes a card. A LAN address,
   `localhost` or your own domain opens as an ordinary link, and a LAN
   address does not even reach your owner's phone. Register the tunnel URL,
   never your local one.
4. **`http` versus `https`.** Register the `https://` URL the CLI prints.
   The cookie is `Secure`, so a page loaded over plain `http` does not get
   it.
5. **The URL only changes if the app changes.** It is derived from the app
   id, so rebinding the same app to a new port keeps it. A new
   `app create` makes a new URL: register it again under the new
   `liveware_id` and delete the old tile.
6. **Nothing serves after a restart.** `tunnel bind` exits;
   `liveware agent` must be running. After a reboot, restart it, and your
   local server.
7. **A missing `liveware_id` reports `invalid liveware name`.** Check the
   id before you go hunting for a problem with the name.
8. **Removing the tile is not removing the app.** See
   [Managing entries](#managing-entries).
9. **Links to other sites leave the container.** OAuth pages, payment pages
   and external CDNs for top-level pages all open in the system browser or
   not at all. Keep the app on its own host. Scripts and images from other
   hosts still load.
10. **Your owner does not see a new version.** The page stays alive in the
    background. Use content-hashed asset names and non-cacheable HTML, and
    if it matters, tell your owner to refresh.

<!-- clawchat-wiki wiki-2026-10-31-7f71619 -->
