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:

  • docx for Word documents
  • xlsx for Excel spreadsheets
  • pptx for 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 plugin
  • version: SemVer string (e.g., "1.0.0")
  • protocol: Integer 1 (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 operations
  • export <source> --out <target>: Convert to a different native format
  • open <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:

  1. User plugins directory: ~/.officecli/plugins/<kind>/<ext>/plugin(.exe)
  2. 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 --info containing name, version, protocol: 1, extensions, and target fields.
  • Install plugins in ~/.officecli/plugins/<kind>/<ext>/plugin or alongside the main binary in <install-dir>/plugins/.
  • Implement the open subcommand to handle custom logic, reading request envelopes and returning ok or error responses.
  • Reference the protocol specification at /plugins/plugin-protocol.md for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →