Plugins
Add commands to the tmux-ide palette and CLI with Lua plugins: the worktree tasks example, what consent covers, and what each capability allows.
A tmux-ide plugin is a small Lua program that adds commands to the palette
(F5 in the TUI, "Plugin commands…" in the Mac app) and to
tmux-ide action. Plugins can run programs you allow, read and drive
panes, and keep a little state. They run in the daemon's plugin host,
each with its own Lua state, time limits and memory cap.
Plugins come from two places:
| Where | Runs |
|---|---|
~/.tmux-ide/init.lua | Always: it is your own file, like a shell rc |
~/.tmux-ide/plugins/<name>/ | Only after you allow it with tmux-ide plugin consent |
Try the worktree tasks example
The worktree-tasks example turns git worktrees into tasks: start one on a
new branch with its own session, open it, list it, remove it. The daemon
does the work with tmux-ide's own worktree code; the
plugin runs no programs.
tmux-ide plugin example worktree-tasks # copy it into ~/.tmux-ide/plugins/
tmux-ide plugin consent worktree-tasks # review what it asks for, then allow itConsent shows what the plugin asks to do:
worktree-tasks 1.0.0
Tasks as git worktrees: start, open and remove them from the palette, with tmux-ide worktree.
…
It asks to:
- create, open and remove git worktrees and their sessions (plain sessions, with git hooks off: no layout or hook from the repository or a branch runs)Then open the palette in a session of the repository and pick New task, Open task, Remove task or List tasks. From a shell inside tmux:
tmux-ide action run plugin.worktree-tasks.new --arg branch=fix/login
tmux-ide action run plugin.worktree-tasks.remove --arg task=fix/login # asks to confirmThe example works on the repository of the session you run it from. The
palette passes that session in the TUI and the Mac app; the CLI uses the
session of the pane it runs in, or --session <name>. It asks for neither
exec nor panes.send, so it cannot run programs or type into a pane.
Removing a task never forces: a worktree with changes is refused. Tasks
open as plain sessions: a branch's own tmux-ide layout (its
.tmux-ide/workspace.yml commands, which come from that branch) never runs
from a plugin, and git runs with hooks off, so no hook from the repository
or from the branch (say a post-checkout hook through a relative
core.hooksPath) runs either. Run tmux-ide worktree open <branch>
yourself for the layout; when you run tmux-ide worktree, hooks run as
usual.
Consent
- Consent covers the plugin's exact files: tmux-ide records a SHA-256 of the
plugin directory. If any file changes, the plugin stops at the next reload
(of any plugin) until you run
tmux-ide plugin consent <name>again. tmux-ide plugin revoke <name>stops the plugin, kills the programs it started, and withdraws consent.- Consent is the security boundary. Allow a plugin only if you trust its author, the same way you would before running their shell script.
Capabilities
A plugin's manifest.json lists what it needs under capabilities. Consent
shows each one in these words:
| Capability | Lets the plugin | Consent says |
|---|---|---|
worktree: true | Create, open, list and remove git worktrees and their sessions (ide.worktree) | "create, open and remove git worktrees and their sessions (plain sessions, with git hooks off: no layout or hook from the repository or a branch runs)" |
exec: ["jq", …] | Run the named programs with ide.exec | "run these programs: jq. A program it may run can usually do anything your account can" |
shell: true | Run any /bin/sh command line | "run ANY shell command line, with full access to your account" |
"panes.send": true | Type into panes, and run a program as if typed in a pane (ide.exec{ context = "%3" }) | "type into your terminals and run commands there, which gives full access to your account" |
Most programs give full control of your account the moment a plugin may
run them, because they can run other commands or drive tmux: tmux,
tmux-ide, shells, interpreters, git, ssh, launchctl, editors,
pagers, script, nice, time and many more. So consent calls every
program full control except a short, reviewed list that only prints or
transforms text (cat, head, tail, wc, cut, tr, jq, echo,
printf, true, false, pwd, basename, dirname, date, uname,
whoami, id, ls, stat), checked by exact name and by the file the
name resolves to:
- NOTE: git, python3 can run other commands or drive tmux, so they give full control of your accountTreat a grant of one of them like shell: true.
Every plugin, with no capability, can read what your panes show, list
panes, run tmux-ide's pane and window actions (split, zoom, select, resize,
rename, close), show a notification, and keep up to 1 MiB in its own store.
The manifest also accepts keys, sidebar and status for features that
are not built yet; consent lists them, and they do nothing today.
What programs a plugin runs can and cannot do
A program a plugin may run can usually do anything your account can: edit your shell profile, start other programs, use the network. The sandbox around it is a seat belt, not a wall:
- It cannot write under
~/.tmux-ide(or yourTMUX_IDE_HOME), except the plugin's own~/.tmux-ide/plugin-data/<name>/, and cannot read the daemon's record, claim, consent records, plugin stores or the app settings. It cannot rename or remove the state home, those files, or any directory above them either, so it cannot move them out from under these rules. - Without
panes.sendit cannot connect to your tmux server, however it finds the socket: the sandbox denies connecting to the tmux socket directories (/private/tmp/tmux-<uid>/, the one underTMUX_TMPDIR, and the daemon server's), and it does not getTMUX_TMPDIR. So a grantedtmuxortmux-ide sendcannot type into your panes. Those directories, the daemon's control socket and every directory above them cannot be renamed or removed, so the sockets cannot be moved out from under the rule. (A program can still start a tmux server of its own.) - No program can connect to the daemon's HTTP port, on any address, or to its control socket.
- It is looked up on the daemon's
PATH, never one the plugin sets, and the plugin may set onlyLANG,LC_*,TZ,TERM, colour variables,COLUMNS,LINESandPLUGIN_*variables for it. - It is stopped after its timeout (at most the callback's 30 seconds); when the command that started it fails; when the plugin fails to load, runs past its limits, is reloaded, disabled or revoked; and when the plugin host exits. A plugin stopped while its program was about to start never starts it.
On macOS the sandbox is sandbox-exec. On Linux, plugins cannot run
programs by default: the bubblewrap sandbox has not been tested on a real
Linux machine yet. To turn it on anyway, start the daemon with
TMUX_IDE_PLUGIN_EXEC_LINUX=1. Under bubblewrap, the daemon's private files
are hidden only if they exist when the program starts; a file created
later under the same name is not. The tmux socket directories are hidden
the same way (without panes.send), but the daemon's port is not blocked
there. Renames are kept in check by mounts rather than rules: the
read-only state home and the hidden paths move with any directory renamed
above them.
Write a plugin
A plugin is a directory with a manifest and a Lua entry file. This one,
~/.tmux-ide/plugins/hello/, has manifest.json:
{
"name": "hello",
"version": "0.1.0",
"api": "^1.0",
"description": "Says hello",
"capabilities": { "exec": ["git"] }
}and init.lua:
ide.action.register{
id = "branch",
title = "Show branch",
category = "Hello",
params = { { name = "repo", label = "Repository path" } },
run = function(ctx, args)
local r = ide.exec{ cmd = "git", args = { "-C", args.repo, "branch", "--show-current" } }
return "On " .. r.stdout
end,
}Then run tmux-ide plugin consent hello. ctx holds where the command was
run from: source, session when known, and pane (a tmux id like %3)
only in the TUI and the CLI. The Mac app does not pass a pane yet.
Reference and helpers:
tmux-ide lua api # every function in the plugin API
tmux-ide lua describe ide.exec # one function, with an example
tmux-ide lua run -e 'return ide.version'
tmux-ide plugin list # the host and each plugin's state
tmux-ide plugin logs hello -f # follow one plugin's log
tmux-ide plugin reload helloTitles, labels and everything a plugin shows must be plain text: no control characters, newlines or invisible formatting characters.
Limits
| Limit | Value |
|---|---|
| Lua time per callback | 250 ms |
| Wall clock per callback (and program timeout) | 30 s |
| Memory per plugin | 64 MiB |
| Timers per plugin | 64 (every at least 250 ms apart) |
| Store per plugin | 1 MiB, 1000 keys |
A plugin that runs past its limits three times within a minute is disabled
until you run tmux-ide plugin reload <name>. Commands of one plugin run
one at a time.
See also
Worktrees
Run parallel agents on parallel branches: tmux-ide worktree creates a git worktree and a ready tmux session in one command, then lists and removes them.
Workspace templates
The workspace templates shipped with tmux-ide 2.9.5, what each layout contains, and how to start a project from one with tmux-ide init --template.