# How to Use OfficeCLI Python and Node.js SDKs for Resident-Pipe Communication

> Learn to use OfficeCLI Python and Node.js SDKs for resident-pipe communication. This guide covers secure subprocess connections, command serialization, and graceful shutdown. Explore iOfficeAI/OfficeCLI today.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: how-to-guide
- Published: 2026-07-27

---

**The OfficeCLI SDKs maintain a persistent subprocess connection to format-handler plugins via JSONL streams over stdin/stdout, automatically managing the open handshake, command serialization, idle-timeout watchdogs, and graceful shutdown.**

The iOfficeAI/OfficeCLI repository provides official Python and Node.js SDKs that wrap the resident-pipe protocol, enabling stateful, low-latency interaction with Office documents without spawning a new process for each operation. By abstracting the subprocess lifecycle and JSONL message framing, these libraries allow developers to send structured commands like `add`, `get`, and `save` while the heavy native plugin remains resident in memory.

## Understanding the Resident-Pipe Architecture

The resident-pipe architecture relies on **format-handler** plugins that implement a JSONL request/response protocol over standard streams. When you instantiate an SDK client, it spawns the plugin as a child process—using `subprocess.Popen` in Python or `child_process.spawn` in Node.js—and maintains that connection for the duration of the session.

Key protocol elements include:

- **Open Handshake**: The client sends `{"msg_type":"open",...}` to establish capabilities and vocabulary, and the plugin replies with supported features.
- **Message Framing**: All communication uses one JSON object per line, UTF-8 encoded, without BOM, ensuring deterministic request/response ordering.
- **Idle-Timeout Watchdog**: A watchdog monitors activity and terminates inactive plugins; long operations must emit `{"heartbeat":true}` on `stderr` to stay alive.

These mechanisms are defined in the [[`plugins/plugin-protocol.md`](https://github.com/iOfficeAI/OfficeCLI/blob/main/plugins/plugin-protocol.md)](https://github.com/iOfficeAI/OfficeCLI/blob/main/plugins/plugin-protocol.md) specification.

## Python SDK Integration with ResidentPipeClient

The Python implementation resides in [[`sdk/python/officecli.py`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/python/officecli.py)](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/python/officecli.py), exposing the `ResidentPipeClient` class to manage the persistent subprocess.

### Initializing the Client and Opening a Document

Instantiate `ResidentPipeClient` with the `plugin_cmd` array and optional `idle_timeout` override (in seconds). The constructor automatically performs the open handshake and stores the plugin's capabilities.

```python
from officecli import ResidentPipeClient

# Spawn the format-handler and perform the open handshake

client = ResidentPipeClient(
    plugin_cmd=["officecli-format-handler", "open", "report.docx"],
    idle_timeout=120,  # Overrides the manifest idle_timeout_seconds

)

```

### Sending Commands and Managing Responses

Use the `command()` method to send operations. It accepts `command`, `args`, and optional `props` parameters, serializes them to JSONL, and returns the parsed response. For long-running tasks, call `send_heartbeat()` to prevent the watchdog from terminating the plugin.

```python

# Add a paragraph to the document body

resp = client.command(
    command="add",
    args={"parent": "/body", "type": "paragraph"},
    props={"text": "Automated content from Python"},
)
print(f"Added node: {resp}")

# Retrieve an existing node

node = client.command(
    command="get",
    args={"path": "/body/paragraph[1]"},
)

# Persist changes and close the session

client.command(command="save", args={})
client.command(command="close", args={})  # Terminates the plugin process

```

Errors raise `PluginError` with `code` and `message` attributes corresponding to protocol error responses such as `invalid_argument` or `plugin_idle_timeout`.

## Node.js SDK Integration with ResidentPipe

The Node.js equivalent is implemented in [[`sdk/node/index.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js)](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js) and exports the `ResidentPipe` class.

### Creating a Persistent Pipe Connection

Configure the pipe with the `cmd` array and `idleTimeout` option. The constructor queues commands internally to guarantee strict request-response ordering as required by the protocol.

```javascript
const { ResidentPipe } = require("@officecli/sdk");

const pipe = new ResidentPipe({
  cmd: ["officecli-format-handler", "open", "presentation.pptx"],
  idleTimeout: 180,  // Seconds; relaxes the watchdog for heavy exports
});

```

### Executing Commands and Lifecycle Management

The `command()` method returns a Promise that resolves with the JSON response. Always send `save` before `close` to ensure changes are flushed to disk.

```javascript
// Add a slide to the presentation
await pipe.command({
  command: "add",
  args: { parent: "/slides", type: "slide" },
  props: { title: "Q4 Results" },
});

// Query the first slide
const slide = await pipe.command({
  command: "get",
  args: { path: "/slides/slide[1]" },
});

// Save and terminate
await pipe.command({ command: "save", args: {} });
await pipe.command({ command: "close", args: {} });

```

Call `sendHeartbeat()` during intensive operations to emit the required heartbeat on `stderr` and reset the idle timer.

## Protocol Implementation Details

### Open Handshake and Capabilities

As implemented in the SDKs, the open handshake exchanges capability metadata. You can inspect the returned vocabulary to determine which commands and properties the specific plugin supports for the opened file format.

### Idle-Timeout and Heartbeat Mechanics

Both SDKs respect the `idle_timeout_seconds` field from the plugin manifest, which can be overridden via constructor options. The watchdog resets when:

- Any byte is received on `stdout` (a response), or
- A line containing `{"heartbeat":true}` is received on `stderr`

The SDKs provide `send_heartbeat()` (Python) and `sendHeartbeat()` (Node) to emit these heartbeats manually during long computations.

### Error Handling

When a plugin returns an error response, the SDKs throw `PluginError` (Python) or reject with a `PluginError` instance (Node). These objects expose:

- `code`: Protocol error string (e.g., `node_not_found`, `save_failed`)
- `message`: Human-readable description

Handle these by restarting the client if the plugin process terminates unexpectedly.

## Summary

- **Resident-pipe architecture** maintains a persistent `format-handler` plugin process, eliminating startup overhead for multiple operations.
- **Python SDK**: Use `ResidentPipeClient` in [[`sdk/python/officecli.py`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/python/officecli.py)](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/python/officecli.py) with `command()`, `send_heartbeat()`, and `PluginError` handling.
- **Node.js SDK**: Use `ResidentPipe` in [[`sdk/node/index.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js)](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js) with async `command()` and configurable `idleTimeout`.
- **Protocol compliance**: All communication uses JSONL framing over stdin/stdout with an initial open handshake and mandatory heartbeat management.
- **Lifecycle requirements**: Always send `save` before `close` to persist changes; catch `PluginError` to handle timeouts and invalid arguments.

## Frequently Asked Questions

### What distinguishes a format-handler plugin from other plugin kinds?

A **format-handler** is a long-lived plugin kind that maintains stateful document models in memory, enabling rapid incremental updates via the resident-pipe protocol. Other kinds, such as exporters or importers, are typically short-lived and execute a single task before exiting, whereas format-handlers persist until explicitly closed.

### How do I prevent the plugin from timing out during long exports?

Set the `idle_timeout` (Python) or `idleTimeout` (Node) constructor option to a value exceeding your longest expected operation, or manually invoke `send_heartbeat()` / `sendHeartbeat()` periodically. This writes `{"heartbeat":true}` to the plugin's `stderr`, resetting the watchdog timer.

### Can I use the resident-pipe SDKs for read-only document inspection?

Yes. Open the document in read-only mode (if the plugin supports it) or simply avoid sending `save` commands. The `command()` method supports read operations like `get` without modifying the underlying file, though you must still send `close` to terminate the plugin process cleanly.

### What error codes does PluginError expose, and how should I handle them?

`PluginError` surfaces protocol-level codes such as `invalid_argument`, `node_not_found`, `plugin_idle_timeout`, and `save_failed`. Handle `plugin_idle_timeout` by recreating the client, handle `invalid_argument` by validating inputs against the capabilities returned during the open handshake, and treat `save_failed` as a critical error requiring user intervention or disk space checks.