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
- Host creates the global
traashtable (API, emptysegments,hooks,ctx). - Each enabled id is loaded and
setup()runs once. - Every ~0.5s the status bar rebuilds (calling every
traash.segments.*function) andon_tickfires. - Other hooks fire from terminal/mux state in the main loop.
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
| Symbol | Role |
|---|---|
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 = fn | Status chip. fn() must return a string; empty hides it. Name becomes ctx.seg.name |
traash.ctx.title / traash.ctx.cwd | Active 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
| Event | When | Argument |
|---|---|---|
on_tick | ~every 0.5s with status refresh | — |
on_bell | Terminal BEL, and only while the window is unfocused | — |
on_command_finished | OSC 133 command-done (prompt returned) | — |
on_pane_focus | Active pane id changes | pane title string |
on_copy | Selection copied | clipboard 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 field | Meaning |
|---|---|
session | Attached mux session name |
window | Active window id |
title | Active pane title |
cwd | OSC-7 cwd when set |
host | Hostname |
time | HH:MM local |
seg.<name> | Plugin chip strings |
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
- Trusted, unsandboxed Lua in the UI process — treat plugin code like the rest of the app.
- Bundled path only:
plugins/examples/<id>/init.lua. - At most 16 ids in
config.plugins. - Status refresh ~2 Hz; one queued
run_actionper frame. - No plugin hot reload; no user plugin dir;
reload_configdoes not re-init plugins. - No direct drawing. Overlays, tabs, and the overview are host UI.
08 — Examples
Bundled plugins
Enabled by default in lua/defaults/config.lua:
| Id | What it does |
|---|---|
git-status | Branch chip via git -C cwd, cached per second |
cwd-short | Short OSC-7 path chip |
battery | Linux sysfs BAT0/BAT1 percentage |
ssh-hint | SSH badge from pane title; notify on focus |
notify-on-bell | Desktop notify on background BEL, 15s cooldown |
autoreload-theme | Watch theme file mtime; reload_theme() |
session-picker | Session-count chip |
copy-enhancements | Trim copy + notify size |
Optional examples (not default-enabled — add them to config.plugins):
| Id | What it does |
|---|---|
hints | Status tip pointing at the shortcuts overlay; notifies once at setup |
welcome | One-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, }