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.
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.*'
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'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: 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. 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.
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).
pumi option set linespace 10
pumi option get linespace
pumi registry list option --name linespace
// ~/.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, 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.
# ~/.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.
{ "options": { "keymap_bindings": { "split_vertical": ["cmd+\\"], "zoom": [] } } }
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.
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
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
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
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
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
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
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
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
pumi call session.get
session.list()
List every session, in creation order.
provided by core · read · returns Array
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
pumi call fs.pick '[{"kind":"file","multiple":true}]'
fs.trash(paths)
Move paths to the Trash.
provided by host · write
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
pumi call clipboard.get
clipboard.set(text)
Put text on the clipboard; null clears it.
provided by host · write
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
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
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
pumi call request.close '["+7",{"answer":{"option":"allow"}}]'
request.get(request)
An open request.
provided by core · read · returns Dict
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
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
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
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
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
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
pumi call view.update '["@1",{"name":"tests","zoom":":3"}]'