Skip to content

Plugin RPC API

Plugins are separate processes spoken to via hashicorp/go-plugin. The host owns rendering; plugins return serializable ViewData and handle actions.

Interface

type Plugin interface {
    GetMetadata() (pluginapi.PluginMetadata, error)
    Configure(ConfigureRequest) error
    GetView(ViewRequest) (ViewData, error)
    DoAction(ActionRequest) (ActionResult, error)
    Stop() error
}

Defined in pkg/pluginrpc.

Handshake

var Handshake = plugin.HandshakeConfig{
    ProtocolVersion:  1,
    MagicCookieKey:   "OMO_PLUGIN",
    MagicCookieValue: "omo-plugin",
}

A mismatch fails fast instead of obscure protocol errors.

Configure

type ConfigureRequest struct {
    Settings map[string]string
}

The host loads KeePass fields into Settings (host, port, password, custom attrs, …).

No nested secrets RPC

Do not open nested RPC back to the host secrets broker during GetView. That can deadlock net/rpc on the shared mux. Always use the config from Configure.

ViewData

type ViewData struct {
    View         string
    Title        string
    Info         string
    Status       string
    Headers      []string
    Rows         [][]string
    SelectionKey string
    ViewBindings []KeyBinding   // digit switches 0-9
    KeyBindings  []KeyBinding
    Actions      []KeyBinding   // actions column
    HelpSections []HelpSection  // "?" modal
    LogsBody     string         // non-empty → shared logs view
}

Prefer returning tables as data. Use helpers like pluginrpc.Decorate / HelpNav / HelpWithGlobal so every plugin gets consistent Global shortcuts (++R++, ++?++, ++/++, Ctrl+T, Esc).

Actions

type ActionRequest struct {
    Action  string
    View    string
    Payload map[string]string
}

type ActionResult struct {
    OK              bool
    Message         string
    Next            *ViewData
    ModalTitle      string
    ModalBody       string
    Reaction        string
    ExternalSession *ExternalSession // e.g. interactive ssh
}

Convention: view switches are actions like goto_keys, goto_containers.

Serving a plugin

Entrypoint under plugins/<name>/cmd/<name> — real plugins call:

func main() {
    pluginrpc.Serve(impl) // e.g. redis.NewService()
}

Serve wraps handshake + ServePluginMap. Prefer this over hand-rolling plugin.Serve unless you need custom options.

Global help bindings

Host-handled shortcuts appended via HelpWithGlobal:

Key Label
++R++ Refresh
++?++ Help
++/++ Filter
Ctrl+T Switch target
Esc Back / home