# Catalog

> Things to install, with a search box and Install or Update. A native Pyzed component for Python desktop GUIs, with a live preview, code examples and its API.

Source: https://albakore.github.io/pyzed/library/catalog/

:::tip[In plain words]
A searchable shop-like list of add-ons, each with an Install button.
:::

`ui.Catalog` is a component for things to install (agents from a registry, MCP servers, skills): a search box over a scrolling list where each row shows the name, `version` and `description`, and on the right Install, Installed or Update to a newer version. `items` are dicts or objects with `id`, `name` and optional `version`, `description`, `installed` (a bool) and `update` (the newer version). Install and Update report `on_install` with the item's id in `event.value`; while `busy` holds an id, that row shows a spinner and the other Install buttons are disabled. `height` is the list's height.

`ui.Catalog` · Category: [AI agents](https://albakore.github.io/pyzed/library/#ai-agents) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#catalog)

**Code:**

```python
pz.container(ui.Catalog(items=[
    {"id": "gemini", "name": "Gemini CLI", "version": "0.64.0", "description": "A coding agent for the terminal.",
     "installed": True, "update": "0.65.0"},
    {"id": "codex", "name": "Codex CLI", "version": "0.40.0", "description": "Writes and runs code for you."},
    {"id": "goose", "name": "Goose", "version": "1.9.0", "description": "An open source agent.",
     "installed": True},
    {"id": "opencode", "name": "OpenCode", "version": "0.12.1", "description": "An agent that edits whole projects."},
], busy="codex", height=220, on_install=on_install), width=460)
```

:::note[New]
This component was added recently. Tell us how it works for you in the [issue tracker](https://github.com/albakore/pyzed/issues).
:::

## When to use

- To add agents, MCP servers, skills or plugins from a list, inside a dialog or a settings page.

## Usage

```python
import pyzed as pz
from pyzed import ui

class Agents(pz.App):
    def __init__(self):
        self.registry = load_registry()  # [{"id", "name", "version", "description", "installed"}, ...]
        self.installing = ""

    def view(self):
        return ui.Catalog(items=self.registry, busy=self.installing,
                          on_install=lambda e: self.install(e.value))
```

## Variants

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### State

While one item installs, the other Install buttons are disabled.

```python
pz.container(ui.Catalog(items=[{"id": "goose", "name": "Goose", "version": "1.9.0",
                                "description": "An open source agent."}],
                         height=50), width=380)
```

```python
pz.container(ui.Catalog(items=[{"id": "goose", "name": "Goose", "version": "1.9.0",
                                "description": "An open source agent.", "installed": True}],
                         height=50), width=380)
```

```python
pz.container(ui.Catalog(items=[{"id": "goose", "name": "Goose", "version": "1.9.0",
                                "description": "An open source agent.", "installed": True,
                                "update": "2.0.0"}], height=50), width=380)
```

```python
pz.container(ui.Catalog(items=[
    {"id": "goose", "name": "Goose", "version": "1.9.0", "description": "An open source agent."},
    {"id": "codex", "name": "Codex CLI", "version": "0.40.0", "description": "Writes and runs code."},
], busy="goose", height=100), width=380)
```

```python
pz.container(ui.Catalog(items=[{"id": "goose", "name": "Goose"}], height=50), width=380)
```

## Events

Catalog has **1 event of its own**, listed below. Like every widget it also has the **18 common events** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), attached with `.on(...)`: see [Events](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

It is a `pz.Component`, so it also raises `on_mount` and `on_unmount`, and its handlers get a `pz.Event` whose `value` holds the data.

| Event | Handler gets | When |
| --- | --- | --- |
| `on_install` | `(event)` | Install or Update was pressed (`event.value` is the id). |

**Handler gets** is what Pyzed passes to your callable: `()` means nothing. Any handler can also be a message (a value that is not callable), which goes to `update()`.

### Handling the events

The same events written three ways. **Imperative**: methods of the app change `self`. **Declarative**: the handlers produce messages and one `update()` decides. **Functional**: a pure function draws an immutable state and asks for changes with `set_state(...)`. See [Imperative, declarative and functional](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
class Agents(pz.App):
    def __init__(self):
        self.items = [{"id": "goose", "name": "Goose", "version": "1.9.0"},
                      {"id": "codex", "name": "Codex CLI", "version": "0.40.0"}]
        self.installing = ""

    def install(self, ident):            # on_install: event.value is the item's id
        self.installing = ident

    def view(self):
        return ui.Catalog(items=self.items, busy=self.installing, height=200,
                          on_install=lambda e: self.install(e.value))
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class Install:
    ident: str

class Agents(pz.App):
    items = [{"id": "goose", "name": "Goose", "version": "1.9.0"}]
    installing = ""

    def update(self, message):
        match message:
            case Install(ident):
                self.installing = ident

    def view(self):
        return ui.Catalog(items=self.items, busy=self.installing, height=200,
                          on_install=lambda e: Install(e.value))
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    installed: frozenset = frozenset()

ITEMS = [{"id": "goose", "name": "Goose", "version": "1.9.0"}]

def view(state, set_state):
    items = [{**i, "installed": i["id"] in state.installed} for i in ITEMS]
    return ui.Catalog(items=items, height=200,
                      on_install=lambda e: set_state(installed=state.installed | {e.value}))
```

## Properties and API

### `ui.Catalog`

```python
class Catalog
```

A component for things to install with a search box (agents from a registry, servers, skills…): each row shows Install, Installed or Update to a newer version.

| Property | Default | Description |
| --- | --- | --- |
| `items` | `None` | Dicts or objects with `id`, `name` and optional `version`, `description`, `installed` and `update`. |
| `busy` | `""` | The id being installed (a spinner). |
| `height` | `320` | Height of the list. |
| `theme` | `None` | Theme. |

Events: `on_install` ([events](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#catalog)

## Related

- [Service row](https://albakore.github.io/pyzed/library/service-row/): A settings row for an MCP server, agent or skill: status, switch and menu.
- [Search input](https://albakore.github.io/pyzed/library/search-input/): An input with a magnifying glass, for filtering.
- [List item](https://albakore.github.io/pyzed/library/list-item/): A list row with icon, text and an end slot.
