Building integrations
mnml integrations are standalone ratatui CLIs that follow a few conventions. mnml hosts them as Pty panes via :term <binary> — no protocol to implement, no manifest to register.
They are not plugins, extensions, or scripts. There is no mnml runtime, no plugin loader, no manifest, no registration step. Each integration is a regular Rust binary that:
- Works standalone in any terminal (Terminal.app, iTerm2, tmux, ssh — anywhere).
- Works inside mnml as a Pty pane automatically — same as Claude Code, Codex, shell.
Two deployment modes
Section titled “Two deployment modes”┌─────────────────────────────────────────────────────────────────────┐│ Mode 1: Standalone ││ ││ $ mnml-db-postgres ││ → ratatui TUI in your current terminal │└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐│ Mode 2: Pty pane inside mnml ││ ││ :term mnml-db-postgres ││ → mnml spawns the binary as a Pty pane — splittable, focusable, ││ key-routed like any other pane │└─────────────────────────────────────────────────────────────────────┘Both modes require nothing from your code beyond being a normal ratatui TUI binary.
Anatomy of an integration
Section titled “Anatomy of an integration”Look at mnml-db-postgres:
mnml-db-postgres/├── Cargo.toml # deps + binary metadata├── README.md└── src/ ├── main.rs # CLI parsing + picks TUI vs --check ├── app.rs # all app state — connections, query buffer, results ├── config.rs # reads ~/.config/mnml-db-postgres.toml ├── postgres.rs # the only file unique to this integration ├── keys.rs # action enum + key → action mapping └── ui.rs # ratatui draw + event loop~1,500 lines of Rust total. The only file you really write from scratch is the one that talks to your backend (postgres.rs here; jira.rs for the Jira viewer; redis_client.rs for Redis; etc.).
Everything else is the family scaffold.
The conventions
Section titled “The conventions”These aren’t enforced by any tool, but following them makes your integration feel at home next to the others:
Naming
Section titled “Naming”mnml-<class>-<name> — e.g.:
mnml-db-postgres,mnml-db-mysql,mnml-db-sqlite,mnml-db-clickhouse,mnml-db-dynamodbmnml-tracker-jira,mnml-tracker-linear,mnml-tracker-shortcutmnml-forge-bitbucket,mnml-forge-github,mnml-forge-gitlab,mnml-forge-azdevopsmnml-aws-codebuild,mnml-aws-cloudwatch-logs,mnml-aws-amplifymnml-fs-s3,mnml-fs-gcs,mnml-fs-azureblobmnml-test-playwright,mnml-test-cypress
Whatever class makes sense. The mnml- prefix is the only “rule” — it’s how cargo search mnml- discovers them. The first-party class names today are db (databases), tracker (issue trackers), forge (code-hosting forges), aws (AWS service viewers), fs (cloud filesystems), and test (test result viewers); coin a new class when nothing fits.
Config location
Section titled “Config location”~/.config/mnml-<class>-<name>.tomlSecrets in a separate ~/.config/mnml-<class>-<name>/token file with chmod 600. The viewer should chmod 600 it for the user when it’s created.
First-run UX: when the config doesn’t exist, scaffold a template and exit with instructions. Don’t blow up.
Key chords
Section titled “Key chords”The family idiom:
| Chord | Action |
|---|---|
1-9 / Alt+1-Alt+9 | Switch tab / connection |
Tab / BackTab | Cycle tabs |
Enter / Ctrl+Enter / F5 | Run / open |
↑↓ / j k | Move selection |
g / G | Top / bottom |
r | Refresh active view |
Ctrl+U | Clear input buffer |
q / Esc / Ctrl+C | Quit |
CLI flags
Section titled “CLI flags”mnml-<thing> # launch the TUImnml-<thing> --check # print resolved config + auth state, exit 0/1--check should show: where the config came from, which connections / tabs are configured, whether auth succeeds. This is the “is my setup right?” command.
Shelling out to vendor CLIs (the AWS pattern)
Section titled “Shelling out to vendor CLIs (the AWS pattern)”Seven first-party siblings now follow a “no SDK; shell out to the vendor CLI” model: mnml-aws-codebuild, mnml-aws-cloudwatch-logs, mnml-aws-amplify, mnml-aws-lambda, mnml-aws-eventbridge, mnml-fs-s3, and mnml-db-dynamodb. The pattern is worth lifting up because it removes a lot of integration code that would otherwise be on you.
The deal: every backend call is a std::process::Command::new("aws").args([...]) subprocess. The CLI’s own credential chain — env vars (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) → shared credentials → SSO → instance role — is what authenticates the call. The viewer’s auth code is “did the subprocess exit 0?”
What you get for free:
- No SDK dep. No
aws-sdk-rust, no token refresh logic, no region resolution code. aws sso loginjust works — the viewer doesn’t manage tokens; the CLI does.- Multi-account / multi-profile — switch
AWS_PROFILEbefore launching; the active profile is the one queried. No config knob needed. - Forward compatibility — when AWS adds a new service flag, the CLI gets it and you inherit it.
What it costs:
- Subprocess latency. A cold
aws codebuild list-builds-for-projectis ~300-800ms; subsequent calls are warmer but never free. Fine for human-paced TUI use; not great for tight polling loops. - JSON parsing on every call. All
awsinvocations use--output json; youserde_json::from_slicethe stdout. Schema drift between CLI versions is a real (but rare) hazard. - You have to have the CLI installed. First-run
--checkshould verify it (which awsoraws --version).
If you’re building an integration against an AWS, GCP, Azure, or other “the vendor ships a first-class CLI” backend, this is the recommended pattern — clone mnml-aws-codebuild or mnml-fs-s3, swap the subprocess calls for your service’s commands, and you’ve skipped the auth code entirely. Don’t reach for an SDK unless you genuinely need streaming responses, long-lived connections, or sub-100ms latency.
Cross-sibling handoffs
Section titled “Cross-sibling handoffs”When one sibling’s data points at another sibling’s surface — a Lambda function’s logs live in CloudWatch, an S3 bucket’s events feed an EventBridge rule, a CodeBuild run’s artifacts land in S3 — the natural move is a single-key handoff to the relevant sibling. The first instance: mnml-aws-lambda’s l chord launches mnml-aws-cloudwatch-logs on the focused function. The mechanism is a plain std::process::Command::new("mnml-aws-cloudwatch-logs").spawn() — no IPC, no shared state, just the sibling binary path on $PATH. If you’re routing through a context (a focused function, an open bucket), pass it as a CLI flag (--log-group /aws/lambda/<fn> is the planned v0.2 of that chord). Treat the sibling like any other vendor CLI — if it’s installed it’ll resolve; if it isn’t, toast that it’s missing and move on.
Wiring a launcher chip
Section titled “Wiring a launcher chip”There are two paths — pick whichever suits your integration.
Path A: --install (recommended, mnml 0.2+)
Section titled “Path A: --install (recommended, mnml 0.2+)”Add mnml-bridge as a dep + expose a --install subcommand. Users run it once after cargo install <your-repo> and mnml auto-discovers the chip + palette command + chord on next restart (or after integrations.refresh).
[dependencies]mnml-bridge = "0.3"use anyhow::Result;use mnml_bridge::{ ChipSpec, CommandSpec, IntegrationSpec, install_integration, uninstall_integration,};
pub fn install() -> Result<()> { install_integration(&IntegrationSpec { id: "your-thing".into(), name: "Your Thing".into(), version: Some(env!("CARGO_PKG_VERSION").into()), binary: "mnml-your-thing".into(), category: Some("db".into()), // or forge/tracker/aws/fs/msg/… chip: Some(ChipSpec { glyph: "\u{F0411}".into(), // any Nerd Font glyph fallback: "Y".into(), color: "blue".into(), tooltip: Some("Open your thing".into()), enabled: true, in_palette_bar: false, badge_key: Some("your-thing".into()), }), commands: vec![CommandSpec { id: "your-thing.open".into(), title: "Your Thing: open".into(), group: Some("integrations".into()), keys: vec!["<leader>iY".into()], run: ":term mnml-your-thing".into(), }], ..Default::default() })?; Ok(())}
pub fn uninstall() -> Result<()> { uninstall_integration("your-thing")?; Ok(())}Wire it into main.rs before your auth / config loads:
if cli.install { return install::install(); }if cli.uninstall { return install::uninstall(); }install_integration writes ~/.config/mnml/integrations/your-thing.toml. Precedence in mnml: user config > manifest > built-in default, so a sibling manifest never clobbers user-authored [[ui.integration_icon]] overrides.
The SDK also exposes runtime helpers — level-tagged toasts, progress notifications, activity badges, statusline segments, OS notifications via OSC 9 / 777 — see the Bridge / Mount protocol doc’s Tier 3 section for the full API.
Path B: manual [[ui.integration_icon]] config
Section titled “Path B: manual [[ui.integration_icon]] config”For integrations you’d rather not depend on mnml-bridge for, or for users who want to customize a chip’s glyph / color, add a block to ~/.config/mnml/config.toml:
[[ui.integration_icon]]id = "your-thing"glyph = "\U000F0411" # any Nerd Font glyphfallback = "Y"command = ":term mnml-your-thing"color = "blue"tooltip = "Open your thing"See the launcher-icon strips for the field reference.
Get listed
Section titled “Get listed”Once your integration is published, send a PR to mnml adding it to Community integrations. The list page is a single Markdown file — one line per entry. The bar is low: it should build, run, and not be malware. We won’t audit your code or gate on quality.
Reference repos
Section titled “Reference repos”The fastest path is: clone the closest reference repo, replace the backend file, rename in Cargo.toml, and ship.
| Reference | What it shows |
|---|---|
| mnml-db-postgres | SQL-shaped viewer with tabbed connections + query buffer + results table |
| mnml-db-redis | Same shape but with a command playground + type-aware response rendering |
| mnml-db-docdb | NoSQL shape — find filter as JSON, results render as _id + document |
| mnml-db-clickhouse | HTTP-based backend instead of a binary driver — uses reqwest + FORMAT JSON |
| mnml-db-dynamodb | NoSQL shape with vendor-CLI auth — aws dynamodb scan + describe-table for the partition-key column |
| mnml-tracker-jira | Tab-list shape — configurable JQL tabs, open-in-browser, periodic refresh |
| mnml-aws-codebuild | Vendor-CLI shell-out reference — Builds + Logs tabs over aws subprocesses |
| mnml-fs-s3 | Object-store / tree shape — bucket tabs, prefix breadcrumb, download-to-cache |
License + ownership
Section titled “License + ownership”You own your repo. Use whatever license you want (the references are MIT). The mnml maintainers don’t require copyright assignment, won’t push to your repo, and won’t take it over. The “family” framing is purely about discoverability and shared UX conventions — there’s no legal or operational coupling.