# Extensions

Plugin system with three tiers: Browser JS (client-side), Starlark
sandbox (server-side, v0.29.0), Sidecar containers (server-side, future).
Currently only Browser tier is implemented.

### User Extensions

**Auth:** Authenticated user

```
GET  /extensions                    → {"data": [...UserExtension]}
     ?tier=browser                  (optional filter by tier)
POST /extensions/:id/settings       ← {"is_enabled": bool, "settings": {...}}
     :id = extension UUID           → {"ok": true}
GET  /extensions/:id/manifest       → {"data": <manifest JSON>}
     :id = ext_id (manifest id)
GET  /extensions/tools              → {"data": [...tool schema objects]}
```

**Notes:**
- `GET /extensions` returns `UserExtension` objects: the base extension
  fields plus `user_enabled` and `user_settings` overrides.
- System extensions (`is_system: true`) cannot be disabled by users.
  Attempting to set `is_enabled: false` on a system extension returns 403.
- `GET /extensions/tools` returns raw tool schema JSON from all enabled
  browser extensions' `manifest.tools[]` arrays.

### Admin Extension Management

**Auth:** Admin role

```
GET    /admin/extensions             → {"data": [...Extension]}
POST   /admin/extensions             ← {ext_id*, name*, version?, tier?,
                                        description?, author?, manifest?,
                                        is_system?, is_enabled?}
                                     → {"data": Extension} (201)
PUT    /admin/extensions/:id         ← {name?, version?, description?,
     :id = extension UUID               author?, is_system?, is_enabled?,
                                        manifest?}
                                     → {"data": Extension}
DELETE /admin/extensions/:id         → {"ok": true}
     :id = extension UUID
```

**Install defaults:** `version` → `"0.0.0"`, `tier` → `"browser"`,
`manifest` → `{}`, `scope` → `"global"`.

**Tier validation:** `tier` must be one of: `browser`, `starlark`, `sidecar`.

**Duplicate rejection:** If `ext_id` is already installed, returns 409.

### Asset Serving

**Auth:** None (public — script tags can't send Authorization headers)

```
GET /extensions/:id/assets/*path    → application/javascript
    :id = ext_id (manifest id)
```

Returns the inline `_script` field from the extension's manifest.
The `*path` segment is accepted but currently ignored (all requests
return the same script). Disabled extensions return 404.

### Builtin Seeding

On startup, `SeedBuiltinExtensions()` scans `extensions/builtin/`
for subdirectories containing `manifest.json` + `script.js`. Each
is upserted as a system extension:
- **New ext_id** → `Create` with `is_system: true`
- **Same version** → skip (idempotent)
- **Different version** → `Update` manifest, name, description, author

---
