How to Create Custom Commands for OfficeCLI: A Complete Plugin Development Guide
You create custom commands for OfficeCLI by implementing a plugin binary that adheres to the plugin protocol, exposing a JSON manifest via --info, and handling subcommands like open through stdin/stdout request envelopes.
OfficeCLI (iOfficeAI/OfficeCLI) is built around a core binary that discovers and delegates operations to external plugins. Each plugin implements a specific kind (docx, xlsx, pptx, etc.) and declares the file extensions it handles, allowing you to extend the CLI with domain-specific functionality without modifying the core codebase.
Understanding the OfficeCLI Plugin Architecture
OfficeCLI follows a plugin-based architecture where the core binary locates executables in specific discovery directories and communicates with them via a standardized protocol defined in /plugins/plugin-protocol.md. When the CLI needs to perform an operation on a file, it invokes the appropriate plugin binary with subcommands like dump, export, or open, then exchanges JSON request envelopes over stdin and stdout.
The core guarantees that only one request is in flight at a time, serializing operations to prevent race conditions. If a plugin returns an invalid manifest or uses an unsupported protocol version, the core aborts with exit code 5, ensuring strict compatibility.
Step-by-Step Guide to Creating Custom Commands
Choose a Document Kind
First, determine which document format your custom command targets. The kind dictates the native format the plugin produces and must match the target field in your manifest. Available kinds include:
docxfor Word documentsxlsxfor Excel spreadsheetspptxfor PowerPoint presentations- Custom kinds you define for specialized formats
This decision affects where you install the plugin and which files the CLI associates with your command.
Define the Plugin Manifest
Every plugin must output a valid JSON manifest when invoked with --info. According to /plugins/plugin-protocol.md#L168-L171, the manifest requires these fields:
name: Human-readable identifier for the pluginversion: SemVer string (e.g.,"1.0.0")protocol: Integer1(current protocol version)extensions: Array of handled extensions (e.g.,[".docx"])target: Native format produced ("docx","xlsx", or"pptx")
{
"name": "replace-placeholder",
"version": "1.0.0",
"protocol": 1,
"extensions": [".docx"],
"target": "docx"
}
The core validates this manifest at /plugins/plugin-protocol.md#L160-L173; missing required fields cause immediate termination with exit code 5.
Implement Subcommand Handlers
Custom commands typically implement the open subcommand, which starts an interactive session where the CLI sends request envelopes over stdin and expects replies over stdout. As documented in /plugins/plugin-protocol.md#L311-L358, your plugin must handle:
dump <source>: Emit a JSON-L batch for replay operationsexport <source> --out <target>: Convert to a different native formatopen <file>: Start the interactive request/response loop
For custom operations, parse the request envelope, execute your logic (accessing properties passed via --prop flags), and return one of the protocol envelopes: {"type":"ok"}, {"type":"error", ...}, or a data payload.
Package and Install the Plugin
Compile your plugin to a binary (Go, .NET, Rust, etc.) and place it in one of the discovery locations specified in /plugins/plugin-protocol.md#L140-L144:
- User plugins directory:
~/.officecli/plugins/<kind>/<ext>/plugin(.exe) - Bundled plugins directory:
<install-dir>/plugins/<kind>/<ext>/plugin(.exe)
Ensure the binary is executable. The CLI scans these directories at runtime to build the available command registry.
Complete Working Example
Here is a minimal Go plugin that implements a custom replace-placeholder command for Word documents:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Info struct {
Name string `json:"name"`
Version string `json:"version"`
Protocol int `json:"protocol"`
Extensions []string `json:"extensions"`
Target string `json:"target"`
}
func main() {
// Respond to `--info`
if len(os.Args) > 1 && os.Args[1] == "--info" {
i := Info{
Name: "replace-placeholder",
Version: "1.0.0",
Protocol: 1,
Extensions: []string{".docx"},
Target: "docx",
}
json.NewEncoder(os.Stdout).Encode(i)
os.Exit(0)
}
// Handle `open` subcommand for custom operations
if len(os.Args) > 1 && os.Args[1] == "open" {
// Read request envelope from stdin (implementation omitted)
// Perform placeholder replacement based on properties
fmt.Fprintln(os.Stdout, `{"type":"ok"}`)
os.Exit(0)
}
fmt.Fprintln(os.Stderr, "unknown command")
os.Exit(1)
}
After installing to ~/.officecli/plugins/docx/.docx/plugin, invoke your custom command:
officecli open report.docx --plugin replace-placeholder \
--prop find=«NAME» --prop replace="Acme Corp"
officecli save report.docx
The CLI forwards the request envelope to your plugin, which processes the replacement and returns ok. The change persists when you run officecli save as shown in /plugins/plugin-protocol.md#L437-L440.
Testing and Validation
Validate your plugin before distribution by running the --info check manually:
./my-plugin --info
Verify that the output JSON contains all required fields and that the protocol field equals 1. Test the discovery mechanism by placing the binary in the correct ~/.officecli/plugins/ subdirectory and running officecli plugins to confirm it appears in the list.
For debugging, examine the Node entry point at npm/officecli.js to understand how the core spawns plugin processes and handles their stdout/stderr streams.
Summary
- OfficeCLI uses a plugin protocol where external binaries handle document operations via stdin/stdout JSON envelopes.
- Plugins require a manifest output via
--infocontainingname,version,protocol: 1,extensions, andtargetfields. - Install plugins in
~/.officecli/plugins/<kind>/<ext>/pluginor alongside the main binary in<install-dir>/plugins/. - Implement the
opensubcommand to handle custom logic, reading request envelopes and returningokor error responses. - Reference the protocol specification at
/plugins/plugin-protocol.mdfor exact envelope formats and validation rules.
Frequently Asked Questions
What programming languages can I use to create OfficeCLI plugins?
You can use any language that compiles to a native executable or can be invoked as a script. Go, Rust, C#, and Python (with a wrapper) are common choices. The only requirement is that the binary must output valid JSON to stdout when invoked with --info and handle the subcommand arguments specified in the protocol.
Where does OfficeCLI look for custom plugins?
The CLI searches two discovery locations as defined in /plugins/plugin-protocol.md#L140-L144: the user directory at ~/.officecli/plugins/<kind>/<ext>/plugin(.exe) and the bundled directory at <install-dir>/plugins/<kind>/<ext>/plugin(.exe). Plugins must be organized by kind and extension subdirectories.
Why does my plugin fail with exit code 5?
Exit code 5 indicates a protocol or manifest validation error. Ensure your plugin outputs all required manifest fields (name, version, protocol, extensions, target) when invoked with --info, and verify that the protocol field is set to integer 1. Missing fields or type mismatches trigger this error at /plugins/plugin-protocol.md#L160-L173.
Can I create custom commands for multiple file types with one plugin?
No, each plugin binary targets a specific kind and extension combination. To support multiple formats (e.g., both .docx and .xlsx), create separate plugins or use symlinks with distinct manifests, placing each in the appropriate ~/.officecli/plugins/<kind>/<ext>/ directory structure.
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 →