Architecture¶
flowchart TB
KP["KeePass<br/>~/.omo/secrets/omo.kdbx<br/>+ ~/.omo/keys/omo.key"]
HOST["omo host<br/>TUI · Package Manager · RPC launcher"]
R["plugin-redis"]
D["plugin-docker"]
S["plugin-s3 …"]
KP -->|"host resolves secrets"| HOST
HOST -->|"hashicorp/go-plugin"| R
HOST --> D
HOST --> S
┌─────────────────────────────────────────────────────────┐
│ KeePass ~/.omo/secrets/omo.kdbx + ~/.omo/keys/omo.key│
│ redis/prod/cache · docker/dev/local · s3/prod/main … │
└──────────────────────────┬──────────────────────────────┘
│ host resolves secrets
┌──────────────────────────▼──────────────────────────────┐
│ omo host (cmd/omo) │
│ · TUI (tview/tcell) │
│ · Package Manager + plugin launcher │
│ · Configure → GetView / DoAction over RPC │
└──────────────────────────┬──────────────────────────────┘
│ hashicorp/go-plugin (exec)
┌─────────────────┼─────────────────┐
▼ ▼ ▼
plugin-redis plugin-docker plugin-s3 …
(ViewData) (ViewData) (ViewData)
Why RPC plugins?¶
Native Go plugins (.so) break across Go versions. omo plugins are separate binaries spoken to over RPC, so:
- Host and plugins release independently
- Cross-compilation stays straightforward
- A crash in one plugin does not take down the TUI host
Configuration contract¶
- User picks a KeePass entry (or host auto-selects)
- Host loads fields into
map[string]string - Host calls
Configure - Plugin serves
GetView/DoActionusing that config
Plugins must not call back into host secrets over nested RPC during GetView — that can deadlock net/rpc on the shared mux.
Repository layout¶
cmd/omo/ # host binary + secrets CLI
internal/host/ # TUI host, RPC renderer, package manager wiring
pkg/
pluginrpc/ # RPC contract (ViewData, DoAction, …)
pluginapi/ # shared metadata / logging helpers
secrets/ # KeePass integration
ui/ # reusable TUI widgets
plugins/<name>/ # one directory per official plugin
cmd/<name>/ # plugin main (Serve)
On-disk layout¶
See On-Disk Layout.