tui-plugins

Plugins for tmui and stui, open source (MIT). Another CLI's output as a table in the tool, and its commands on keys and on the command line inside it.

A plugin brings another CLI into tmui or stui: its output as a view in the tool's table, and its commands on keys and on the : line. A plugin is one file, plugin.json, and nothing runs that the file does not spell out. tmui and stui read the same files. The plugins, the format (SPEC.md, plugin.json v 1) and the checker are in sophonz-labs/tui-plugins (MIT).

Installing

tmui ext install gh-prs       # from the repository's main branch
tmui ext install ./my-plugin  # a directory with a plugin.json, or a path or URL to one
tmui ext list                 # the plugins, and what was left out of them
tmui ext remove gh-prs

stui takes the same commands: stui ext install gh-prs. The command is ext in both because stui plugin was already taken: it installs stui's orchestration skills for coding agents.

Before writing anything, install checks the file against the spec, prints every command the plugin will run, and asks y/N (-y skips the question). It writes to the tool's own directory, ~/.config/<tool>/plugins/<name>/. A plugin you put in ~/.config/tui-plugins/<name>/ by hand is read by both tools; when both directories have one of the same name, the tool's own wins.

Inside the tool, :ext lists the plugins, what each one added and what was left out, and ? shows the keys. A plugin view opens with :<name>, such as :prs. In stui, a key a plugin puts on panes works on the agents view too.

The plugins

PluginNeedsAdds
gh-prsgh:prs, the pane's repository's pull requests (P on a pane or window): o browser, d diff, c checks, C check out
gh-runsgh:gh-runs, its GitHub Actions runs (A on a pane or window): w watch in a popup, o browser, L failed logs, r rerun failed
docker-psdocker:containers: L logs, e shell, i inspect, S stop
kubectl-podskubectl:pods in the current context: L logs, d describe, e shell, D delete
git-statusgit:git-status of the pane's directory (S on a pane or window): d diff, a stage, u unstage
lazygitlazygitL on a session, window or pane, and :lazygit, in the pane's directory

A plugin whose program is not on PATH still loads, and :ext says what is missing.

Writing one

A plugin has v, name and at least one of views, actions and commands. This one lists the files in the pane's directory and opens one in less:

{
  "v": 1,
  "name": "files",
  "description": "Files in the pane's directory.",
  "views": [
    {
      "name": "files",
      "run": ["ls", "-1"],
      "format": "lines",
      "cwd": "pane"
    }
  ],
  "actions": [
    { "key": "F", "title": "files", "scopes": ["panes", "windows"], "open": "files" },
    { "key": "v", "title": "view", "scopes": ["files"], "run": ["less", "{line}"], "mode": "foreground" }
  ]
}
  • Views run a command and show its output as a table: json (an array, or one object per line), tsv with a header line, or lines. Fields of the row under the cursor are placeholders, here {line}; so are {session}, {window}, {pane}, {pane_cwd} and {pane_command}.
  • Actions are keys on some views, which run a command or open a view. Commands are :<name> [args].
  • run is argv with no shell. A placeholder fills part of one argument and never splits it, so a field holding x; rm -rf ~ runs nothing.
  • mode is background (the default), foreground (the command gets the terminal) or popup (a tmux popup). confirm asks y/N first.
  • Unknown fields are an error, so a misspelt "confirm" cannot quietly run without asking.

Some keys are reserved on every view and a manifest that binds one is invalid: j k g G h l q / : ? enter esc ctrl-b ctrl-f ctrl-r ctrl-c ctrl-e y Y and the digits. y copies the row's value (on a plugin view, its url field when it has one, else its id) and Y the whole row. A key the tool uses on its own views, or one another plugin already took there, is left out and reported in :ext; the tool's own key always wins.

Everything else (refresh, timeouts, nested JSON fields, literal braces) is in SPEC.md.

To try a plugin, install it from its directory:

tmui ext install ./my-plugin

In a clone of tui-plugins, go run ./check . checks every plugins/<name>/plugin.json the way the repository's CI does: the spec, the views each action names, no two plugins taking one : name, and a line for each in the README.

Sending one to the repository

A pull request adds the plugin's directory under plugins/ and its line in the README's table. How to send one is in CONTRIBUTING.md.