How to Add New Commands to OfficeCLI: Extending the CommandBuilder Pattern
To add a new command to OfficeCLI, create a static method in the partial class CommandBuilder that returns a configured System.CommandLine.Command object, then register it in the RootCommand assembly located in Program.cs.
OfficeCLI is a .NET-based command-line interface for Office documents that leverages the System.CommandLine library with a modular CommandBuilder pattern. If you need to extend OfficeCLI with custom verbs, understanding the CommandBuilder partial class architecture in the src/officecli directory is essential. This guide walks through the exact implementation pattern used in the iOfficeAI/OfficeCLI repository, providing runnable code examples that mirror the built-in save, refresh, and plugins commands.
Understanding the CommandBuilder Architecture
OfficeCLI organizes its command-line interface through a partial static class CommandBuilder located in the OfficeCli namespace. Rather than defining all verbs in a single file, the architecture splits each command into its own partial class file, such as CommandBuilder.Save.cs, CommandBuilder.Refresh.cs, and CommandBuilder.Plugins.cs.
The Partial Class Pattern
Each command file implements a private static method following the naming convention Build{Command}Command. These methods return a Command object configured with arguments, options, and action handlers. This separation ensures that adding new commands never requires modifying existing command logic—you simply create a new file and register the method in Program.cs.
Command Structure Standards
Every command builder method follows a consistent template:
- Define arguments using
Argument<T>for positional parameters - Define options using
Option<T>for flags like--json - Compose a
Commandinstance with a description - Call
SetActionto register the handler, typically wrapped with theSafeRunhelper for consistent error handling - Return the constructed
Command
Step-by-Step: Adding a New Command to OfficeCLI
Step 1 - Create the Command Builder Method
Create a new file in src/officecli/ following the existing naming convention. For example, to add a hello command, create CommandBuilder.Hello.cs:
// src/officecli/CommandBuilder.Hello.cs
// -------------------------------------------------
using System.CommandLine;
namespace OfficeCli;
static partial class CommandBuilder
{
private static Command BuildHelloCommand(Option<bool> jsonOption)
{
var nameArg = new Argument<string>("name")
{
Description = "Name to greet"
};
var helloCmd = new Command("hello", "Print a friendly greeting.");
helloCmd.Add(nameArg);
helloCmd.Add(jsonOption);
helloCmd.SetAction(result => {
var json = result.GetValue(jsonOption);
var name = result.GetValue(nameArg)!;
var msg = $"Hello, {name}!";
if (json)
Console.WriteLine(OutputFormatter.WrapEnvelopeText(msg));
else
Console.WriteLine(msg);
return 0;
});
return helloCmd;
}
}
Step 2 - Register in the Root Command
After creating the builder method, expose the command by adding it to the RootCommand in Program.cs:
// src/officecli/Program.cs (excerpt)
// -------------------------------------------------
var root = new RootCommand("OfficeCLI – command‑line interface for Office documents");
// Existing commands
root.AddCommand(CommandBuilder.BuildSaveCommand(jsonOption));
root.AddCommand(CommandBuilder.BuildRefreshCommand(jsonOption));
// ...
// Register the new command
root.AddCommand(CommandBuilder.BuildHelloCommand(jsonOption));
return await root.InvokeAsync(args);
Consistent Error Handling and JSON Output
All built-in commands in OfficeCLI support a --json flag for machine-readable output and use the SafeRun helper for uniform error handling. According to the source code in CommandBuilder.Save.cs, you should:
- Accept the shared
Option<bool> jsonOptionparameter in your builder method - Add the option to your command via
helloCmd.Add(jsonOption) - Check the flag value inside the
SetActiondelegate - Use
OutputFormatter.WrapEnvelopeText()when JSON mode is enabled - Wrap your logic with
SafeRun(omitted in the example above for brevity) to ensure exceptions are caught and formatted consistently with other commands
Testing Your New Command with SDK Smoke Tests
After implementing a new command, validate it through the SDK smoke test harness. The iOfficeAI/OfficeCLI repository maintains test suites in:
sdk/node/smoke.js(Node.js SDK tests)sdk/python/smoke.py(Python SDK tests)
Add a corresponding test entry that invokes your new command and verifies the exit code and output format. This ensures CI pipelines catch regressions and that the command works correctly through the SDK wrappers.
Summary
- Create a new partial class file following the
CommandBuilder.{Command}.csnaming convention insrc/officecli/ - Implement a
private static Command Build{Command}Command(Option<bool> jsonOption)method that constructs and returns aCommandobject - Configure arguments using
Argument<T>and options usingOption<T> - Call
SetActionto register the handler, referencing thejsonOptionvalue for consistent output formatting - Register the new command in
Program.csviaroot.AddCommand(CommandBuilder.Build{Command}Command(jsonOption)) - Add smoke tests in
sdk/node/smoke.jsorsdk/python/smoke.pyto verify CLI behavior and exit codes
Frequently Asked Questions
What is the CommandBuilder pattern in OfficeCLI?
The CommandBuilder pattern is a modular architecture using a partial static class CommandBuilder where each command verb is implemented as a separate static method returning a System.CommandLine.Command object. This pattern, located in the src/officecli directory, separates concerns by placing each verb's logic in its own file (e.g., CommandBuilder.Save.cs) while maintaining a consistent interface for argument parsing and error handling across the entire CLI.
Do I need to modify existing files to add a new command?
No. You create a new file for your command logic (e.g., CommandBuilder.Hello.cs) since CommandBuilder is declared as a partial class. However, you must modify Program.cs to register the new command by calling your builder method and adding the returned Command to the RootCommand instance using root.AddCommand().
How does OfficeCLI handle JSON output for new commands?
All commands share a common Option<bool> jsonOption that enables structured output. According to the source code in files like src/officecli/CommandBuilder.Save.cs, you should add this option to your command and check its value inside the SetAction handler. When enabled, wrap output using OutputFormatter.WrapEnvelopeText() to ensure consistent JSON formatting across the CLI.
Where should I add tests for custom OfficeCLI commands?
After implementing a new command, add corresponding smoke tests in the SDK test harnesses located at sdk/node/smoke.js and sdk/python/smoke.py. These tests validate that the command responds correctly to invocation and returns the expected exit codes, ensuring CI/CD pipelines catch regressions and that the command integrates properly with the Node.js and Python SDKs.
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 →