How the OfficeCLI `watch` Command Enables Live Preview with Auto-Refresh

The watch command starts an HTTP server with Server-Sent Events (SSE) that serves a document preview and automatically refreshes the browser whenever OfficeCLI modifies the file.

The OfficeCLI watch command provides developers with real-time visual feedback when editing Office documents programmatically. By combining a lightweight HTTP server, embedded JavaScript clients, and an inter-process notification system, the tool eliminates the need to manually reload browsers after each change.

Command Architecture and Entry Point

The watch command is declared in src/officecli/CommandBuilder.Watch.cs. The command description explicitly states its purpose: "Start a live preview server that refreshes when officecli modifies the document (external edits are not detected)" with subcommands for mark/unmark/marks/goto operations on the running preview.

When executed, the command instantiates WatchServer and begins the live preview lifecycle. The implementation deliberately limits detection to CLI-initiated changes only, avoiding the complexity of file system watchers while ensuring predictable behavior.

HTTP and Server-Sent Events Infrastructure

The core server implementation resides in src/officecli/Core/Watch/WatchServer.cs. On startup, the server loads two embedded JavaScript resources that establish the client-side refresh mechanism:

// From WatchServer.cs lines 90-99
var sseCore = LoadEmbeddedResource("watch-sse-core.js");
var overlayScript = LoadEmbeddedResource("watch-overlay.js");

These scripts—which correspond to Resources/watch-sse-core.js and Resources/watch-overlay.js—perform two critical functions:

  • SSE connection management: Opens and maintains a persistent connection to the server's /events endpoint
  • DOM injection: Receives HTML payloads and updates the preview without full page reloads

Port Discovery and Process Coordination

The watch server creates a predictable marker file under $TMPDIR with the naming pattern officecli-watch-<hash>.port. This file stores the TCP port number where the HTTP server listens.

// WatchServer.cs lines 186-204
var markerPath = Path.Combine(
    Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp",
    $"officecli-watch-{ComputeHash(documentPath)}.port");
File.WriteAllText(markerPath, port.ToString());

This marker file enables process discovery: other CLI instances can determine if a watch server is already running for a given document and route notifications appropriately.

The Notification and Refresh Cycle

Live refresh depends on WatchNotifier in src/officecli/Core/Watch/WatchNotifier.cs. When any mutating command completes—such as set, add, or remove—it calls:

// WatchNotifier.cs lines 15-26
public static async Task NotifyAsync(string documentPath, string html)
{
    var pipeName = GetPipeNameFromMarker(documentPath);
    try {
        using var pipe = new NamedPipeClientStream(".", pipeName, 
            PipeDirection.Out);
        await pipe.ConnectAsync(100); // 100ms timeout
        await WriteMessageAsync(pipe, html);
    }
    catch {
        // Fire-and-forget: ignore if no watcher running
    }
}

This fire-and-forget design ensures the CLI remains non-blocking. The notification travels through a named pipe to the watch server, which then broadcasts the HTML payload to all connected browser clients via SSE.


# Terminal 1: Start live preview

officecli watch myDoc.docx

# Terminal 2: Modify document—browser auto-refreshes

officecli set myDoc.docx /body --prop "font-size=14pt"
officecli add myDoc.docx /body/paragraph "New content"

Graceful Shutdown and Cleanup

The watch server monitors a CancellationTokenSource (_cts) that triggers on multiple termination paths:

  • Explicit unwatch command invocation
  • SIGINT/SIGTERM signals
  • Dispose pattern from the hosting context
// WatchServer.cs lines 437-494
_cts.Token.Register(() => {
    _httpListener?.Stop();
    File.Delete(markerPath);
    _namedPipeServer?.Dispose();
});

Removing the marker file on shutdown allows subsequent watch invocations to start fresh servers without port conflicts.

Practical Usage Examples

Start a live preview for any supported Office document:

officecli watch report.docx
officecli watch presentation.pptx
officecli watch workbook.xlsx

Control the running preview with subcommands:

officecli watch report.docx mark /body/paragraph[3]    # Add visual marker

officecli watch report.docx goto /body/table[1]        # Scroll to element

officecli unwatch report.docx                          # Stop server

When running in CI or automated environments, omit unwatch—the server will terminate when the parent process exits, though explicit cleanup is recommended.

Summary

  • HTTP + SSE server: WatchServer serves preview content and pushes updates via Server-Sent Events
  • Embedded JavaScript clients: watch-sse-core.js and watch-overlay.js handle browser-side refresh without page reloads
  • Port marker files: Enable cross-process discovery of running watch servers via $TMPDIR/officecli-watch-*.port
  • Named pipe notifications: WatchNotifier transmits rendered HTML through fire-and-forget messages
  • Graceful lifecycle: Cancellation token coordination ensures clean shutdown across normal and signal-based termination paths

Frequently Asked Questions

Why doesn't external file editing trigger auto-refresh?

The watch command only detects modifications made through OfficeCLI itself. According to the source in CommandBuilder.Watch.cs, this is an intentional design choice to avoid file system polling complexity and ensure the preview always reflects the CLI's internal document state.

What happens if the watch server crashes or is killed?

The marker file in $TMPDIR may persist temporarily, but subsequent watch commands will detect stale markers through connection attempts and replace them. The unwatch command includes cleanup logic for orphaned marker files.

Can multiple documents be watched simultaneously?

Yes. Each document receives a unique hash-derived marker filename and independent TCP port. The WatchServer constructor takes a document path parameter, and separate named pipes isolate communication channels between instances.

Is there a performance cost to leaving watch running?

Minimal. The server uses async I/O with low overhead when idle. The SSE connection maintains an open TCP socket but transmits data only when WatchNotifier sends updates, making it suitable for long-running development sessions.

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 →