---
url: /guide/quickstart.md
---
# Quickstart

This page registers one command and dispatches it from two surfaces in a web page: a button and a
key chord.

## Register a command

```ts
import { OK, Registry, ok } from '@ufo-soft/caelus'
import { keybindings } from '@ufo-soft/caelus/keybindings'

let count = 0
const registry = new Registry()

registry.register({
  name: 'increment',
  command: { description: 'Add one', parameters: [] },
  handler: () => ok(++count),
})
```

A registration has three parts. The `name` is how every surface calls the command. The `command`
describes it: a description and its parameters, here none. The `handler` does the work and returns
an outcome: `ok` with data, or `failed` with a reason.

## Dispatch it from two surfaces

```ts
// A button per command, built from the registry
for (const { name, command } of registry.all()) {
  const button = document.createElement('button')
  button.textContent = name
  button.title = command.description
  button.onclick = () => registry.invoke(name, {})
  document.body.append(button)
}

// A key chord for the same command
keybindings(registry, { 'alt+up': 'increment' }).attach()
```

The buttons come from `registry.all()`, so every registered command gets one with no per-command
code. The keymap maps the chord `alt+up` to the command's name. Both call
`registry.invoke` with the name and the args.

## Read the outcome

```ts
const outcome = await registry.invoke('increment', {})
if (outcome.kind === OK) console.log(outcome.data)
```

```txt
1
```

`invoke` always returns a promise of an outcome. Check `kind` before reading `data`: a failed
outcome has a `reason` instead.

## Next

* [Commands](./commands.md): parameters, handlers, outcomes, and the registry in full.
* [Keybindings](./keybindings.md), [CLI](./cli.md), [MCP](./mcp.md): the surfaces.
