# Installable applet packages

One file installs a whole applet on a Bridgit phone. The owner approves it once, on one review screen. The phone then creates every part itself and keeps it running with no agent online.

- Format: the template registry's own `bridgit-template/1` definition, with `runtime.adapter: "android.package.v1"`. No second format.
- Parts: up to 5 watches, one schedule, one workflow and one card. All are optional, but there must be at least one.
- Blanks: typed values the owner (or you) fill in: camera, interval, sensitivity, time and so on.
- Ready-made examples: every recipe has one at `https://bridgit.cc/recipes/<id>/applet.json`, for example [room-change-watcher](https://bridgit.cc/recipes/room-change-watcher/applet.json) or [situation-counter](https://bridgit.cc/recipes/situation-counter/applet.json) (needs API revision 71: its `situation` blank takes a library id from `GET /v1/situations` or the owner's own words).
- Needs API revision 69 or later (`GET /v1/health` → `api_revision`).

## What the owner does

1. Opens an Import link (the **Import to my phone** button on a recipe page, a QR code, or `bridgit://install?…`), or an `applet.json` file.
2. Reads one screen: what each part does and how often, what it can use, the worst-case paid model cost, and anything switched off on the phone. Changes any blank.
3. Taps **Approve and install** (with fingerprint or PIN if the phone asks for it).
4. Later, in **Applets › Installed applets**, they can Pause/Resume, Check for update, or Remove it.

Nothing runs and nothing is granted before Approve. Each installed applet gets its own entry under Agents. That entry holds only the permissions the file declares, and no key can use it. Phone switches still apply: if Camera is switched off, the review says so and the install is refused.

## What an agent does

| Call | Does |
| --- | --- |
| `POST /v1/applets/validate` | Dry run. Returns exactly what the owner would review, or the errors. Stores nothing. |
| `POST /v1/applets/install` | Asks the owner (202, `awaiting_owner`). Then `applet.installed` (with `instance_id`) or `applet.declined` arrives in `/v1/events`. |
| `GET /v1/applets/installed` | The applets you asked for: status, config, the watch ids they run. |
| `POST /v1/applets/installed/{id}/update` | A new revision of the same package. It applies at once when it adds no permission; otherwise it waits for the owner. |
| `POST /v1/applets/installed/{id}/pause` | `{"paused":true}` or `false`. |
| `DELETE /v1/applets/installed/{id}` | Removes every part and its permissions. `left_behind` is empty when nothing is left. |

The body is one of:

- `{"package": {…}}`: the file itself.
- `{"url": "https://bridgit.cc/…/applet.json"}`: bridgit.cc only.
- `{"template": "<registry key>", "revision": N}`: built-in templates, or published templates of the account the phone is linked to.

Add `"config": {…}` to fill blanks. Your agent needs the **Install applets** permission.

Updates are the fast way to change a running applet:

- Send the whole new file with a higher `version`. The phone recompiles it and swaps in only the parts that changed.
- A watch whose filled request is unchanged keeps its id and its counts.
- A changed watch is replaced, and the card is re-pointed at the new watch id.
- The owner's blank values carry over.
- If the new revision no longer accepts a carried-over value, the update is refused rather than silently reset.

## The file

```json
{
  "format": "bridgit-template/1", "id": "room-change-watcher", "version": "1.0.0", "kind": "applet",
  "name": "Room change watcher", "summary": "A free image-change trigger…",
  "runtime": {"adapter": "android.package.v1", "applet_key": "room-change-watcher", "supports_dynamic_execution": false},
  "config_schema": {"type": "object", "additionalProperties": false, "required": ["camera", "interval_s"],
    "properties": {"camera": {"type": "string", "enum": ["rear", "front"]}, "interval_s": {"type": "integer", "minimum": 30, "maximum": 3600}}},
  "defaults": {"camera": "rear", "interval_s": 30},
  "processing": {
    "watches": [{"id": "main", "request": {"label": "Room image changes", "data": ["camera"], "camera": {"$input": "camera"},
      "model": "none", "interval_s": {"$input": "interval_s"}, "local": {"change_check": true, "sensitivity": "normal"}}}],
    "schedule": {"manifest": {"…": "a POST /v1/schedule manifest"}},
    "workflow": {"…": "a workflow.propose body"}
  },
  "permissions": {"required": ["camera", "interactions", "watches"], "optional": [], "remote": [], "automatic_grants": false},
  "ui": {"inputs": {"camera": {"label": "Camera", "help": "The camera that faces the room."}},
         "card": {"asset": "card.html", "title": "Room change watcher", "data": {"resource_id": {"$watch": "main"}}}},
  "assets": {"card.html": {"media_type": "text/html", "text": "<!doctype html>… /*RECIPE*/{}/*END RECIPE*/ …", "sha256": "…"}}
}
```

The registry also requires `observation_schema`, `metrics`, `lifecycle`, `migration` and `data_policy`. Copy them from a recipe's `applet.json`; the phone ignores them.

Rules the phone enforces:

- **Blanks are whole values.** `{"$input": "camera"}` is replaced by the checked value of that blank. Nothing is spliced into text, so a value can't change a URL, a question or HTML.
- **Watch ids reach the card.** `{"$watch": "main"}`, used only in `ui.card.data`, becomes the real `watch_id`. The data goes into the card's `/*RECIPE*/…/*END RECIPE*/` block as JSON, with `<` escaped.
- **Permissions are worked out from the parts.** The phone derives them from the watch data (camera → `camera`), rule and schedule calls, the card (`interactions`), and the workflow's scopes. If the file uses something it didn't declare, it is refused. Declare everything the card calls too (for example `weather`). A card's `Bridgit.act` runs as the applet, so a call it didn't declare is refused.
- **Watches stay alive.** They get the longest lifetime the rules allow (24 hours, or 90 days with a tally) and are renewed while the applet is active. The card (7 days) is re-posted when it expires, unless the owner dismissed it.
- **Each part passes its own rules first.** Every part is checked by the same rules as the matching API call, before Approve is offered (`POST /v1/watches`, `POST /v1/interactions`, `POST /v1/schedule`, `workflow.validate`).
- **Workflows need their Device tools feature on.** Workflow scopes are granted only for that workflow. Its feature (for example Local workflows) must be on in Device tools, or the review lists it as blocked.
- **Sources are limited.** The phone downloads only from https://bridgit.cc and Bridgit's template registry. An Import link may carry `sha256` of the file, and the phone refuses a file that doesn't match.
- **The Pet monitor is the built-in monitor.** Its registry template (`dog-monitor`, adapter `android.dog.v1`) installs the native monitor with the chosen camera.

## Not covered yet

- Custom logic beyond watches, rules, schedules and workflow steps. There is no script step: packages are declarative, and `supports_dynamic_execution` stays false. A sandboxed script step is a possible later addition.
- Installing straight from an agent's own server URL. Send the file inline as `package` instead.
- Remote view of an installed applet's results. Use the card on the phone, or your own `/v1/events` reader.
