| title | Plugin authoring | ||||
|---|---|---|---|---|---|
| status | stable | ||||
| version | 0.2.0 | ||||
| last_updated | 2026-06-12 | ||||
| source_refs |
|
||||
| owner | @rfluid | ||||
| tags |
|
Aura plugins are stand-alone executables. The host invokes them with a period flag, reads JSON from stdout, and renders a panel in the modal. There is no Rust-only ABI — write a plugin in any language that can print JSON.
This guide covers:
- The wire contract (CLI + JSON schema)
- Building the reference plugin (
aura-plugin-hello) - Installing your plugin into Aura
For the broader plugin-system rationale, see
plugin-system.md.
<your-binary> --period <all|7d|30d>- The host always passes
--period. Honour it if your data has a time dimension; otherwise mark the section asuses_period = false(see below) and the modal will hide the period pill row. - If no section in your panel sets
uses_period = true, the host reuses your previous output when the user switches periods instead of re-invoking the binary (a manual refresh still re-runs everything). Don't rely on being called once per period change. - Stdout must be a single UTF-8 JSON object. Stderr is captured and surfaced as the error message on non-zero exit.
- The host enforces a 500 ms budget per invocation. Cache or pre-aggregate anything that would push past that.
- Exit
0for success (panel rendered), non-zero for failure (panel shows your stderr as the error message).
The full type is aura_core::plugin::PluginPanel. Minimum required
fields:
{
"title": "My Plugin",
"sections": [
{
"id": "overview",
"label": "Overview",
"type": "lines",
"lines": [
{ "label": "Status", "value": "Running", "highlight": true }
]
}
]
}Section content variants (tagged by type):
type |
Extra fields | Renders as |
|---|---|---|
lines |
lines: PluginLine[] |
Key/value rows |
table |
headers, rows |
Tabular with header row |
text |
text: string |
Preformatted text block |
controls |
controls: PluginControl[] |
Interactive button rows (see below) |
PluginLine fields:
| Field | Type | Default | Notes |
|---|---|---|---|
label |
string | — | Left column |
value |
string | — | Right column |
highlight |
bool | false |
Bold + accent color |
progress |
number | null | null |
0.0–1.0; draws a fill bar under the value |
PluginRow fields (used in table sections):
| Field | Type | Default | Notes |
|---|---|---|---|
cells |
string[] | — | One cell per header |
highlight |
bool | false |
Highlight the row |
progress |
number | null | null |
0.0–1.0; trailing "Impact" bar |
PluginSection fields:
| Field | Type | Default | Notes |
|---|---|---|---|
id |
string | — | Stable identifier (preserved on tab switch) |
label |
string | — | Tab label |
uses_period |
bool | true |
Set false to hide the period pill row |
A controls section makes a panel interactive. Each control is a row
with a label, an optional dim hint line, and a set of pill buttons:
{
"id": "agents",
"label": "Agents",
"uses_period": false,
"type": "controls",
"controls": [
{
"label": "Peh",
"hint": "hooks: Stop, Notification",
"buttons": [
{ "id": "agent:Peh:tags", "label": "tags", "active": true },
{ "id": "agent:Peh:off", "label": "Off" },
{ "id": "hooks:Peh:remove", "label": "Remove", "danger": true }
]
}
]
}PluginControl fields:
| Field | Type | Default | Notes |
|---|---|---|---|
label |
string | — | Row label |
hint |
string | null | null |
Dim second line (a path, a status) |
indent |
number | 0 |
Nesting depth; inset + guide bar under parent row |
buttons |
PluginButton[] | [] |
May be empty (status-only row) |
PluginButton fields:
| Field | Type | Default | Notes |
|---|---|---|---|
id |
string | — | Opaque action id sent back to the plugin |
label |
string | — | Pill text; may be empty when icon is set |
active |
bool | false |
Accent background (current selection) |
danger |
bool | false |
Error-color label (destructive) |
icon |
string | null | null |
SVG before the label: embedded asset (icons/close.svg), abs or ~/ path |
confirm |
string | null | null |
Two-click confirm: first click shows this label armed in the error color, second fires; any other click disarms |
When the user clicks a button, the host re-invokes your binary as:
<your-binary> action <id> --period <all|7d|30d>Perform the operation, then print the full refreshed panel JSON on
stdout exactly as for a normal invocation (print {"title": ..., "error": "..."} to surface a failure; the next refresh recovers).
Two differences from panel refreshes:
- The budget is 180 s, not 500 ms — an action may legitimately block
on user interaction, e.g. opening a
zenity/kdialogfile picker. - While the action runs, the focus-loss auto-dismiss is suspended, so a dialog your plugin opens can take focus without closing the modal.
Action ids are opaque to the host: pick any encoding you like and parse it yourself. Test actions headlessly with:
aura plugin run "My Plugin" --action "agent:Peh:off"To show a friendly error in the panel without exiting non-zero:
{ "title": "My Plugin", "error": "Could not reach metrics API" }The host renders the error string in place of the panel body. Use this for expected failure modes (offline, missing config). For unexpected crashes, exit non-zero and let stderr carry the message.
The repository ships a complete example at
plugins/hello/. Build it with:
cargo build -p aura-plugin-hello --releaseThe binary lands at target/release/aura-plugin-hello. Run it
directly to see the JSON it emits:
./target/release/aura-plugin-hello --period 7dThe example is built by cargo build --workspace but not installed
by install.sh. Users opt in via aura plugin add (below) or by
dropping the binary into the user plugins dir.
There are three ways to register a plugin with Aura. Pick whichever matches your use case.
Copy a built binary into ~/.config/aura/plugins/:
aura plugin add ./target/release/aura-plugin-hello \
--name "Hello" \
--color "#22c55e"Supported flags:
| Flag | Purpose |
|---|---|
--as <filename> |
Override the destination filename |
--link |
Symlink instead of copy (Unix). Useful for dev loops |
--name <label> |
Display name in the modal |
--color <#hex> |
Accent color override |
--icon <path> |
Embedded asset name, abs path, or ~/-relative path |
Flags map 1:1 to the sidecar TOML keys; they're stored at
<plugins-dir>/<binary>.toml and persist across upgrades.
For active development, prefer --link: rebuilding the source updates
the live plugin in place without re-running aura plugin add.
~/.config/aura/plugins/ (or your OS equivalent — see below) is
scanned at every modal open. Any executable file in that directory
counts as a plugin.
mkdir -p ~/.config/aura/plugins
cp ./target/release/aura-plugin-hello ~/.config/aura/plugins/
chmod +x ~/.config/aura/plugins/aura-plugin-helloOptional metadata sidecar (same dir, same basename + .toml):
# ~/.config/aura/plugins/aura-plugin-hello.toml
name = "Hello"
color = "#22c55e"
icon = "icons/blocks.svg"Without a sidecar, the display name is derived from the binary's
filename (aura-plugin-rtk-gains → "Rtk Gains").
User plugins dir per OS:
| Platform | Path |
|---|---|
| Linux | ~/.config/aura/plugins/ |
| macOS | ~/Library/Application Support/aura/plugins/ |
| Windows | %APPDATA%\aura\plugins\ |
The classic path. Use this when the plugin lives somewhere outside the
user plugins dir (e.g. /usr/local/bin/...):
# ~/.config/aura/config.toml
[[plugins]]
name = "Hello"
command = "/usr/local/bin/aura-plugin-hello"
color = "#22c55e"[[plugins]] entries in config.toml always win over discovered
plugins with the same name, so you can also use this to override the
color or icon of a discovered plugin without removing the binary.
aura plugin list
# NAME SOURCE COMMAND
# RTK Gains config aura-plugin-rtk
# Hello discovered /home/me/.config/aura/plugins/aura-plugin-hello
aura plugin remove "Hello"
# Removed /home/me/.config/aura/plugins/aura-plugin-helloremove only deletes plugins in the user plugins dir (the
"discovered" ones). Config-file entries must be removed by editing
config.toml.
Other useful subcommands for plugin authors:
aura plugin dir # print the user-plugins directory path
aura plugin list --format json # machine-readable for scripts
aura plugin run "Hello" --period 7d
# pretty-prints the panel JSON your plugin emitted — no need to install
# and reopen the modal on every change. Combine with `--link` above for
# a tight edit/build/inspect loop.Before publishing a plugin:
- Stays under the 500 ms budget on a cold cache
- Emits valid JSON on stdout (test with
jq .) - Honours
--periodif your data has a time axis - Returns
{"error": "..."}for expected failures rather than panicking - Does not write to Aura's config or any other process's state
- Ships an
aura-plugin-<name>binary name so the derived display name reads naturally