Guide

Plugin development

Lua plugins run in-process, unsandboxed, against the host in src/plugin/host.c. The repository-side canonical API lives in lua/api/README.md — keep both in sync when you change the host.

01 — Shape

File and module layout

Each plugin is a directory whose name is the plugin id. The host loads exactly one file:

lua/plugins/examples/<id>/init.lua

There is no user plugin directory. To ship a plugin, add it under that tree (or point TRAASH_LUA_PATH at a tree that contains it) and list the id in config.plugins.

-- lua/plugins/examples/my-chip/init.lua
return {
  setup = function()
    traash.segments.mychip = function()
      return "ok"  -- empty string hides the chip
    end
    traash.on("on_bell", function()
      traash.notify("Bell")
    end)
    traash.log("my-chip: ready")
  end,
}

The file may instead return a function; that function is called as setup. Any other return value is ignored after load.

Enabling

plugins = {
  "git-status",
  "cwd-short",
  "my-chip",
}

Config accepts at most 16 plugin ids. Order is load order. A failed dofile or setup error is logged; that plugin is marked failed and skipped.

02 — Lifecycle

When plugins run

  1. Host creates the global traash table (API, empty segments, hooks, ctx).
  2. Each enabled id is loaded and setup() runs once.
  3. Every ~0.5s the status bar rebuilds (calling every traash.segments.* function) and on_tick fires.
  4. Other hooks fire from terminal/mux state in the main loop.
No hot reload. reload_config reloads config.lua (theme, keys, appearance) but does not re-run plugin setup. Change plugin code or the plugin list, then restart traa.sh.

03 — API

Host functions

SymbolRole
traash.log(msg)Write to the traa.sh log
traash.notify(msg)Log plus a desktop notification. Linux uses notify-send (transient 3.5s, replaces the previous traa.sh notification). macOS uses osascript
traash.on(event, fn)Register a hook. Multiple listeners per event are kept in a list
traash.segments.name = fnStatus chip. fn() must return a string; empty hides it. Name becomes ctx.seg.name
traash.ctx.title / traash.ctx.cwdActive pane OSC title and OSC-7 cwd (updated before status paint)
traash.set_clipboard(text)Override clipboard contents from on_copy
traash.theme_path()Filesystem path of the active theme Lua file (bundled lua dir)
traash.reload_theme()Request the host to reload the current theme next frame
traash.sessions()Array of mux session names
traash.run_action(name)Queue an action id (for example "overview", "command_palette")

Do not invent host APIs. Plugins cannot draw UI, open windows, or bind keys directly — use segments, hooks, notify, clipboard override, or run_action.

Queued actions

run_action stores a single pending name. The main loop drains it once per frame, so a second call in the same frame overwrites the first. Valid names are the action ids in the keymap, including overview.

04 — Hooks

Events

EventWhenArgument
on_tick~every 0.5s with status refresh
on_bellTerminal BEL, and only while the window is unfocused
on_command_finishedOSC 133 command-done (prompt returned)
on_pane_focusActive pane id changespane title string
on_copySelection copiedclipboard text

Inactive tabs also get an attention badge on output, BEL, or command-finished even when those hooks do not fire (for example BEL while focused).

05 — Status

Segments and ctx

Status styles are Lua files under lua/status/<id>.lua returning function(ctx) … end. Before the style runs, the host fills ctx.seg by calling every function in traash.segments. Empty strings are omitted.

ctx fieldMeaning
sessionAttached mux session name
windowActive window id
titleActive pane title
cwdOSC-7 cwd when set
hostHostname
timeHH:MM local
seg.<name>Plugin chip strings
Styles that consume chips: pills, minimal, powerline, dev, tmux read ctx.seg.*. Styles that ignore chips: compact and centered build a fixed line and never look at ctx.seg.

Keep segment functions cheap. Status and on_tick run about twice a second. Cache subprocess work (see git-status).

06 — Side effects

Clipboard and notifications

In on_copy, call traash.set_clipboard(text) to replace what the host puts on the clipboard (the copy-enhancements example trims trailing whitespace this way).

traash.notify is a short-lived desktop bubble, not a persistent inbox. On Linux it replaces the previous traa.sh notification. If notify-send is missing, the message is still logged. On macOS it uses Notification Center via osascript. Prefer rate-limiting in the plugin (the bell example waits 15 seconds).

07 — Constraints

Practical limits

08 — Examples

Bundled plugins

Enabled by default in lua/defaults/config.lua:

IdWhat it does
git-statusBranch chip via git -C cwd, cached per second
cwd-shortShort OSC-7 path chip
batteryLinux sysfs BAT0/BAT1 percentage
ssh-hintSSH badge from pane title; notify on focus
notify-on-bellDesktop notify on background BEL, 15s cooldown
autoreload-themeWatch theme file mtime; reload_theme()
session-pickerSession-count chip
copy-enhancementsTrim copy + notify size

Optional examples (not default-enabled — add them to config.plugins):

IdWhat it does
hintsStatus tip pointing at the shortcuts overlay; notifies once at setup
welcomeOne-shot welcome notification on startup

Minimal hook example

-- lua/plugins/examples/notify-on-bell/init.lua
local last = 0
return {
  setup = function()
    traash.on("on_bell", function()
      local now = os.time()
      if now - last >= 15 then
        last = now
        traash.notify("Bell in traa.sh")
      end
    end)
  end,
}