Skip to content

command batch

goworm edited this page Jul 17, 2026 · 63 revisions

batch

Execute multiple commands in a single pass.

Synopsis

officecli batch <file> [--input <file>] [--commands '<json>'] [--stop-on-error] [--best-effort] [--force] [--json]

Description

Executes a sequence of commands from a JSON array, opening the document once and saving once at the end. This is more efficient than running individual commands.

Batches are atomic by default (v1.0.137+): every item runs (so the receipt always reports every failure and N succeeded, M failed stays meaningful), but if any item fails, the whole batch is rolled back — the target file is left byte-identical to before the batch ran. There is no partial-application middle ground unless you opt out. See Atomic mode and summary semantics below for the full mechanics and JSON shape.

  • --best-effort restores the pre-1.0.137 apply-what-succeeds behavior: successful items persist, failed items are skipped, nothing rolls back. Useful for lossy dump → batch replays where aborting entirely on the first unsupported item would lose the rest of an otherwise-good replay.
  • --stop-on-error means early-abort in both modes — it stops running items as soon as one fails (remaining items are skipped) — but doesn't change whether what already ran gets persisted: under the atomic default it still rolls back everything; combined with --best-effort it keeps whatever succeeded before the stop.
  • --force is unrelated to any of the above — it remains the docx-protection bypass only.

Commands are read from a JSON file (--input), inline JSON (--commands), or from stdin.

Arguments

Name Type Required Default Description
file FileInfo Yes - Office document path

Options

Name Type Required Default Description
--input FileInfo No stdin JSON file containing batch commands
--commands string No - Inline JSON array of batch commands (alternative to --input or stdin)
--stop-on-error bool No false Stop running items as soon as one fails (remaining items are skipped, not attempted). Default atomic mode still rolls back regardless; with --best-effort it keeps whatever succeeded before the stop.
--best-effort bool No false (v1.0.137+) Apply whatever succeeds even when other items fail — the pre-atomic legacy semantics. Default (this flag omitted): any failure rolls back the entire batch.
--force bool No false docx-protection bypass (unrelated to error handling)
--json bool No false Output results as structured JSON envelope {success, data} (parity with resident mode)

Input Format

The input is a JSON array of command objects. Each object has the following fields:

Field Type Required Description
command string Yes Command name: get, query, set, add, remove, move, swap, view, raw, raw-set, validate. Alias: op
path string No DOM path (for get, set, remove, move)
parent string No Parent path (for add)
type string No Element type (for add)
from string No Source path for copy (for add)
index int No Insert position (for add, move)
after string No Insert/move after anchor path or find:text (for add, move)
before string No Insert/move before anchor path or find:text (for add, move)
to string No Target parent (for move). Auto-inferred from after/before if omitted.
path2 string No Second element path (for swap)
props object OR string[] No Properties for set / add. Accepts an object ({"font":"Arial","size":"12pt"}) or an array of "k=v" strings (["font=Arial","size=12pt"]) — the latter is friendlier to hand-authored JSONL and mirrors the CLI --prop k=v form.
selector string No CSS-like selector (for query)
mode string No View mode (for view)
depth int No Child depth (for get)
part string No Part path (for raw, raw-set)
xpath string No XPath expression (for raw-set)
action string No XML action (for raw-set)
xml string No XML fragment (for raw-set)

Output Format

Text Mode (default)

Each result is prefixed with its 1-based index. The final line is a frozen, machine-consumed skeleton — Batch complete: N succeeded, M failed, T total, with (atomic: no changes were applied) appended when the default atomic mode rolled the batch back:

[1] Added paragraph at /body/p[@paraId=00100002]
[2] ERROR: Path not found: /slide[999]

Batch complete: 1 succeeded, 1 failed, 2 total (atomic: no changes were applied)

With --best-effort, the same batch omits the parenthetical (the successful item was actually persisted):

[1] Added paragraph at /body/p[@paraId=00100002]
[2] ERROR: Path not found: /slide[999]

Batch complete: 1 succeeded, 1 failed, 2 total

JSON Mode

{
  "results": [
    { "index": 0, "success": true, "output": "..." },
    { "index": 1, "success": true, "output": "..." },
    { "index": 2, "success": false, "error": "error message", "code": "not_found", "item": { "command": "set", "path": "/slide[999]", "props": { "title": "bad" } } }
  ],
  "summary": {
    "total": 3,
    "executed": 3,
    "succeeded": 2,
    "failed": 1,
    "skipped": 0
  }
}

When get or query commands return structured data, the output field contains the parsed JSON object directly (not a double-encoded string).

Failed results include the original batch item in the item field, so the caller can inspect the failing command and its parameters without having to correlate by index.

Failed results also carry a machine-readable code (since v1.0.137) from the same frozen list as the envelope-level error.code (see error-codes). The field is absent when the failure is unclassified — fall back to the error message text; never branch on message wording.

Atomic mode and summary semantics

Batch is atomic by default (since v1.0.137): if any item fails, the whole batch is rolled back and the target file is untouched. The JSON summary then carries "atomicRolledBack": true.

  • results[].success / summary.succeeded count in-memory per-item execution — they tell you which item failed and what had executed before it, even when the batch was rolled back.
  • summary.atomicRolledBack is the only authoritative signal that nothing reached disk. A rolled-back batch can legitimately report succeeded: 2, failed: 1, atomicRolledBack: true.
  • --best-effort restores the legacy semantics: successful items persist, failed items are skipped, no rollback (and atomicRolledBack never appears).
  • Concurrent batches against the same file are corruption-safe but not lossless: each batch stages on a private temp copy and promotes atomically, so the last promoter wins and earlier concurrent batches' changes are overwritten (each still reports its own in-memory success). Serialize writes to a single file externally if you need all of them to land.

Large Output (spill-to-file)

When the JSON output exceeds 8 KB, the full results are written to a temp file and a slim envelope is returned inline:

{
  "outputFile": "/tmp/officecli_batch_abc123.json",
  "outputSize": 156234,
  "results": [
    { "index": 0, "success": true },
    { "index": 1, "success": true },
    { "index": 2, "success": false, "error": "Slide 999 not found", "item": { "command": "set", "path": "/slide[999]", "props": { "title": "bad" } } }
  ],
  "summary": { "total": 3, "executed": 3, "succeeded": 2, "failed": 1, "skipped": 0 }
}

The temp file contains the full original JSON with all output fields. Error messages and the original item are always inline so the caller can act without reading the file.

Examples

From stdin

echo '[
  {"command": "set", "path": "/body/p[1]", "props": {"style": "Heading1", "text": "Title"}},
  {"command": "add", "parent": "/body", "type": "paragraph", "props": {"text": "New paragraph"}},
  {"command": "set", "path": "/body/p[2]", "props": {"bold": "true"}}
]' | officecli batch report.docx

From file

officecli batch report.docx --input commands.json

JSON output

officecli batch report.docx --input commands.json --json

Apply what succeeds, even if some items fail

officecli batch report.docx --input commands.json --best-effort

Complex batch example

commands.json:

[
  {
    "command": "set",
    "path": "/",
    "props": { "title": "Quarterly Report", "author": "Finance Team" }
  },
  {
    "command": "add",
    "parent": "/body",
    "type": "paragraph",
    "props": { "text": "Executive Summary", "style": "Heading1" }
  },
  {
    "command": "add",
    "parent": "/body",
    "type": "paragraph",
    "props": { "text": "This report covers Q4 2024 performance." }
  },
  {
    "command": "add",
    "parent": "/body",
    "type": "table",
    "props": { "rows": "4", "cols": "3" }
  },
  {
    "command": "set",
    "path": "/body/tbl[1]/tr[1]/tc[1]",
    "props": { "text": "Category", "bold": "true", "shd": "4472C4", "color": "FFFFFF" }
  },
  {
    "command": "set",
    "path": "/body/tbl[1]/tr[1]/tc[2]",
    "props": { "text": "Q3", "bold": "true", "shd": "4472C4", "color": "FFFFFF" }
  },
  {
    "command": "set",
    "path": "/body/tbl[1]/tr[1]/tc[3]",
    "props": { "text": "Q4", "bold": "true", "shd": "4472C4", "color": "FFFFFF" }
  },
  {
    "command": "get",
    "path": "/body",
    "depth": 1
  },
  {
    "command": "validate"
  }
]

Excel batch example

[
  { "command": "add", "parent": "/", "type": "sheet", "props": { "name": "Summary" } },
  { "command": "set", "path": "/Summary/A1", "props": { "value": "Total Revenue", "bold": "true" } },
  { "command": "set", "path": "/Summary/B1", "props": { "formula": "=SUM(Sheet1!B:B)", "numFmt": "#,##0.00" } },
  { "command": "add", "parent": "/Summary", "type": "chart", "props": {
    "chartType": "bar",
    "title": "Revenue by Quarter",
    "categories": "Q1,Q2,Q3,Q4",
    "series1": "Revenue:1000,2000,1500,3000"
  }}
]

PowerPoint batch example

[
  { "command": "add", "parent": "/", "type": "slide", "props": { "title": "Introduction", "layout": "title" } },
  { "command": "set", "path": "/slide[1]", "props": { "background": "1A1A2E" } },
  { "command": "add", "parent": "/slide[1]", "type": "shape", "props": {
    "text": "Welcome",
    "x": "2cm", "y": "3cm", "width": "20cm", "height": "5cm",
    "font": "Arial", "size": "36", "bold": "true", "color": "FFFFFF",
    "fill": "none"
  }},
  { "command": "add", "parent": "/", "type": "slide", "props": { "layout": "blank" } },
  { "command": "add", "parent": "/slide[2]", "type": "chart", "props": {
    "chartType": "pie",
    "title": "Market Share",
    "categories": "Product A,Product B,Product C",
    "data": "Share:40,35,25",
    "x": "2cm", "y": "2cm", "width": "20cm", "height": "15cm"
  }}
]

Persisting changes for external readers

A standalone batch saves to disk on its own, so this example needs nothing extra:

officecli batch report.docx --input commands.json   # applied AND saved to disk
python my_reader.py report.docx                      # reads the new content

But if a resident is already running for the file — because you called open, or ran any earlier command that auto-started one — the batch is applied in memory and the save is deferred. It will be flushed automatically by the idle auto-save (adaptive 2–10s after the last command), but if a non-OfficeCLI tool must read the file right away, flush explicitly:

officecli open report.docx                           # resident now holds the file
officecli batch report.docx --input commands.json    # applied in memory, NOT yet on disk
officecli save report.docx                            # flush to disk, keep the resident (stays fast)
python my_reader.py report.docx                       # now reads the new content
officecli close report.docx                           # flush + release when finished

Use save for an immediate mid-session flush and close when you are done. The full set of flush triggers (explicit save/close + automatic idle auto-save / idle shutdown) is in open / close → When the file on disk is refreshed.

Notes

  • All commands in a batch run in a single pass (one open/save cycle standalone; a deferred flush under a resident), making batch mode significantly faster than individual commands.
  • Atomic by default (v1.0.137+): every item still runs and is reported, but if any item fails the whole batch rolls back — confirmed live in both standalone mode (batch runs against a same-directory temp copy, promoted only on full success) and resident mode (the poisoned in-memory DOM is dropped and the pre-batch file reloaded, without ever writing the partial state to disk). Pass --best-effort to go back to apply-what-succeeds; --stop-on-error only changes how early the run stops, not whether it's kept.
  • Returns non-zero exit code when any command fails, atomic rollback or not.
  • Read-only commands (get, query, view, validate) can be mixed with write commands.
  • Add commands accept path as fallback when parent is not set.
  • A standalone batch (no resident running) opens the file, applies every item, and saves to disk before exiting — no separate save/close is needed, and an external tool can read the file immediately afterward.
  • When a resident is active (you ran open, or any prior command auto-started one), the batch is forwarded into the resident and applied in memory with the save deferred — the file on disk is not updated until a flush: the idle auto-save (adaptive 2–10s), officecli save, officecli close, or idle shutdown. (Under OFFICECLI_RESIDENT_FLUSH=each the batch flushes once, before it returns.) This is the same deferred-flush behavior as individual commands; see Persisting changes for external readers below. (A read routed through OfficeCLI still sees the change immediately.)

See Also


Based on OfficeCLI v1.0.137

Clone this wiki locally