DocxReviewer parses DOCX in Node, lets agents comment, propose tracked changes, batch LLM output, export .docx. Server-side, no DOM.
The headless transport for @eigenpal/docx-editor-agents. DocxReviewer parses a DOCX buffer, lets you comment / suggest tracked changes / accept / reject programmatically, and serializes back to .docx. No DOM, no React, runs on Node, edge, or any JavaScript runtime.
npm install @eigenpal/docx-editor-agents
DocxReviewer addresses paragraphs by index; the agent bridge wraps it with paraId.DocxReviewer is the low-level primitive: one process, one buffer, one batch. Indices map directly to the parsed Document.body.paragraphs[i] and don't drift because the buffer doesn't change under you. The agent toolkit speaks one tool catalog across transports though, so createReviewerBridge(reviewer) exposes the same paraId-based EditorBridge the live editor exposes (resolving paraId to index at call time). Use indices when your code drives the reviewer; wrap with the bridge when an LLM does.
Parse a DOCX, mutate it, write it back. The DocxReviewer itself is index-addressed:
import { DocxReviewer } from '@eigenpal/docx-editor-agents';const reviewer = await DocxReviewer.fromBuffer(buffer, 'AI Reviewer');// Read: plain text with paragraph indices for LLM promptsconst text = reviewer.getContentAsText();// [0] Introduction// [1] The liability cap is $50k per incident.// [2] (table, row 1, col 1) Revenue ...// Comment on a paragraph (anchors to whole paragraph)reviewer.addComment(1, 'This cap seems too low.');// Replace text (creates a tracked change)reviewer.replace(1, '$50k', '$500k');// Or batch from an LLM JSON responsereviewer.applyReview({ comments: [{ paragraphIndex: 1, text: 'Too low.' }], proposals: [{ paragraphIndex: 1, search: '$50k', replaceWith: '$500k' }],});// Export back to .docxconst output = await reviewer.toBuffer();
When an LLM drives the reviewer, wrap it. createReviewerBridge(reviewer) returns an EditorBridge whose methods take paraId instead of paragraphIndex, the same surface the live editor exposes, so one tool catalog, one set of prompts, both transports.
import { DocxReviewer, createReviewerBridge } from '@eigenpal/docx-editor-agents';const reviewer = await DocxReviewer.fromBuffer(buffer, 'AI Reviewer');const bridge = createReviewerBridge(reviewer);const text = bridge.getContentAsText();// [4F2A0001] Introduction// [4F2A0002] The liability cap is $50k per incident.bridge.addComment({ paraId: '4F2A0002', text: 'Too low.', search: '$50k' });bridge.proposeChange({ paraId: '4F2A0002', search: '$50k', replaceWith: '$500k' });const output = await reviewer.toBuffer();
The bridge resolves paraId to paragraphIndex internally on each call, so you keep the static-buffer guarantees while the agent talks paraIds.
Get document content as plain text for LLM prompts. Each paragraph is prefixed with its index: [0] text, [1] text, etc. Table cells include position: [5] (table, row 1, col 2) cell text. Avoids JSON quote-escaping issues — LLMs can copy text verbatim.
addComment(paragraphIndex: number, text: string): number
Add a comment on a paragraph.
addComment(options: AddCommentOptions): number
(overload)
replyTo(commentId: number, text: string): number
Reply to an existing comment.
replyTo(commentId: number, options: ReplyOptions): number
(overload)
removeComment(commentId: number): void
Remove a comment by ID. Removing a top-level comment also removes its replies and the anchored range markers. Removing a reply only removes that reply.