Advise plugin runtime code to use Tribes.HTTP for outbound requests while allowing standalone test and supertest scripts to keep raw Req usage.
Tribes Plugin Template
Template for creating Tribes plugins.
Getting Started
- Click "Use this template" on GitHub to create your own repo
- Clone and rename:
git clone https://github.com/you/your-plugin.git
cd your-plugin
./scripts/rename.sh your_plugin YourPlugin
- Edit
manifest.json— set description, capabilities, requirements - Implement your plugin in
lib/your_plugin/plugin.ex - Run validation and tests:
mix deps.get
mix tribes.plugin.validate
scripts/plugin test
If you are using the shared tribes devenv, run through the local helper instead:
devenv shell -- plugin validate
devenv shell -- plugin test
devenv shell -- plugin precommit
Development
For local development alongside a Tribes checkout:
# Symlink into the host plugins directory once
cd /path/to/tribes
ln -s /path/to/your-plugin plugins/your_plugin
# Start Tribes dev server
iex --sname dev -S mix phx.server
Then edit the plugin in its own repo. In development, the host now watches symlinked external plugins and automatically:
- runs
mix compilefor Elixir/HEEx or manifest changes - runs
npm run build --prefix assetswhenassets/package.jsonis present - reloads the plugin in the running Tribes VM
- triggers a Phoenix browser reload after the rebuild finishes
That means the normal edit/compile loop is:
cd /path/to/your-plugin
mix deps.get
# in another terminal
cd /path/to/tribes
iex --sname dev -S mix phx.server
Inside the plugin repo's devenv shell, the plugin helper forwards to the host devenv automatically:
plugin validate
plugin test
plugin precommit
If your plugin does not need a custom frontend pipeline, you can skip
assets/package.json and write browser-ready files directly under assets/js
and assets/css; the host dev watcher will copy them into priv/static for
you in development.
Project Structure
your_plugin/
├── manifest.json # Plugin metadata (Nix build + runtime)
├── mix.exs # Dependencies
├── config/ # Host-backed test config
├── lib/
│ ├── your_plugin/
│ │ ├── plugin.ex # Tribes.Plugin entry point
│ │ └── application.ex # OTP supervision tree (optional)
│ └── your_plugin_web/
│ └── live/ # LiveView pages
├── assets/ # JS/CSS (one bundle per plugin)
│ ├── package.json # Optional build script used by dev + Guix packaging
│ └── package-lock.json # Optional, recommended for reproducible builds
├── priv/
│ ├── static/ # Built assets for release
│ └── repo/migrations/ # Ecto migrations
├── scripts/
│ ├── plugin # Shared devenv/non-devenv test wrapper
│ └── rename.sh
└── test/
Manifest
manifest.json declares your plugin's identity and capabilities:
{
"id": "org.example.plugins.your-plugin",
"slug": "your_plugin",
"display_name": "Your Plugin",
"entry_module": "TribeOne.TribesPlugin.YourPlugin.Plugin",
"host_api": "1",
"otp_app": "your_plugin",
"provides": ["some_capability@1"],
"requires": ["org.tribe-one.caps.ui@1"],
"enhances_with": ["org.tribe-one.caps.inference@1"]
}
- entry_module — must be
<Owner>.<Plugin>.Plugin - otp_app — required, vendor-prefixed Mix application name
- provides — capabilities this plugin makes available
- requires — hard dependencies (build fails without them)
- enhances_with — optional dependencies (plugin degrades gracefully)
See the Plugin System docs for the full specification.
The default template assumes the plugin owns a top-level page such as /your_plugin
and any views beneath it.
Sender renders its LiveView pages inside the host chrome through
Tribes.Plugin.Layouts.app, so org.tribe-one.caps.ui@1 is a hard runtime dependency.
LiveView Hooks
External plugins register browser hooks from JS bundles listed in
manifest.json assets.global_js:
window.TribesPluginHooks = window.TribesPluginHooks || {};
window.TribesPluginHooks.SenderHook = {
mounted() {}
};
The host loads plugin JS before connecting LiveSocket and does not auto-import
external plugin phoenix-colocated/<otp_app> modules into its app.js bundle.
Plugin Data
If your plugin adds Ash resources that should replicate cluster-wide, use
extensions: [AshNostrSync] on those resources and follow the host defaults:
- persisted attributes sync by default unless explicitly excluded
- synced resources should use one UUID primary key by default
AshNostrSyncrequires exactly one primary key; composite keys are not supported- synced resources get extension-managed
deleted_atsoft-delete support by default - destroy actions can use
soft_delete()when they should tombstone rows soft_delete?(false)is the opt-out if a synced resource should not use the default tombstone model
Keep resource-specific side effects explicit in actions. AshNostrSync owns the
default tombstone/filter/projection behavior, but your plugin still decides which
actions are semantic deletes and what else they should do.
Testing
The template starts with two host-backed test layers:
- Contract tests (
test/your_plugin/plugin_contract_test.exs) — manifest and runtime spec stay aligned - Page tests (
test/your_plugin/home_page_test.exs) — the plugin renders through the real host page pipeline
Run them with:
mix tribes.plugin.validate
scripts/plugin test
Use plugin test / plugin precommit for host-backed tests. The helper invokes
Mix with the host database/services and host plugin-manager paths, including
built-in providers such as tribe-one-tribes-ui. Raw mix test in a plugin checkout is
guarded and prints this guidance; mix raw_test / mix raw_precommit are
available only for unusual manual debugging when you have set the same host
environment yourself.
Building for Release
MIX_ENV=prod mix compile
npm run build --prefix assets
mkdir -p dist/your_plugin
cp -r _build/prod/lib/your_plugin/ebin dist/your_plugin/
cp -r priv dist/your_plugin/
cp manifest.json dist/your_plugin/
For Guix-based deployment, package your plugin in the guix-tribes channel and
enable it from the node config.
Licence
TODO: Choose a licence.