# Custom Plugins *Extend Aura with your own commands, hooks, and tools.* ## Overview The integrations shipped in Aura (GitHub, GitLab, Linear, Jira, Slack, Discord) cover ~90% of typical stacks. For the rest — an internal code-review tool, a bespoke ticket system, a compliance pipeline, a custom orchestrator — Aura exposes a plugin system. A plugin can: - Add new CLI subcommands (e.g. `aura my-org deploy-gate`). - Register hook handlers that run on Aura events (intent logged, prove failed, zone blocked, etc.). - Expose new MCP tools to AI agents, namespaced under your plugin. - Integrate external systems by subscribing to the event stream and calling the HTTP API. - Read and write to plugin-private storage in `.aura/plugins//`. Plugins are local to a repo or installed globally. They are sandboxed: they cannot modify Aura's core DB directly — all mutations flow through the public API. ## Setup ### Scaffold a plugin ```bash aura plugin new my-plugin --lang rust # or --lang typescript | python | go ``` This generates: ``` my-plugin/ ├── aura-plugin.toml # manifest ├── src/ │ └── main.rs # entry point └── README.md ``` ### Install a plugin From a local directory: ```bash aura plugin install ./my-plugin ``` From a registry: ```bash aura plugin install aura-community/linear-advanced ``` From a git URL: ```bash aura plugin install https://github.com/acme/aura-deploy-gate ``` Installed plugins land in `~/.aura/plugins/` (global) or `.aura/plugins/` (per-repo). Per-repo overrides global. ### List and remove ```bash aura plugin list aura plugin remove my-plugin ``` ## The Manifest `aura-plugin.toml` declares everything Aura needs to load the plugin. ```toml [plugin] name = "deploy-gate" version = "0.1.0" description = "Block intent logging unless a deploy window is open." authors = ["ashiq@example.com"] license = "MIT" [plugin.runtime] kind = "rust" # rust | wasm | node | python | binary entrypoint = "target/release/deploy-gate" [plugin.api] min_aura_version = "0.14.0" [[plugin.commands]] name = "deploy-gate" description = "Configure and inspect the deploy-gate plugin." [[plugin.hooks]] event = "intent.before_log" handler = "on_intent_before_log" [[plugin.hooks]] event = "prove.failed" handler = "on_prove_failed" [[plugin.mcp_tools]] name = "deploy_gate_status" description = "Returns whether a deploy window is open." schema = "schema/status.json" [plugin.permissions] network = ["https://deploygate.internal.acme.com"] storage = "plugin_private" # plugin_private | repo_readonly | repo_readwrite read_events = ["intent.*", "prove.*"] call_api = ["intents:read", "messages:write"] ``` The `permissions` block is enforced by the Aura runtime. A plugin cannot, for example, post to a domain it didn't declare. ## Hook Points Plugins subscribe to events by name. The full catalog: | Event | Fires | |-------|-------| | `intent.before_log` | Before an intent is written. Handler can veto. | | `intent.after_log` | After an intent is written. | | `intent.mismatch` | Hook detects intent-vs-diff divergence. | | `prove.started` | `aura prove` begins. | | `prove.passed` / `prove.failed` | Prove completes. | | `pr_review.finding` | PR review surfaces a finding. | | `pr_review.done` | PR review finishes. | | `zone.before_claim` | Before a zone is claimed. Handler can veto. | | `zone.blocked` | Edit blocked by a zone. | | `live.push` / `live.pull` | Mothership sync events. | | `msg.received` | Team or sentinel message. | | `session.start` / `session.end` | Session lifecycle. | | `commit.pre` / `commit.post` | Git hook events relayed through Aura. | | `daemon.start` / `daemon.stop` | Daemon lifecycle. | Handlers receive the event envelope and can return: - `Allow` — default, continue. - `Deny(reason)` — vetoable events only. Aura aborts the operation and surfaces `reason` to the user. - `Mutate(patch)` — on events that support patching (e.g. `intent.before_log` can add ticket references). ## Examples ### Deny intent without a ticket from an internal system A plugin that rejects intents unless they cite an `OPS-` ticket that exists in the company's internal tracker: ```rust use aura_plugin::{plugin, Event, Decision, Context}; #[plugin::hook("intent.before_log")] async fn check_ticket(ctx: &Context, evt: Event) -> Decision { let summary = evt.data["summary"].as_str().unwrap_or(""); let re = regex::Regex::new(r"OPS-(\d+)").unwrap(); let Some(m) = re.captures(summary) else { return Decision::Deny("Intent must reference an OPS-### ticket.".into()); }; let ticket = &m[0]; let exists = ctx.http() .get(format!("https://ops.internal.acme.com/api/tickets/{ticket}")) .send().await .map(|r| r.status().is_success()) .unwrap_or(false); if exists { Decision::Allow } else { Decision::Deny(format!("Ticket {ticket} not found.")) } } ``` ### Post Prove failures to an internal compliance system ```typescript import { Plugin, on } from "@aura/plugin"; export default new Plugin("compliance-sink", { on: { "prove.failed": async (ctx, evt) => { await ctx.http.post("https://compliance.acme.com/api/events", { source: "aura", kind: "prove_failed", repo: evt.repo, branch: evt.branch, goal: evt.data.goal, commit: evt.data.commit, }); }, }, }); ``` ### Expose a new MCP tool Plugins can extend the MCP surface. Tools are namespaced under the plugin: ```python from aura_plugin import Plugin, mcp_tool plugin = Plugin("deploy-gate") @plugin.mcp_tool( name="deploy_gate_status", description="Returns whether a deploy window is currently open.", ) async def status(ctx, args): open = await ctx.http.get_json("https://deploygate.internal.acme.com/state") return {"window_open": open["is_open"], "closes_at": open["closes_at"]} ``` AI agents see this as `plugin_deploygate_deploy_gate_status` in their MCP tool list. ### Custom CLI subcommand A manifest-declared command dispatches to the plugin binary: ```bash aura deploy-gate status ``` The plugin receives the args via stdin as JSON and returns output on stdout. See the [scaffolded example](https://github.com/Naridon-Inc/aura-plugin-examples). ## Distribution ### The plugin registry `plugins.aura.build` hosts the community registry. Publishing requires: 1. `aura plugin publish` from the plugin repo. 2. A signed release — plugins must be signed with a key registered to the publisher account. 3. Metadata review for the first publish of a given name. Installed plugins from the registry are verified against the publisher signature every time the daemon loads them. ### Private distribution For internal plugins, skip the public registry: ```bash aura plugin install https://git.internal.acme.com/platform/aura-deploy-gate.git ``` Or pack and distribute a tarball: ```bash aura plugin pack # produces my-plugin-0.1.0.tar.gz aura plugin install ./my-plugin-0.1.0.tar.gz ``` For air-gapped environments, place the tarball in `~/.aura/plugins/` and run `aura plugin reload`. ## Community Plugins A non-exhaustive list of plugins maintained by the community or Aura contributors: | Plugin | What it does | |--------|--------------| | `aura-asana` | Asana ticket bridge — parallel to built-in Linear/Jira. | | `aura-shortcut` | Shortcut (formerly Clubhouse) integration. | | `aura-pagerduty` | Fires PagerDuty incidents on prove failures on main. | | `aura-datadog` | Emits intent and prove metrics as Datadog events. | | `aura-notion` | Mirrors intents into a Notion database. | | `aura-teams` | Microsoft Teams webhook destination. | | `aura-bazel` | Scoped prove runs for Bazel build graphs. | | `aura-monorepo-scope` | Intent/prove scoping for Turborepo and Nx. | Browse and submit at `plugins.aura.build`. ## Troubleshooting **Plugin not loading.** Check `aura doctor` — it reports manifest errors and permission violations. Logs live in `~/.aura/logs/plugins/.log`. **`permission denied: network`.** The plugin tried to call a domain not in `plugin.permissions.network`. Add it to the manifest and reinstall. **Handler slow, blocking intent log.** Vetoable hooks (`intent.before_log`, `zone.before_claim`) have a default 2-second timeout. Raise with `timeout_ms = 5000` on the hook entry, or do the work async and return `Allow` immediately. **Signature verification failed.** The publisher rotated keys. Re-install the latest version — Aura caches the new public key automatically. **Plugin crashes crash the daemon.** They shouldn't — plugins run in isolated processes for all runtimes except `binary`. If the daemon dies, the plugin is `binary` and linked into the wrong ABI. Switch to `rust`, `wasm`, `node`, or `python`. **Local plugin dev loop.** Use `aura plugin dev ./my-plugin` to hot-reload on file change without re-installing. ## Runtime Details Plugins run in language-specific runtimes managed by the daemon. **Rust plugins** compile to native binaries and run as child processes. Aura communicates with them over a JSON-RPC protocol on stdin/stdout. They are the fastest and most memory-efficient option. **WASM plugins** run inside a Wasmtime sandbox embedded in the daemon. They have the strongest isolation — no filesystem access beyond what the host explicitly grants, no arbitrary syscalls — and are the recommended choice for distribution via the public registry. **Node plugins** run under a pinned Node.js runtime shipped with Aura (currently Node 20 LTS). No external Node install is required. **Python plugins** run under a pinned CPython 3.12 interpreter. Third-party packages are declared in `pyproject.toml` and installed into a plugin-private virtualenv on install. **Binary plugins** are opaque executables. They are supported for legacy compatibility but are not sandboxed, so the registry does not accept them. ## Plugin Lifecycle 1. **Install.** `aura plugin install` verifies the manifest, checks signatures for registry plugins, provisions the runtime, and writes to `.aura/plugins//`. 2. **Load.** On daemon start (or hot-reload), each plugin is loaded, its hooks registered, and its MCP tools added to the tool table. 3. **Invoke.** Events matching a hook, or MCP tool calls to a plugin-provided tool, are routed to the plugin via JSON-RPC. 4. **Unload.** On daemon stop or `aura plugin remove`, the plugin is given 5 seconds to flush state, then terminated. 5. **Upgrade.** `aura plugin upgrade` pulls a newer version, verifies it, and swaps atomically; in-flight calls complete on the old version. ## Observability Every plugin invocation is logged with latency, input size, and output size. Surface them via: ```bash aura plugin stats # recent invocations, p50/p95/p99 latency aura plugin logs my-plugin # tail the plugin's stderr ``` High-latency plugins block whatever they hook. Prove-failed hooks that take 10 seconds delay user feedback by 10 seconds. Keep hook handlers under 500ms wherever possible; offload heavy work to async background tasks. ## Testing Plugins The `aura-plugin` crate (Rust) and `@aura/plugin` package (TypeScript) include test harnesses that let you drive hooks synthetically: ```rust #[tokio::test] async fn test_ticket_check_rejects_missing() { let harness = aura_plugin::test::Harness::new(); let evt = harness.synth_event("intent.before_log", json!({ "summary": "just a cleanup" })); let decision = check_ticket(&harness.ctx(), evt).await; assert!(matches!(decision, Decision::Deny(_))); } ``` The harness mocks the HTTP client, the Aura API, and the event envelope. Real integration tests run under `aura plugin dev` with a disposable daemon. ## See Also - [MCP server](/mcp-server) — the surface plugins can extend with new tools. - [Aura HTTP API](/aura-api-reference) — what plugins call under the hood. - [Slack / Discord webhooks](/slack-discord-webhooks) — an alternative for simple event forwarding. - [Linear / Jira integration](/linear-jira-integration) — reference implementation patterns.