Extending OfficeCLI with a Plugin System: A Complete Developer's Guide
OfficeCLI supports a three-layer plugin architecture that enables developers to add custom format handlers, dump-readers, and exporters without modifying the core binary. The system uses language-agnostic external executables that communicate via JSON-L over standard streams, discovered at runtime through a deterministic lookup order.
OfficeCLI is a lightweight command-line tool for manipulating Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) documents natively. When you need to work with additional formats—whether legacy .doc files, proprietary formats like .hwpx, or custom export pipelines—the OfficeCLI Plugin Protocol (v1) provides a clean extension point that keeps the core binary single-file and secure.
Understanding the Plugin Architecture
OfficeCLI's design separates native document operations from extension logic across four conceptual layers:
| Layer | Responsibility | Plugin Integration |
|---|---|---|
| L1 – Read | High-level format-agnostic views (view, text, html) |
Not involved—works directly on native OOXML |
| L2 – DOM | Structured element operations (get, set, add, remove) |
Not involved—core binary manipulates native documents |
| L3 – Raw XML | Direct XPath access for edge cases | Not involved |
| Plugin Layer | New format capabilities and offline exporters | Full integration via executable spawning and JSON-L IPC |
The host never loads plugin code into memory. Instead, it launches external executables and forwards standard streams, making the system safe for sandboxed environments and compatible with any programming language.
Three Plugin Kinds
Every plugin declares one or more kinds that determine its lifecycle and communication pattern:
-
dump-reader— Converts foreign formats into native OfficeCLI targets (e.g.,.doc→.docx). Short-lived, one-shot execution. Writes JSON-L batch commands tostdout. -
exporter— Renders native documents to foreign formats (e.g.,.docx→.pdf). Short-lived, one-shot execution with simple CLI invocation. -
format-handler— Completely owns a foreign format (e.g.,.hwpx). Long-lived, session-wide with full request/response JSON-L overstdin/stdout.
Source: OfficeCLI Plugin Protocol – Motivation & Kinds
Plugin Discovery Mechanism
When OfficeCLI needs a plugin for a (kind, ext) pair, it searches in this deterministic order—first match wins:
- Environment variable —
OFFICECLI_PLUGIN_<KIND>_<EXT>(absolute path) - User plugins directory —
~/.officecli/plugins/<kind>/<ext>/plugin(.exe) - Bundled plugins directory —
<binary-dir>/plugins/<kind>/<ext>/plugin(.exe) - PATH lookup — executable named
officecli-<kind>-<ext>orofficecli-<ext>
Discovery results are cached for the process lifetime, so newly installed plugins require a fresh command invocation.
Source: Plugin Discovery section
Required Manifest and --info Protocol
Every plugin must respond to --info with a single JSON object describing its capabilities. Required fields include:
| Field | Description |
|---|---|
name |
Plugin identifier |
version |
SemVer string |
protocol |
Protocol version (currently 1) |
kinds |
Array of supported kinds |
extensions |
Array of handled file extensions |
target |
For dump-reader: the native format produced |
Optional fields advertise runtime environment, idle-timeout budgets, and vocabulary (for format-handler plugins).
{
"name": "officecli-pdf",
"version": "0.1.0",
"protocol": 1,
"kinds": ["exporter"],
"extensions": [".pdf"],
"runtime": "dotnet",
"idle_timeout_seconds": { "default": 60, "verbs": { "export": 120 } },
"supports": ["from:docx", "from:xlsx", "from:pptx"]
}
Source: Manifest examples
Idle-Timeout and Heartbeat System
Long-running plugins must periodically emit heartbeats to prevent termination:
- Emit
{"heartbeat":true}onstderrto reset the watchdog - Default timeouts from manifest; override globally with
OFFICECLI_PLUGIN_IDLE_TIMEOUT_SECONDS - Per-verb timeouts supported (e.g., longer timeout for
exportoperations)
Source: Idle-timeout watchdog
Error Handling Conventions
Plugins report structured errors via JSON on stdout ("msg_type":"error"). Exit codes convey failure categories:
| Exit Code | Meaning |
|---|---|
5 |
Protocol mismatch |
6 |
Idle timeout exceeded |
The host translates these into user-friendly messages with corrective suggestions.
Source: Exit codes & error codes
Building Plugins: Complete Examples
Dump-Reader Plugin in C#
A minimal converter that ingests .doc files and emits native document construction commands:
// officecli-doc-minimal.cs
using System.Text.Json;
if (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];
// ...parse the .doc file (library omitted)...
// Emit one JSONL command per line; flush each line.
var out = Console.Out;
out.WriteLine(JsonSerializer.Serialize(new {
command = "add",
parent = "/body",
type = "paragraph",
props = new { text = "Hello from .doc" }
}));
out.Flush();
return 0;
Source: Dump-reader example
Exporter Plugin in Go
A PDF exporter with concurrent heartbeat generation:
// officecli-pdf-min.go
package main
import (
"encoding/json"
"fmt"
"os"
"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": 30,
"verbs": map[string]int{"export": 120},
},
})
return
}
// args: export <source> --out <target>
src := 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]
}
}
// Periodic heartbeat to keep host alive.
go func() {
for {
fmt.Fprintln(os.Stderr, `{"heartbeat":true}`)
time.Sleep(20 * time.Second)
}
}()
// Delegate to actual conversion library...
// Exit 0 on success, 3 for unsupported features, etc.
}
Source: Exporter example
Using a Format-Handler Plugin
Format-handlers enable full document lifecycle management for non-native formats:
# Register custom handler via environment variable
export OFFICECLI_PLUGIN_FORMAT_HANDLER_HWPX=$HOME/.officecli/plugins/format-handler/hwpx/plugin
# Open triggers handler spawning
officecli open report.hwpx
# Operations forwarded to handler process
officecli get report.hwpx /body/p[1] --json
officecli set report.hwpx /body/p[1] --prop text="새로운 텍스트"
officecli save report.hwpx # Forces flush to handler
officecli close report.hwpx # Terminates handler session
Source: Format-handler flow
Managing Plugins with CLI Commands
OfficeCLI provides built-in plugin introspection:
# List discovered plugins with their kinds
officecli plugins list
# Validate manifest and protocol compliance
officecli plugins lint officecli-pdf
Source: Plugins command reference
Key Reference Files
| File | Purpose | Link |
|---|---|---|
plugins/plugin-protocol.md |
Complete v1 protocol specification | GitHub |
README.md (Plugins section) |
User-facing officecli plugins documentation |
Plugins command |
sdk/python/README.md |
Python SDK with automatic plugin management | Python SDK |
examples/ |
Real-world plugin invocation samples | Examples |
Summary
Extending OfficeCLI with custom plugins follows a straightforward but precise protocol:
- Three plugin kinds (
dump-reader,exporter,format-handler) cover conversion, export, and full format ownership scenarios - Language-agnostic design through standard stream IPC keeps the core secure and lightweight
- Four-tier discovery (environment, user directory, bundled directory, PATH) provides flexible deployment
- Mandatory
--infomanifest with optional heartbeat and timeout declarations enables reliable long-running operations - Structured error reporting with documented exit codes ensures graceful failure handling
Frequently Asked Questions
What programming languages can I use for OfficeCLI plugins?
Any language that produces executable binaries and handles standard I/O. The examples in the source repository include C#, Go, and Python implementations. Since the host only spawns external processes, runtime choice has no impact on compatibility.
How do I debug a plugin that isn't being discovered?
Run officecli plugins list to see discovery results. Check that your plugin responds to --info with valid JSON, verify the executable path matches the discovery order priority, and ensure file permissions allow execution.
Can a single plugin handle multiple file extensions?
Yes. The extensions field in the manifest accepts an array. A plugin can also declare multiple kinds if it implements diverse functionality (e.g., both reading and exporting a format).
What happens if my plugin exceeds its idle timeout?
The host sends SIGTERM, then SIGKILL if shutdown doesn't complete within a grace period. Plugins should emit {"heartbeat":true} on stderr every few seconds during long operations, or request extended timeouts in their manifest's idle_timeout_seconds configuration.
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 →