pumi

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 listwhat 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 EVENTevents as they fire; --match, --once
pumi do FILErun 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 --nameseach function's (block's, option's, …) name and what it is, one a line
pumi completion zsh|bash|fishwhat your shell completes for pumi
pumi plugin new|dev|logsPlugins: make one, run it as you edit it, read its log
pumi server list|stopthe 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

SettingOptionDefaultWhat it does
New tabs opennew_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

SettingOptionDefaultWhat it does
Themeui_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 schemeterminal_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

SettingOptionDefaultWhat it does
Generate 256 colors from the schemeterminal_palette_generatefalseMake 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 backgroundterminal_palette_harmoniousfalseOn 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 contrastterminal_minimum_contrast1Text 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 rightterminal_padding_x4Space between a terminal's text and its pane's left and right edges (Ghostty's window-padding-x).
Padding top and bottomterminal_padding_y2Space between a terminal's text and its pane's top and bottom edges (Ghostty's window-padding-y).
Padding colourterminal_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

SettingOptionDefaultWhat it does
Fontguifont""The typeface for code: terminals, and any editor that follows it (the SDK's useFont; Ghostty's font-family).
Line heightlinespace0How 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 fontsterminal_font_fallbacks""Fonts the terminals use, in order, for characters the font doesn't have; separated by commas (Ghostty's repeated font-family).
Font weightterminal_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 textterminal_font_smoothingfalseThicken the terminals' text as macOS's font smoothing does (Ghostty's font-thicken). Off draws thinner, crisper glyphs.
Ligaturesterminal_ligaturestrueJoin characters the font draws as one, as -> or != in a coding font. Off draws each as typed (Ghostty's font-feature = -calt).

Cursor

SettingOptionDefaultWhat it does
Cursor shapeterminal_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 cursorterminal_cursor_blink"off"Whether the cursor blinks (Ghostty's cursor-style-blink). (off, on)

Mouse

SettingOptionDefaultWhat it does
Copy text when selectedterminal_copy_on_selectfalseCopy text to the clipboard as soon as it is selected (Ghostty's copy-on-select).

Keys

SettingOptionDefaultWhat it does
Use Option as Altmacos_option_as_alttrueUse Option for terminal shortcuts like alt+b instead of typing characters like å (Ghostty's macos-option-as-alt).
Base keymapkeymap_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 bindingskeymap_bindings{}Your shortcuts that differ from the base keymap.

Updates

SettingOptionDefaultWhat it does
Update automaticallyautoupdatefalseCheck every few hours and download new versions in the background.

Plugins

SettingOptionDefaultWhat it does
Check for plugin updates automaticallypluginautocheckfalseCheck every few hours whether an installed plugin has a new version, and say so.
Web Inspectorwebview_devtoolstrueInspect and debug the web pages plugins show with alt+cmd+i.
OptionScopeDefaultWhat it does
default_sizeglobal"80x24"The size of a view no client shows, as WIDTHxHEIGHT.
default_shellglobal""The program a terminal runs when none is given; empty is $SHELL, as a login shell.
own_permissionsglobaltruemacOS: 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_indexsession0The index of a session's first view.
worktreeview""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_bytestab"4 << 20"Bytes of a terminal's output the core keeps: what a client attaching is sent, and what a restart restores.
remain_on_exittabfalseKeep a terminal tab, dead, when its program exits.
restore_commandtab""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.
environmentsession{}Variables a new terminal gets, by name; a false value removes one.
exit_emptyglobaltrueThe core exits a while after its last session closed and no one is connected.
viewoptionsglobal"scrollback,history"What a tab's saved state includes beyond its options and type data: scrollback (terminals), history (browsers), cookies.
titletab""The tab's title: what its program said (OSC 2), its page's title, or what select-tab -T set.
keysglobal{}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_activeclient—The client's app is in front: one of its windows has the focus (app.changed); unset until it says.
pane_tabsglobaltrueA 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_dirglobal"~/.pumi/worktrees"Where a new worktree is made: <worktree_dir>/<repository>/<name>. ~ is the home folder.
history_limittab2000Lines of a terminal's scrollback kept in its saved state.
automatic_renameviewtrueA view without a name shows its active tab's title.
sessionoptionsglobal"sessions,views,tabs,floats,options"What mksession writes.
renderersglobal{}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_sizetab—The tab's font size in points; empty takes the client's.
runtimepathglobal""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.
pluginindexglobal"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_limitglobal50The most paste buffers kept; past it the oldest goes.
command_aliasglobal["server-info=show-messages -JT","info=show-messages -JT"]Command aliases, each alias=command line.
prefixsession"C-b"The prefix key: it enters the prefix key table.
prefix2session"None"A second prefix key.
status_intervalsession15Seconds between status line redraws.
status_leftsession"[#{session_name}] "The left of the default status line, a format.
status_rightsession"\"#{=21:tab_title}\" %H:%M %d-%b-%y"The right of the default status line, a format.
main_pane_widthview"80"The main pane's width in the main-vertical layout, in cells or a percentage (60%).
synchronize_tabstabfalseTyping goes to every tab of the view at once.
key_tableclient"root"The client's mode: the key table its keys go through.
client_tabclient—The tab the client's front window focuses: what its calls' current tab is, and tab_focused.
client_visibleclient—The tabs the client draws, a list of ids: tab_visible.
statuslinesession"#{T:status_left}#[align=right]#{T:status_right}"The status bar, a format.
tablinesession"#{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.
winbartab""A bar on each tab, a format; empty shows none.
titlestringsession"#{session_name}"The window's title, a format.
ui_font_sizeglobal13The size of the window's text: views, the side bar, pane titles, menus. Terminals' text is the Font's size.
ui_inactive_pane_fadeglobal25How much a terminal fades while another pane has the focus; 0 keeps every pane as bright as the focused one.
ui_pane_gapglobal0Room between a view's panes and round them; 0 butts them together with a line between.
ui_radiusglobal6How round the corners of menus, dialogs, buttons and rows are; 0 is square.
ui_accentglobal""The colour of the focus, selections and primary buttons, #rrggbb; empty keeps the theme's blue.
ui_background_opacityglobal95How 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_blurglobal40How much what shows through a transparent background is blurred; 0 leaves it sharp. Only seen while the background opacity is under 100.
ui_pane_titlesglobaltrueA bar over each pane with its name and buttons, to drag it by.
ui_pane_outlineglobalfalseA line round the pane with the focus while its view is split.
ui_pane_equalizeglobaltrueEvery pane in a view gets an equal share as one opens or closes.
ui_focus_follows_mouseglobalfalseA pane takes the focus as the mouse moves onto it, without a click.
ui_sidebar_swipeglobaltrueTwo fingers swept left over the side bar hide it; ⌘B brings it back.
ui_view_iconsglobal3How many of a view's tabs show as icons beside its name; past that, a count.
animateglobal1Panes glide into place as they open, close, move and zoom, and the start screen fades in.
hapticsglobaltrueA 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 keymapKeymap
defaultDefault
CommandWhat it doesDefault keys
new_windowNew windowcmd+shift+n
close_windowClose windowcmd+alt+w
minimize_windowMinimize windowcmd+m
zoom_windowZoom window—
fullscreenToggle full screencmd+ctrl+f
new_workspaceNew sessioncmd+n
close_workspaceClose session—
previous_workspacePrevious sessioncmd+shift+[
next_workspaceNext sessioncmd+shift+]
switch_workspaceSwitch to session 1–9—
new_viewNew viewcmd+t
new_worktreeNew worktree—
close_viewClose viewcmd+shift+w
reopen_closedReopen closed tabcmd+shift+t
previous_viewPrevious viewcmd+[
next_viewNext viewcmd+]
switch_viewSwitch to view 1–9cmd+1..9
split_verticalSplit rightcmd+d
split_horizontalSplit downcmd+shift+d
new_tabNew tab in pane—
next_tabNext tab in pane—
previous_tabPrevious tab in pane—
close_tabClose tabcmd+w
close_paneClose pane and its tabs—
zoomZoom panecmd+enter, shift+escape
clear_tabClear terminalcmd+k
cycle_pane_nextNext pane—
cycle_pane_previousPrevious pane—
focus_pane_leftFocus pane leftcmd+alt+left
focus_pane_rightFocus pane rightcmd+alt+right
focus_pane_upFocus pane upcmd+alt+up
focus_pane_downFocus pane downcmd+alt+down
resize_pane_leftResize pane leftcmd+alt+shift+left
resize_pane_rightResize pane rightcmd+alt+shift+right
resize_pane_upResize pane upcmd+alt+shift+up
resize_pane_downResize pane downcmd+alt+shift+down
font_biggerBigger textcmd+equal, cmd+plus
font_smallerSmaller textcmd+minus
font_resetDefault text sizecmd+0
reload_configReload keymapcmd+shift+r
settingsOpen settingscmd+,
diagnosticsOpen diagnostics—
restart_to_updateRestart to update—
toggle_sidebarToggle sidebarcmd+b
toggle_pane_titlesToggle pane title bars—
toggle_pane_tabsToggle tabs in panes—
equalize_panesEqualize panescmd+alt+equal
toggle_auto_equalizeToggle equalizing panes on open and close—
quitQuitcmd+q

Prefix C-b, then:

Key, after the prefixWhat it does
"up
0 … 9Tab 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.

KindJSON-RPC codeExit status
ambiguous-320023
cancelled-32003130
exception-320001
invalid_params-326022
not_found-320013
provider_unavailable-320054
timeout-320041

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}]'

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"}]'