# Command line

Tabs, their status, settings, keys and every API function, from a shell.


`pumi` is on the `PATH` of every tab. It talks to the server, so it works with the window closed, and every function is a command: its name in words, its id first.

```sh
pumi tab open pumi_dev.terminal.shell --split right -- pi --model opus "fix the tests"
pumi tab list --block pumi_dev.terminal.shell
pumi tab read %3 --lines 80
pumi open README.md
pumi event on 'tab.*'
```

```text
usage: pumi COMMAND [ARGS…]

  pumi NOUN VERB [ID] [ARGS…] [--flags]   every function as words (`pumi registry list fn`), its id first

  pumi call                               any function, by name
  pumi completion zsh|bash|fish           what a shell completes for pumi
  pumi do                                 a TypeScript script, now, with its answer and status
  pumi help                               a function, an event, a block, or a noun's commands
  pumi open                               a file in a tab: `pumi open PATH [--line N]`
  pumi plugin new|dev|logs                make a plugin, run it as you edit it, read its log
  pumi server list|stop                   the server processes themselves
  pumi wait                               until a tab's program ends, with its status: `tab wait --exit`

An argument is JSON when it reads as JSON (`3`, `true`, `{}`) and text when it does not. Flags are
the options of the function's last argument, in kebab case: `--title x`, `--no-focus`, `--state`.
A tab is an id (`%3`), a pane `:4`, a view `@2`, a session `$1`, a request `+4`; a bare number
takes the sigil its place asks for. After `--`, a program and its words: `pumi tab open
pumi_dev.terminal.shell --split right -- pi "hi"` (the Terminal plugin's block).

For agents: `pumi tab list` (what runs where), `pumi tab read %3` (its text), `pumi tab wait %3`,
`pumi help` (every command), `pumi registry list fn --names`.

Global flags: --json, --server NAME, --socket PATH, --headless, --clean, --version, --help.
```

## Commands

There are no tmux verbs. `pumi NOUN VERB [ID] [ARGS…] [--flags]` is a function: `pumi tab get %3` is `tab.get("%3")`, and `pumi tab read` is `tab.call`'s `read` method. The flags are the options of the function's last argument, in kebab case: `--title x` is `{title: "x"}`, `--no-focus` is `false`, and `--state` is `true`.

An argument is JSON when it reads as JSON (`3`, `true`, `{"a":1}`) and text when it does not. A function whose one argument is an object takes its required fields first, as words: `pumi tab cells %3 --lines 5` is `tab.cells({tab: "%3", lines: 5})`. After `--` come a program and its words, for a command that runs one: `pumi tab open pumi_dev.terminal.shell -- htop` is `{command: ["htop"]}`, and `pumi tab run %3 -- npm test`. `pumi_dev.terminal.shell` is the [Terminal plugin](https://github.com/pumi-dev/plugins/tree/main/terminal)'s block, a shell in a tab. `get` prints the model; `tab read` prints the text; `list` prints a table on a terminal and JSON when it is piped. `pumi tab list` is a table either way, and `--json` says JSON (`tab list --json` is the ids).

A tab is an id, `%3` (`3` will do, where a tab is what is asked for); a pane `:4`, a view `@2`, a session `$1`, a request `+4`. `$PUMI_TAB`, `$PUMI_VIEW` and `$PUMI_SESSION` are yours, and a command that leaves its session or view out means them; one that leaves its pane out means your tab's.

Where a tab opens is its place, and its kind follows from its flags: `--split`, `--pane`, `--beside`, `--size` are a tiled place's, `--anchor`, `--width`, `--row` a floating one's; otherwise say `--kind tiled|float|view|session`. `--split right` opens it in a pane of its own beside yours; `--pane :4` opens it as a tab of pane `:4` (`--pane current` is yours), after the tab in front, and brings it there unless `--no-focus`. `pumi tab update %3 --place '{"kind": "tiled", "pane": ":4", "index": 0}'` moves a tab to the start of a pane's strip. `pumi pane focus --direction left`, `pumi pane update --zoom toggle` and `pumi pane close` act on a pane; `pumi tab focus --offset 1` goes to the next tab in yours.

| | |
| --- | --- |
| `pumi NOUN VERB [ID] [ARGS…] [--flags]` | every function: `pumi registry list fn` lists them |
| `pumi call NAME [args…]` | a function by name, when spelling it is easier |
| `pumi tab read\|input\|run\|wait\|selection\|report\|attach [TAB] [--flags]` | what a tab's block answers (`tab.call`) |
| `pumi tab list` | what runs where: tab, view, pane, block, status, title, directory |
| `pumi open PATH [--line N]` | a file in a tab: `tab open file '{"path": "PATH"}'` |
| `pumi wait [TAB]` | until the tab's program ends, with its status: `tab wait --exit` |
| `pumi event on EVENT` | events as they fire; `--match`, `--once` |
| `pumi do FILE` | run a TypeScript file with the SDK |
| `pumi help [NAME]` | every command, one a line; or a function, a tab method (`tab.read`), an event, a block, or a noun's commands |
| `pumi registry list KIND --names` | each function's (block's, option's, …) name and what it is, one a line |
| `pumi completion zsh\|bash\|fish` | what your shell completes for pumi |
| `pumi plugin new\|dev\|logs` | [Plugins](https://pumi.dev/docs/plugins/): make one, run it as you edit it, read its log |
| `pumi server list\|stop` | the server processes themselves |

Installing, updating and removing a plugin are functions: `pumi plugin add SOURCE`, `pumi plugin update PLUGIN --enabled`, `pumi plugin remove PLUGIN`, and `pumi registry list plugin` shows what there is.

An error is one object on stderr, `{"kind": "not_found", "message": "no tab %7"}`, and `pumi` exits 1. A command line that names nothing to do exits 2. `pumi wait` exits with its program's status, and `pumi do` with its script's.

Global flags: `--json`, `--server NAME` (or a socket's path), `--socket PATH`, `--headless`, `--clean`, `--version`, `--help`.

## Status

A tab's `@status` says how what runs in it is doing: `working`, `blocked`, `error`, `done`, `idle` or `unknown`. The side bar shows a row for each tab that has one. A terminal's program reports it with an escape sequence (OSC 7501), and until one does pumi reads what it says otherwise: OSC 9;4 progress is `working` (or `error`), a braille spinner at the start of its title (OSC 0/2) is `working` until it stops, and a desktop notification (OSC 9, 99, 777) is `done` until you look at the tab. A plugin's block may set its own tabs' `@status`.

pumi has no agents of its own: they are the [Agents plugin](https://github.com/pumi-dev/plugins/tree/main/agents). It opens Claude Code, Codex and pi in terminals with their hooks on, which set the tab's status, and can show each as a chat of the same session, which queues prompts while the agent works.

```sh
pumi plugin add pumi-dev/plugins/agents     # the Agents plugin
pumi call pumi_dev.agents.open claude       # Claude Code in a terminal, its hook on
pumi tab list                               # every tab: its block, status, title and directory
pumi tab read %3                            # its screen's last 50 lines, as text
printf '\e]7501;state=working\e\\'          # working | blocked | done | idle | error
```

## When the app is slow

Window ▸ Diagnostics (`diagnostics` in the palette) opens a window that measures the app once a second while it is open: each window's frames (p50, p95 and max) and the main thread's slowest work (with the source it came from), every terminal's cost and how long it took to show, the app's JS (its event loop, React's re-renders, server calls), and the CPU, GPU and memory of pumi, pumid, pumi-host and, on macOS, WindowServer. Closed, nothing is measured.

## Settings

Every setting is an option. `pumi option set` changes it, and the server keeps it with the rest of its state; `settings.json` is where you write one yourself, and edits to it apply within a second. Options can be set globally, on one session, view or tab, or for one plugin (`--scope`, `--id`, `--plugin`).

```sh
pumi option set linespace 10
pumi option get linespace
pumi registry list option --name linespace
```

```json
// ~/.config/pumi/settings.json (Windows: %APPDATA%\pumi\settings.json)
{ "options": { "ui_theme": "default", "haptics": false } }
```

The terminals' settings are kept in `~/.config/pumi/config.ghostty` instead, in [Ghostty's config syntax](https://ghostty.org/docs/config), so Ghostty's lines work as they are. Settings → Appearance writes it, imports your Ghostty config into it (Import from Ghostty), and opens it in your editor (Edit file); edits apply within a second. The keys pumi reads: `font-family` (repeated for fallbacks), `font-size`, `font-style`, `font-thicken`, `font-feature` (`-calt`/`-liga` turn ligatures off), `adjust-cell-height`, `theme`, `background`, `foreground`, `cursor-color`, `selection-background`, `palette`, `palette-generate`, `palette-harmonious`, `minimum-contrast`, `background-opacity`, `background-blur`, `cursor-style`, `cursor-style-blink`, `window-padding-x`, `window-padding-y`, `window-padding-color` (`extend`, the default, carries a full-screen program's background into the padding; `background` leaves it the theme's), `copy-on-select` and `macos-option-as-alt`. Other keys stay in the file, unused.

```ini
# ~/.config/pumi/config.ghostty
font-family = JetBrains Mono
font-family = Symbols Nerd Font Mono
font-size = 13
adjust-cell-height = 10%
cursor-style = bar
theme = Dracula
```

### General

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **New tabs open** | `new_tab` | `""` | What a new tab, split, view or session starts: a block (`pumi_dev.launcher.new`) or a plugin's `tab.new` action, by its function (`pumi_dev.agents.new_claude`). Empty, or naming nothing installed: the Launcher's list, else a shell, else the first thing installed that starts one. |

### Appearance

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Theme** | `ui_theme` | `"default"` | The theme: the whole window — its layout, its parts and its look, for the app and every plugin. pumi's own `default`, or a plugin's: a `theme.ts` in one of the directories its manifest lists under `themes`. |
| **Color scheme** | `terminal_colorscheme` | `""` | Give terminals a colour scheme of their own (one of iTerm2-Color-Schemes'), or leave empty to match the theme. pumi's own theme takes its colours from the scheme too, dark or light as the scheme's background is (Ghostty's `theme`). |

### Terminal

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Generate 256 colors from the scheme** | `terminal_palette_generate` | `false` | Make the 256-colour palette's colours 16–255 from the terminals' sixteen, background and foreground, so programs that use them match the colour scheme (Ghostty's `palette-generate`). Off keeps xterm's, which older programs assume. Colours a theme pins stay. |
| **Shade light schemes from the background** | `terminal_palette_harmonious` | `false` | On a light background, the generated colours run from the background to the foreground, as on a dark one, instead of from dark to light (Ghostty's `palette-harmonious`). |
| **Minimum text contrast** | `terminal_minimum_contrast` | `1` | Text whose contrast with what is under it is below this ratio (1 to 21, WCAG's) is lightened or darkened until it reads. 1 leaves every colour as the program gave it (Ghostty's `minimum-contrast`). |
| **Padding left and right** | `terminal_padding_x` | `4` | Space between a terminal's text and its pane's left and right edges (Ghostty's `window-padding-x`). |
| **Padding top and bottom** | `terminal_padding_y` | `2` | Space between a terminal's text and its pane's top and bottom edges (Ghostty's `window-padding-y`). |
| **Padding colour** | `terminal_padding_color` | `"extend"` | What fills the padding: the theme's background, or the colour of the cells at the screen's edge, so a program that paints its whole screen is painted to the pane's edges (Ghostty's `window-padding-color`). (`extend`, `background`) |

### Font

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Font** | `guifont` | `""` | The typeface for code: terminals, and any editor that follows it (the SDK's `useFont`; Ghostty's `font-family`). |
| **Line height** | `linespace` | `0` | How much taller than the font's own line each line of code is, in percent (Ghostty's `adjust-cell-height`): terminals, and any editor that follows it. 0 is the font's own. |
| **Fallback fonts** | `terminal_font_fallbacks` | `""` | Fonts the terminals use, in order, for characters the font doesn't have; separated by commas (Ghostty's repeated `font-family`). |
| **Font weight** | `terminal_font_style` | `"regular"` | The weight of the terminals' regular text; bold text is drawn heavier still (Ghostty's `font-style`). (`thin`, `extralight`, `light`, `regular`, `medium`, `semibold`, `bold`, `extrabold`, `black`) |
| **Thicken text** | `terminal_font_smoothing` | `false` | Thicken the terminals' text as macOS's font smoothing does (Ghostty's `font-thicken`). Off draws thinner, crisper glyphs. |
| **Ligatures** | `terminal_ligatures` | `true` | Join characters the font draws as one, as `->` or `!=` in a coding font. Off draws each as typed (Ghostty's `font-feature = -calt`). |

### Cursor

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Cursor shape** | `terminal_cursor_style` | `"block"` | The cursor's shape while the program hasn't asked for another (Ghostty's `cursor-style`). (`block`, `bar`, `underline`, `block_hollow`) |
| **Blinking cursor** | `terminal_cursor_blink` | `"off"` | Whether the cursor blinks (Ghostty's `cursor-style-blink`). (`off`, `on`) |

### Mouse

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Copy text when selected** | `terminal_copy_on_select` | `false` | Copy text to the clipboard as soon as it is selected (Ghostty's `copy-on-select`). |

### Keys

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Use Option as Alt** | `macos_option_as_alt` | `true` | Use Option for terminal shortcuts like alt+b instead of typing characters like å (Ghostty's `macos-option-as-alt`). |
| **Base keymap** | `keymap_preset` | `"default"` | Start from the shortcuts of an editor you already know, or from none. (`default`, `vscode`, `jetbrains`, `sublime_text`, `atom`, `textmate`, `emacs`, `cursor`, `none`) |
| **Key bindings** | `keymap_bindings` | `{}` | Your shortcuts that differ from the base keymap. |

### Updates

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Update automatically** | `autoupdate` | `false` | Check every few hours and download new versions in the background. |

### Plugins

| Setting | Option | Default | What it does |
| --- | --- | --- | --- |
| **Check for plugin updates automatically** | `pluginautocheck` | `false` | Check every few hours whether an installed plugin has a new version, and say so. |
| **Web Inspector** | `webview_devtools` | `true` | Inspect and debug the web pages plugins show with alt+cmd+i. |

| Option | Scope | Default | What it does |
| --- | --- | --- | --- |
| `default_size` | global | `"80x24"` | The size of a view no client shows, as WIDTHxHEIGHT. |
| `default_shell` | global | `""` | The program a terminal runs when none is given; empty is $SHELL, as a login shell. |
| `own_permissions` | global | `true` | macOS: a new terminal's program asks for privacy permissions (files, screen recording, …) as itself, so the prompt names it, not pumi; off, pumi asks and what pumi was granted covers every program it runs. |
| `base_index` | session | `0` | The index of a session's first view. |
| `worktree` | view | `""` | The git worktree a view works in, as a folder: where a terminal opened in the view without a `cwd` starts. Empty is its session's folder. |
| `history_bytes` | tab | `"4 << 20"` | Bytes of a terminal's output the core keeps: what a client attaching is sent, and what a restart restores. |
| `remain_on_exit` | tab | `false` | Keep a terminal tab, dead, when its program exits. |
| `restore_command` | tab | `""` | A line a restored terminal types into its new shell, as the program running in it set it (an agent's resume command). Dropped when the shell is in the foreground again. |
| `environment` | session | `{}` | Variables a new terminal gets, by name; a `false` value removes one. |
| `exit_empty` | global | `true` | The core exits a while after its last session closed and no one is connected. |
| `viewoptions` | global | `"scrollback,history"` | What a tab's saved state includes beyond its options and type data: `scrollback` (terminals), `history` (browsers), `cookies`. |
| `title` | tab | `""` | The tab's title: what its program said (OSC 2), its page's title, or what `select-tab -T` set. |
| `keys` | global | `{}` | The server's key tables, by table and key: each is `{call, args}`, called at the tab the key was pressed in (`root`, `prefix`, a block's own table). |
| `client_active` | client | — | The client's app is in front: one of its windows has the focus (`app.changed`); unset until it says. |
| `pane_tabs` | global | `true` | A pane holds several tabs, with a strip of them on top; off, every tab is a pane of its own, as in tmux, and a tab put into a pane splits it. |
| `worktree_dir` | global | `"~/.pumi/worktrees"` | Where a new worktree is made: `<worktree_dir>/<repository>/<name>`. `~` is the home folder. |
| `history_limit` | tab | `2000` | Lines of a terminal's scrollback kept in its saved state. |
| `automatic_rename` | view | `true` | A view without a name shows its active tab's title. |
| `sessionoptions` | global | `"sessions,views,tabs,floats,options"` | What `mksession` writes. |
| `renderers` | global | `{}` | Which plugin draws each block another plugin declares, by block: `{ "pumi_dev.terminal.shell" = "me/term" }`. Without one, its own plugin draws it; a tab's `@tab_renderer` picks one for that tab. |
| `font_size` | tab | — | The tab's font size in points; empty takes the client's. |
| `runtimepath` | global | `""` | Where plugins' files are found, comma-separated: the config directory, the plugins' site, pumi's runtime, then each enabled plugin. The host keeps it as plugins come and go. |
| `pluginindex` | global | `"https://api.pumi.dev/v1/index.json"` | The store's index: a URL or a file of the plugins `pumi registry list plugin --available` finds and `pumi plugin add OWNER/NAME` installs. Installs from it are counted (once per address a day) at the same server. |
| `buffer_limit` | global | `50` | The most paste buffers kept; past it the oldest goes. |
| `command_alias` | global | `["server-info=show-messages -JT","info=show-messages -JT"]` | Command aliases, each `alias=command line`. |
| `prefix` | session | `"C-b"` | The prefix key: it enters the `prefix` key table. |
| `prefix2` | session | `"None"` | A second prefix key. |
| `status_interval` | session | `15` | Seconds between status line redraws. |
| `status_left` | session | `"[#{session_name}] "` | The left of the default status line, a format. |
| `status_right` | session | `"\"#{=21:tab_title}\" %H:%M %d-%b-%y"` | The right of the default status line, a format. |
| `main_pane_width` | view | `"80"` | The main pane's width in the `main-vertical` layout, in cells or a percentage (`60%`). |
| `synchronize_tabs` | tab | `false` | Typing goes to every tab of the view at once. |
| `key_table` | client | `"root"` | The client's mode: the key table its keys go through. |
| `client_tab` | client | — | The tab the client's front window focuses: what its calls' current tab is, and `tab_focused`. |
| `client_visible` | client | — | The tabs the client draws, a list of ids: `tab_visible`. |
| `statusline` | session | `"#{T:status_left}#[align=right]#{T:status_right}"` | The status bar, a format. |
| `tabline` | session | `"#{W:#[range=window\|#{window_index}]#{?window_active,#[fg=TabLineSel],#[fg=TabLine]} #{window_index}:#{window_name} #[norange default]}"` | The view bar, a format; each view a `range=window\|N` so a click selects it. |
| `winbar` | tab | `""` | A bar on each tab, a format; empty shows none. |
| `titlestring` | session | `"#{session_name}"` | The window's title, a format. |
| `ui_font_size` | global | `13` | The size of the window's text: views, the side bar, pane titles, menus. Terminals' text is the Font's size. |
| `ui_inactive_pane_fade` | global | `25` | How much a terminal fades while another pane has the focus; 0 keeps every pane as bright as the focused one. |
| `ui_pane_gap` | global | `0` | Room between a view's panes and round them; 0 butts them together with a line between. |
| `ui_radius` | global | `6` | How round the corners of menus, dialogs, buttons and rows are; 0 is square. |
| `ui_accent` | global | `""` | The colour of the focus, selections and primary buttons, `#rrggbb`; empty keeps the theme's blue. |
| `ui_background_opacity` | global | `95` | How much the window's background hides what is behind it: the side bar, the title bar and the terminals' background. 100 is opaque (no transparency); lower lets the desktop show through. |
| `ui_background_blur` | global | `40` | How much what shows through a transparent background is blurred; 0 leaves it sharp. Only seen while the background opacity is under 100. |
| `ui_pane_titles` | global | `true` | A bar over each pane with its name and buttons, to drag it by. |
| `ui_pane_outline` | global | `false` | A line round the pane with the focus while its view is split. |
| `ui_pane_equalize` | global | `true` | Every pane in a view gets an equal share as one opens or closes. |
| `ui_focus_follows_mouse` | global | `false` | A pane takes the focus as the mouse moves onto it, without a click. |
| `ui_sidebar_swipe` | global | `true` | Two fingers swept left over the side bar hide it; ⌘B brings it back. |
| `ui_view_icons` | global | `3` | How many of a view's tabs show as icons beside its name; past that, a count. |
| `animate` | global | `1` | Panes glide into place as they open, close, move and zoom, and the start screen fades in. |
| `haptics` | global | `true` | A tap on a Force Touch trackpad as a switch turns or a slider steps. Nothing on a mouse. |

## Keys

A key goes first to the app's keymap, which runs window commands like `split_vertical`. If the keymap doesn't take it, the server's `keys` option binds it to any API call, after the prefix or without one. Anything left goes to the tab.

```json
{ "options": { "keymap_bindings": { "split_vertical": ["cmd+\\"], "zoom": [] } } }
```

```sh
pumi option set keys '{"prefix":{"h":{"call":"tab.focus","args":[null,{"direction":"left"}]}}}'
```

| Base keymap | Keymap |
| --- | --- |
| `default` | Default |

| Command | What it does | Default keys |
| --- | --- | --- |
| `new_window` | New window | `cmd+shift+n` |
| `close_window` | Close window | `cmd+alt+w` |
| `minimize_window` | Minimize window | `cmd+m` |
| `zoom_window` | Zoom window | — |
| `fullscreen` | Toggle full screen | `cmd+ctrl+f` |
| `new_workspace` | New session | `cmd+n` |
| `close_workspace` | Close session | — |
| `previous_workspace` | Previous session | `cmd+shift+[` |
| `next_workspace` | Next session | `cmd+shift+]` |
| `switch_workspace` | Switch to session 1–9 | — |
| `new_view` | New view | `cmd+t` |
| `new_worktree` | New worktree | — |
| `close_view` | Close view | `cmd+shift+w` |
| `reopen_closed` | Reopen closed tab | `cmd+shift+t` |
| `previous_view` | Previous view | `cmd+[` |
| `next_view` | Next view | `cmd+]` |
| `switch_view` | Switch to view 1–9 | `cmd+1..9` |
| `split_vertical` | Split right | `cmd+d` |
| `split_horizontal` | Split down | `cmd+shift+d` |
| `new_tab` | New tab in pane | — |
| `next_tab` | Next tab in pane | — |
| `previous_tab` | Previous tab in pane | — |
| `close_tab` | Close tab | `cmd+w` |
| `close_pane` | Close pane and its tabs | — |
| `zoom` | Zoom pane | `cmd+enter`, `shift+escape` |
| `clear_tab` | Clear terminal | `cmd+k` |
| `cycle_pane_next` | Next pane | — |
| `cycle_pane_previous` | Previous pane | — |
| `focus_pane_left` | Focus pane left | `cmd+alt+left` |
| `focus_pane_right` | Focus pane right | `cmd+alt+right` |
| `focus_pane_up` | Focus pane up | `cmd+alt+up` |
| `focus_pane_down` | Focus pane down | `cmd+alt+down` |
| `resize_pane_left` | Resize pane left | `cmd+alt+shift+left` |
| `resize_pane_right` | Resize pane right | `cmd+alt+shift+right` |
| `resize_pane_up` | Resize pane up | `cmd+alt+shift+up` |
| `resize_pane_down` | Resize pane down | `cmd+alt+shift+down` |
| `font_bigger` | Bigger text | `cmd+equal`, `cmd+plus` |
| `font_smaller` | Smaller text | `cmd+minus` |
| `font_reset` | Default text size | `cmd+0` |
| `reload_config` | Reload keymap | `cmd+shift+r` |
| `settings` | Open settings | `cmd+,` |
| `diagnostics` | Open diagnostics | — |
| `restart_to_update` | Restart to update | — |
| `toggle_sidebar` | Toggle sidebar | `cmd+b` |
| `toggle_pane_titles` | Toggle pane title bars | — |
| `toggle_pane_tabs` | Toggle tabs in panes | — |
| `equalize_panes` | Equalize panes | `cmd+alt+equal` |
| `toggle_auto_equalize` | Toggle equalizing panes on open and close | — |
| `quit` | Quit | `cmd+q` |

Prefix `C-b`, then:

| Key, after the prefix | What it does |
| --- | --- |
| `"` | up |
| `0 … 9` | Tab by index |

## API

The server speaks JSON-RPC 2.0 on the socket in `$PUMI` (a Unix socket; on Windows a named pipe), one message per line. These are the functions the SDK, `pumi`, keys and menus reach. `pumi registry list fn --json` prints all of them.

```sh
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tab.list","params":[{}]}' | nc -U "$PUMI"
```

An error has a `kind`, and `pumi` exits 1 with it.

| Kind | JSON-RPC code | Exit status |
| --- | --- | --- |
| `ambiguous` | -32002 | 3 |
| `cancelled` | -32003 | 130 |
| `exception` | -32000 | 1 |
| `invalid_params` | -32602 | 2 |
| `not_found` | -32001 | 3 |
| `provider_unavailable` | -32005 | 4 |
| `timeout` | -32004 | 1 |

## Sessions, tabs and panes

### `pane.close(pane)`

Close a pane and every tab in it. No id is the caller's tab's pane.

*provided by core · write*

```sh
pumi call pane.close '[":1"]'
```

### `pane.focus(pane, opts)`

Give a pane its view's focus (its front tab becomes the view's active tab), and make its view
and session current; or move from it to another: the one beside it (`direction`), so many
on (`offset`), or the one used last (`last`). No id is the caller's tab's pane. Moving out of
a zoomed pane unzooms the view; nothing there is not an error.

*provided by host · write*

```sh
pumi call pane.focus '[":2"]'
```

### `pane.get(pane)`

A pane: its view, its tabs in its strip's order, the one in front, and whether it has the
focus or is zoomed. No id is the caller's tab's pane.

*provided by core · read · returns Dict*

```sh
pumi call pane.get '[":1"]'
```

### `pane.list(opts)`

List panes: a view's, or every view's, in layout order.

*provided by core · read · returns Array*

```sh
pumi call pane.list '[{}]'
```

### `pane.update(pane, patch)`

Zoom a pane, or move its borders. No id is the caller's tab's pane.

*provided by host · write*

```sh
pumi call pane.update '[":2",{"zoom":true}]'
```

### `session.close(session)`

Close a session and its views and tabs. No id is the caller's.

*provided by core · write*

```sh
pumi call session.close '["$1"]'
```

### `session.focus(session, opts)`

Show a session in the caller's client; or move from it to another by offset, or to the last
one (`-n`, `-p`, `-l`). No id is the caller's session.

*provided by host · write*

```sh
pumi call session.focus '["$1"]'
```

### `session.get(session, opts)`

A session: its name, directory, views and active view; with `deep`, the whole tree in one call.
No id is the caller's: its tab's session, its client's, or the one used last.

*provided by core · read · returns Dict*

```sh
pumi call session.get
```

### `session.list()`

List every session, in creation order.

*provided by core · read · returns Array*

```sh
pumi call session.list
```

### `session.update(session, patch)`

Change a session's name or directory. Fires `session.changed` with what changed. No id is the
caller's session.

*provided by core · write*

```sh
pumi call session.update '["$1",{"name":"work"}]'
```

### `tab.call(tab, method, args)`

Call a method of the block in a tab: the protocol every block answers (`read`, its text;
`input`, `{text}`, `{paste}` or `{keys}`; `selection`; `report`, an OSC 7501 body), a
terminal tab's `run`, `wait` and `attach` (a block on the terminal engine), and the block's own
methods — a terminal's `respawn`, a plugin block's `<block>.<method>`, a file tab's
`<viewer>.<method>`. `tab` is the caller's own
when it is `null`.

*provided by host · forward · returns Object*

```sh
pumi call tab.call '["%3","read",{"source":"visible","lines":80}]'
```

### `tab.close(tab)`

Close a tab. Its state is kept, so it can be reopened (`tab.list({closed: true})`). No id is
the caller's.

*provided by core · write*

```sh
pumi call tab.close '["%3"]'
```

### `tab.focus(tab, opts)`

Make a tab its pane's front tab and its view's active tab, and its view and session current;
or move from it: to the next tab of its pane (`offset`), to the pane beside it (`direction`),
or to its view's tab used last (`last`). No id is the caller's tab. Moving out of a zoomed
pane unzooms the view; nothing there is not an error.

*provided by host · write*

```sh
pumi call tab.focus '["%3"]'
```

### `tab.get(tab, opts)`

A tab: its block, traits, place, title, directories, process, neighbours, focus, zoom and
options; with `state`, its saved state. No id is the caller's: `PUMI_TAB` for a program in a
tab, else its view's active tab.

*provided by core · read · returns Dict*

```sh
pumi call tab.get
```

### `tab.list(opts)`

List tabs, every one or those `opts` asks for: pane by pane in layout order within a view,
then floats and hidden tabs. `closed` lists the tabs closed lately instead.

*provided by core · read · returns Array*

```sh
pumi call tab.list '[{}]'
```

### `tab.open(block, args, place)`

Open a tab holding `block` (`null`: what a new tab holds, what the `new_tab` option names, a
block or a plugin's `tab.new` action; while it names nothing installed, the Launcher's list, a
shell or the first thing installed that starts one; refused while nothing is, as pumi has no
block of its own for one), placed as `place` says:
in a pane split from a tab's, into a pane
as one of its tabs (`{"pane": ":2"}`), in a new view or session, floating; or in an open tab
instead of its block (`{"kind": "tiled", "replace": "%1"}`), which
keeps its id, place, focus and options (`tab.changed {block}`). It is the one way anything
appears. `args` are the block's (a terminal block's `command`, `cwd` and `env`; a file's `path`;
a page's `url`), or `{"state": …}` to restore a saved state into the tab. Returns the tab's id.

*provided by host · write · returns String*

```sh
pumi call tab.open '["pumi_dev.terminal.shell",{"command":["make","test"]},{"kind":"tiled","split":"right"}]'
```

### `tab.update(tab, patch)`

Change a tab: its title, place, size, zoom, whether it was seen, and its options. Each change
fires `tab.changed` (or its view's `view.changed`) with what changed. No id is the caller's tab.

*provided by host · write*

```sh
pumi call tab.update '["%3",{"title":"tests"}]'
```

## Options and formats

### `option.get(name, opts)`

An option's value in a scope: the object's own, else the one it inherits, else the default.
A plugin's is its own store, and a `secret` one is in the OS keychain.

*provided by host · read · returns Object*

```sh
pumi call option.get '["guifont",{}]'
```

### `option.set(name, value, opts)`

Set an option on an object, in a plugin's store, or globally. `@name` options need no
declaration. Setting a type's option is acting on the tab: a browser's `url` navigates.
`null` unsets it, so it inherits again. `ttl` makes the value expire, and a report whose `seq`
is not past the last one from the same `source` is dropped.

*provided by host · write*

```sh
pumi call option.set '["@build","ok",{"scope":"global"}]'
```

## Events

### `event.emit(event, opts)`

Fire an event to every channel subscribed: mostly a plugin's own (`owner.name.done`), with
which plugins and scripts tell each other things, and the events channels have of their own
(the CLI's `plugin.reloadRequested`).

*provided by core · write*

```sh
pumi call event.emit '["owner.name.done",{"data":{"status":0},"tab":"%3"}]'
```

### `event.off(subscription)`

End a subscription (`event.on`'s handle); whether there was one.

*provided by core · write · returns Boolean*

```sh
pumi call event.off '[8]'
```

### `event.on(event, opts)`

Subscribe to events, by name or glob (`tab.*`; `*` is every event but those fired before
what they name, in `-ing`). Each one that fires goes to the caller's channel
as `{"method": "rpc.event", "params": [subscription, event]}`, until `event.off` or the
channel closes. Answers the subscription's integer handle.

*provided by core · write · returns Integer*

```sh
pumi call event.on '["tab.changed",{"match":"%3"}]'
```

### `event.wait(event, opts)`

Wait for an event, by name or glob: the call answers when one that matches fires, with the
event (`{event, match, session, view, tab, data}`), or with `null` when `timeout` passes or
the caller goes. A wait that times out is not an error.

*provided by core · write · returns Dict*

```sh
pumi call event.wait '["tab.changed",{"match":"%3","timeout":10}]'
```

## Menus and buttons

### `menu.set(path, item)`

Set a menu item: `path` names it and every menu above it, which are made as needed, and the
item is the function choosing it calls, with its title and when it shows. Setting a path
again replaces the item; `null` removes it, and a menu with everything in it.

*provided by host · write*

```sh
pumi call menu.set '["File.Build",{"fn":"owner.name.build","title":"Build"}]'
```

## Functions and calls

### `call(name, args)`

Call any function by name, as the same caller. `args` is positional (an array) or named (an
object); leaving it out calls the function with no arguments. A list of `{name, args}` is a
batch: called in order with nothing else running in between, answering every result; it stops
at the first error, which names the call, and what earlier calls did is not undone.

*provided by core · forward · returns Object*

```sh
pumi call call '["api.getInfo",[]]'
```

### `fn.set(name, spec)`

Provide a function every surface can call (`pumi call git.status`, a script's
`pumi.api["git.status"]`): calls to it from any channel come to this one as requests, with
the caller in `rpc.caller`, and its answers go back; or, for one with a `callback`, as a
`rpc.callback` notification, while the call answers `null`. It appears in `api.getInfo` with
the rest. A core function cannot be provided, and nor can one another channel provides as
someone else (another plugin, another program): it is theirs until they withdraw it. With
`runsOn: "client"` each app provides its own, for its own client. `null` withdraws it; it goes
with the channel too.

*provided by core · write*

```sh
pumi call fn.set '["hello.greet",{"desc":"Greet.","effect":"read"}]'
```

## Plugins

### `plugin.add(source, opts)`

Install a plugin: a store id (`owner/name`, or a name only one listing has), a GitHub
repository (`owner/repo`, or `owner/repo/dir` for one in a folder), a git URL, or a directory
on this machine, which is linked. The commit installed is recorded; a manifest's `minPumi`
newer than this pumi is refused. One from git with `dependencies` has them installed (`bun
install --production --no-save --ignore-scripts`), and is refused when they fail to install.
An install from GitHub is counted by the store
(`pluginindex`'s server, once per address a day). One already installed stays as it is.
Answers its id (`owner/name`).

*provided by host · write · returns String*

```sh
pumi call plugin.add '["https://github.com/someone/pumi-caffeinate",{}]'
```

### `plugin.contribute(plugin, patch)`

Change what a running plugin contributes, without starting it again: `patch` is a JSON merge
patch (RFC 7396) over its `contributes` as they are now (an object merges into an object, `null`
takes a key away, anything else replaces what was there, a list as a whole), checked as its
manifest is. Settings, the palette, keys, blocks and the window's slots follow at once. `null`
gives back what its manifest says, as the plugin stopping does; nothing is written to it.

*provided by host · write*

```sh
pumi call plugin.contribute '["owner/name",{"slots":{"tab.new":{"actions":[{"id":"claude","title":"Claude Code","icon":"sparkle","command":"open"}]}}}]'
```

### `plugin.remove(plugin, opts)`

Uninstall a plugin. A linked one's directory stays: only the link goes. What it kept stays
too, unless `data` says.

*provided by host · write*

```sh
pumi call plugin.remove '["owner/name",{}]'
```

### `plugin.update(plugin, patch)`

Patch a plugin, or refresh it from its source: a linked one is read again where it is, and a
git one is fetched and checked out at the ref it follows (or the one `version` gives), and its
dependencies are installed when they changed or are missing. What
its manifest contributes is read again, and the app starts it again. `enabled` turns it on or
off, and it stays installed.

*provided by host · write*

```sh
pumi call plugin.update '["owner/name",{"enabled":false}]'
```

## Clients, channels and servers

### `fs.list(path, opts)`

The entries of a folder on the server's machine: `~` and `~/…` are its home, a relative path
is in it, and an empty path is the home itself. Hidden entries are left out unless `hidden`,
and `recursive` lists every folder under it too; both sorted by name, case-insensitively.

*provided by host · read · returns Array*

```sh
pumi call fs.list '["~/src"]'
```

### `fs.open(path, opts)`

Open a path in its default app, or in the one `with` names; `reveal` shows it in the file
manager instead.

*provided by host · write*

```sh
pumi call fs.open '["/tmp",{"reveal":true}]'
```

### `fs.pick(opts)`

Choose files to open, or a folder: a native picker. Answers the path chosen, the paths when
it takes more than one, and `null` when it was cancelled.

*provided by host · read · returns Object*

```sh
pumi call fs.pick '[{"kind":"file","multiple":true}]'
```

### `fs.trash(paths)`

Move paths to the Trash.

*provided by host · write*

```sh
pumi call fs.trash '[["/tmp/old.log"]]'
```

## Other

### `clipboard.get()`

The clipboard's text: `null` when it holds none.

*provided by host · read · returns String*

```sh
pumi call clipboard.get
```

### `clipboard.set(text)`

Put text on the clipboard; `null` clears it.

*provided by host · write*

```sh
pumi call clipboard.set '["hello"]'
```

### `process.list(opts)`

The processes on this machine, matching the fields given: `pid`, `command` (any part of its
command line), and `cwd` (its directory). No fields matches every process.

*provided by host · read · returns Array*

```sh
pumi call process.list
```

### `registry.list(kind, opts)`

Registered entries of one kind: functions, blocks, traits and options; the key bindings; the
menus; the plugins (or the store's, with `available`); the themes; the viewers of a file
(`path`); the methods `tab.call` answers; the models; and the plugins' pages and sections in
Settings. `{name}` picks one entry.

*provided by host · read · returns Array*

```sh
pumi call registry.list '["fn",{"palette":true}]'
```

### `request.close(request, opts)`

Close a request: answer it, or dismiss it without `answer`. A notification takes no answer.
The first close wins: a request already closed is not found.

*provided by core · write*

```sh
pumi call request.close '["+7",{"answer":{"option":"allow"}}]'
```

### `request.get(request)`

An open request.

*provided by core · read · returns Dict*

```sh
pumi call request.get '["+7"]'
```

### `request.list(opts)`

The open requests, oldest first: every one, or those of `tab` and of `kind`.

*provided by core · read · returns Array*

```sh
pumi call request.list '[{"tab":"%3"}]'
```

### `request.open(request, opts)`

Put something to the user: a notification (`kind: "notification"`, no `options`), a
permission for what runs in `tab` (the `tool` and the `options` to allow or reject it), or a
question. Whoever listens to `request.opened` shows it, and anyone may answer it with
`request.close(id, {answer})`; the first answer wins, and `request.closed` says how it ended.
pumi-host posts notifications to the OS's notification centre and takes them back when they
close. Answers the request's id.

*provided by core · write · returns String*

```sh
pumi call request.open '[{"kind":"notification","title":"Build finished"}]'
```

### `view.close(view)`

Close a view and every tab in it. No id is the caller's.

*provided by core · write*

```sh
pumi call view.close '["@1"]'
```

### `view.focus(view, opts)`

Make a view its session's active view; or move from it to another of its session by offset
(`next-window` is `{"offset": 1}`), by index, or the last one. No id is the caller's view.

*provided by host · write*

```sh
pumi call view.focus '["@1"]'
```

### `view.get(view)`

A view: its session, name, index, layout of panes (each with its tabs), active tab and pane,
and zoom. No id is the caller's: its session's active view.

*provided by core · read · returns Dict*

```sh
pumi call view.get '["@1"]'
```

### `view.update(view, patch)`

Change a view: its name, index or session, layout, and zoom. Each change fires `view.changed`
with what changed. No id is the caller's view.

*provided by host · write*

```sh
pumi call view.update '["@1",{"name":"tests","zoom":":3"}]'
```

---

Source: https://pumi.dev/docs/cli/ · Index for agents: https://pumi.dev/llms.txt
