Skip to content

Plugin Protocol

Version: 1.0.0

This document defines the JSON communication contract between mine and plugin executables. Any language can implement a mine plugin — the only requirement is a binary that reads JSON from stdin and writes JSON to stdout.

Every invocation includes a protocol_version field. Plugins should check this field and fail gracefully if the major version is unsupported.

"protocol_version": "1.0.0"

When mine triggers a hook, it sends a Context object as JSON on the plugin’s stdin:

{
"protocol_version": "1.0.0",
"type": "hook",
"stage": "preexec",
"mode": "transform",
"context": {
"command": "todo.add",
"args": ["buy milk"],
"flags": {
"priority": "high",
"due": "tomorrow"
},
"result": null,
"timestamp": "2026-01-15T10:30:00Z"
}
}

For transform mode hooks, the plugin writes a modified context to stdout:

{
"status": "ok",
"context": {
"command": "todo.add",
"args": ["buy milk"],
"flags": {
"priority": "high",
"due": "tomorrow",
"tags": "groceries"
},
"result": null,
"timestamp": "2026-01-15T10:30:00Z"
}
}

For notify mode hooks, mine does not read stdout. The plugin receives the context on stdin and performs side effects (logging, syncing, webhooks, etc.) without producing output. Notify hooks run concurrently and failures are logged but do not abort the command.

Commands traverse four hook stages:

StageWhenModePurpose
prevalidateBefore arg parsingtransformModify args before validation
preexecAfter validation, before executiontransformModify validated context
postexecAfter execution, before outputtransformModify result
notifyAfter outputnotifyFire-and-forget notifications

Plugins can register custom commands. When a user runs a plugin command (e.g. mine obsidian sync --vault notes), mine invokes the plugin binary with:

{
"protocol_version": "1.0.0",
"type": "command",
"command": "sync",
"args": ["--vault", "notes"]
}

args contains the raw, unparsed command-line arguments exactly as the user typed them. Flag parsing is the plugin’s responsibility. The flags field is not populated for command invocations.

The plugin writes raw output to stdout (not JSON). This output is displayed directly to the user.

On error, plugins should exit with a non-zero status code and write a JSON error object to stderr:

{
"status": "error",
"error": "vault not found at /path/to/vault",
"code": "VAULT_NOT_FOUND"
}

If stderr is not valid JSON, mine displays the raw stderr text as the error message.

Plugins may optionally handle lifecycle events:

EventWhenExpected Response
initmine startupNone (fire-and-forget)
shutdownmine exitNone (fire-and-forget)
healthmine plugin info{"status": "ok"}
{
"protocol_version": "1.0.0",
"type": "lifecycle",
"event": "health"
}
ModeDefaultConfigurable
Transform5sPer-hook in manifest
Notify30sPer-hook in manifest
CommandNoneN/A

If a plugin exceeds its timeout, mine kills the process and reports an error.

  • Malformed JSON response: mine logs the raw output and reports a parse error
  • Plugin crash (non-zero exit, no stderr): mine reports “plugin exited unexpectedly”
  • Empty stdout for transform hook: mine uses the original context unchanged
  • Plugin not executable: mine reports a permission error with fix instructions
  • Plugin binary missing: mine reports the plugin as broken, suggests reinstall
#!/usr/bin/env python3
import json
import sys
def main():
# Read input
input_data = json.load(sys.stdin)
# Validate protocol version
if not input_data.get("protocol_version", "").startswith("1."):
print(json.dumps({
"status": "error",
"error": "unsupported protocol version"
}), file=sys.stderr)
sys.exit(1)
# Modify context
context = input_data["context"]
if "tags" not in context["flags"]:
context["flags"]["tags"] = "auto-tagged"
# Write response
print(json.dumps({
"status": "ok",
"context": context
}))
if __name__ == "__main__":
main()
package main
import (
"encoding/json"
"log"
"os"
)
type Input struct {
ProtocolVersion string `json:"protocol_version"`
Type string `json:"type"`
Context map[string]interface{} `json:"context"`
}
func main() {
var input Input
if err := json.NewDecoder(os.Stdin).Decode(&input); err != nil {
log.Fatal(err)
}
// Perform side effect (e.g., log to file, send webhook)
log.Printf("Command executed: %v", input.Context["command"])
// No output required for notify hooks
}