How to Develop and Install Custom Plugins for OfficeCLI
OfficeCLI extends its core document capabilities through standalone plugin executables that communicate via JSON-L over stdout, discovered through specific directory hierarchies or PATH entries, and installed manually or via the built-in registry installer.
OfficeCLI, maintained by the iOfficeAI/OfficeCLI repository, implements a well-defined plugin protocol that allows developers to add support for legacy formats, custom exporters, and specialized document handlers. The architecture requires plugins to act as standalone executables that the main binary discovers and invokes based on file extensions and declared capabilities. This guide covers the complete workflow for developing and installing these plugins according to the official specification.
Understanding the OfficeCLI Plugin Architecture
The plugin system relies on a main binary that manages external executables through structured inter-process communication defined in plugins/plugin-protocol.md.
Plugin Discovery Mechanism
The main binary searches for plugins in a hierarchical order: environment variables first, then the user-local ~/.officecli/plugins directory, followed by bundled plugins/ directories, and finally the system PATH using the naming pattern officecli-<kind>-<ext>. This discovery sequence allows both system-wide and user-specific plugin installations without conflicts.
Plugin Kinds and Lifecycle
OfficeCLI defines three plugin kinds that determine the interaction pattern. The dump-reader kind imports legacy formats into the internal document representation, the exporter kind renders documents to external targets like PDF or EPUB, and the format-handler kind maintains long-lived sessions for interactive editing. Each kind enforces a specific contract regarding input arguments, output streams, and session duration according to the protocol specification.
The Plugin Manifest
Every plugin must emit a JSON manifest when invoked with the --info argument. This manifest declares the plugin name, version, protocol version (currently 1), supported kinds, file extensions, runtime environment, and idle_timeout_seconds budget. The main binary validates this manifest during discovery to ensure compatibility and uses it to populate help output and command availability.
Developing a Custom Plugin
Creating a plugin requires implementing the protocol contracts regardless of programming language, focusing on three core responsibilities: manifest emission, command handling, and IPC compliance.
Choosing a Plugin Kind
Most custom development starts with either a dump-reader to convert legacy formats into OfficeCLI's internal representation, or an exporter to render documents to external formats. The format-handler kind requires implementing a persistent session loop processing open, save, and close messages, making it suitable for complex editing scenarios.
Implementing the --info Flag
Your executable must handle the --info argument by outputting a single-line JSON object containing required fields. The main binary calls this during discovery to build the plugin registry. Required fields include name, version, protocol set to 1, kinds as an array, extensions as an array, and idle_timeout_seconds as an object with default values.
Handling IPC and JSON-L Protocol
Communication occurs via JSON-L lines over stdout, with each line containing exactly one JSON object. The protocol enforces UTF-8 encoding without BOM and requires specific envelope fields: protocol set to 1 and msg_type indicating the message type. Dump-readers stream batch items using commands like add and set, while exporters write target file paths upon completion.
Managing Idle Timeouts
The main binary implements an idle-watchdog that terminates unresponsive plugins exceeding their declared timeout. To prevent termination during long operations, emit heartbeat JSON objects on stderr at regular intervals shorter than the idle_timeout_seconds declared in your manifest. The watchdog monitors both stdout activity and stderr heartbeats.
Code Examples for Custom Plugins
The following examples demonstrate minimal implementations for different plugin kinds using the protocol defined in plugins/plugin-protocol.md.
Minimal Dump-Reader in C#
This C# example reads legacy .doc files and streams document construction commands as JSON-L:
using System.Text.Json;
if (args.Length > 0 && args[0] == "--info")
{
Console.WriteLine(JsonSerializer.Serialize(new {
name = "officecli-doc-minimal",
version = "0.0.1",
protocol = 1,
kinds = new[] { "dump-reader" },
extensions = new[] { ".doc" },
target = "docx",
runtime = "dotnet",
idle_timeout_seconds = new { @default = 30 }
}));
return 0;
}
// args: dump <source-file>
string sourcePath = args[1];
// Emit document construction command
var stdout = Console.Out;
stdout.WriteLine(JsonSerializer.Serialize(new {
command = "add",
parent = "/body",
type = "paragraph",
props = new { text = "Hello from .doc" }
}));
stdout.Flush();
return 0;
Minimal Exporter in Go
This Go plugin converts documents to PDF using LibreOffice while maintaining heartbeat activity on stderr:
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"time"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == "--info" {
json.NewEncoder(os.Stdout).Encode(map[string]any{
"name": "officecli-pdf-min",
"version": "0.0.1",
"protocol": 1,
"kinds": []string{"exporter"},
"extensions": []string{".pdf"},
"runtime": "go",
"idle_timeout_seconds": map[string]any{
"default": 60,
"verbs": map[string]int{"export": 120},
},
})
return
}
// Parse arguments: export <source> --out <target>
source := os.Args[2]
var target string
for i, a := range os.Args {
if a == "--out" && i+1 < len(os.Args) {
target = os.Args[i+1]
}
}
// Start heartbeat on stderr
go func() {
for {
fmt.Fprintln(os.Stderr, `{"heartbeat":true}`)
time.Sleep(20 * time.Second)
}
}()
cmd := exec.Command("soffice", "--headless", "--convert-to", "pdf",
"--outdir", "/tmp/officecli-pdf", source)
if err := cmd.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(3) // Exit code 3 indicates unsupported feature
}
// Move produced PDF to target location...
}
Format-Handler Session Management
For long-lived sessions, implement a loop reading from stdin and responding to protocol messages:
var stdin = new StreamReader(Console.OpenStandardInput(), new UTF8Encoding(false));
var stdout = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false))
{
NewLine = "\n",
AutoFlush = true,
};
while (true)
{
var line = stdin.ReadLine();
if (line == null) break;
var msg = JsonNode.Parse(line)!;
switch ((string)msg["msg_type"]!)
{
case "open":
// Load file and reply with capabilities
stdout.WriteLine(JsonSerializer.Serialize(new {
protocol = 1,
msg_type = "ok",
result = new {
capabilities = new {
commands = new[] { "get", "set", "save" },
features = Array.Empty<string>()
},
vocabulary = new {}
}
}));
break;
case "save":
// Persist changes
stdout.WriteLine("""{"protocol":1,"msg_type":"ok","result":null}""");
break;
case "close":
stdout.WriteLine("""{"protocol":1,"msg_type":"ok","result":null}""");
return;
}
}
Installing Custom Plugins for OfficeCLI
Once developed, plugins must be placed in discoverable locations or registered in the public registry to function with the main binary.
Manual Installation
Place the compiled binary in the user-local plugins directory following the structure ~/.officecli/plugins/<kind>/<ext>/plugin:
# Make executable
chmod +x officecli-doc-minimal
# Create directory structure
mkdir -p ~/.officecli/plugins/dump-reader/.doc
# Move to discovery location
mv officecli-doc-minimal ~/.officecli/plugins/dump-reader/.doc/plugin
# Verify installation
officecli plugins list
Using the Built-in Installer
For plugins published to the registry, use the CLI installer which validates SHA-256 hashes and handles placement automatically:
officecli plugins install officecli-doc-minimal
The installer contacts https://officecli.ai/plugins/registry.json, downloads the binary, and places it in ~/.officecli/plugins/. The install.sh script and npm/officecli.js wrapper in the repository demonstrate how the binary locates and manages these plugin directories.
Summary
- OfficeCLI uses a plugin protocol defined in
plugins/plugin-protocol.mdwhere standalone executables communicate via JSON-L over stdout - Plugins must implement
--infoto return a manifest declaring protocol version 1, supported kinds, and timeout budgets - Three plugin kinds exist: dump-reader, exporter, and format-handler, each with specific lifecycle contracts
- Discovery follows a hierarchy: environment variables →
~/.officecli/plugins→ bundledplugins/→ PATH - Installation methods include manual placement in the user directory or using
officecli plugins installfor registry-hosted plugins - Heartbeat messages on stderr prevent the idle-watchdog from terminating long-running operations
Frequently Asked Questions
What programming languages can I use to develop OfficeCLI plugins?
You can use any language capable of producing a standalone executable and handling stdin/stdout. The protocol only requires that your binary emit JSON-L lines and respond to --info. The repository includes examples in C# and Go, but Python, Rust, or Node.js binaries work equally well as long as they adhere to the IPC contract.
Where does OfficeCLI search for custom plugins?
The main binary searches environment variables first, then the user-local ~/.officecli/plugins directory, followed by bundled plugins/ directories, and finally the system PATH using the naming convention officecli-<kind>-<ext>. This discovery sequence is documented in plugins/plugin-protocol.md under the plugin discovery section.
How do I prevent my plugin from being killed during long operations?
Emit heartbeat JSON objects on stderr at regular intervals shorter than your declared idle_timeout_seconds. The watchdog monitors both stdout activity and stderr heartbeats; if neither occurs within the timeout window, the plugin process terminates. The Go exporter example above demonstrates this pattern using a goroutine that emits {"heartbeat":true} every 20 seconds.
Can I distribute plugins through the public OfficeCLI registry?
Yes. Submit your plugin to the registry at https://officecli.ai/plugins/registry.json including SHA-256 hashes for verification. Once published, users can install via officecli plugins install <name>. The registry schema and validation logic are referenced in the main binary source code under src/officecli/.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →