-
Notifications
You must be signed in to change notification settings - Fork 1.7k
command batch
Execute multiple commands in a single pass.
officecli batch <file> [--input <file>] [--commands '<json>'] [--stop-on-error] [--best-effort] [--force] [--json]
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-effortrestores the pre-1.0.137 apply-what-succeeds behavior: successful items persist, failed items are skipped, nothing rolls back. Useful for lossydump → batchreplays where aborting entirely on the first unsupported item would lose the rest of an otherwise-good replay. -
--stop-on-errormeans early-abort in both modes — it stops running items as soon as one fails (remaining items areskipped) — but doesn't change whether what already ran gets persisted: under the atomic default it still rolls back everything; combined with--best-effortit keeps whatever succeeded before the stop. -
--forceis 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.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
file |
FileInfo | Yes | - | Office document path |
| 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) |
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) |
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
{
"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.
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.succeededcount 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.atomicRolledBackis the only authoritative signal that nothing reached disk. A rolled-back batch can legitimately reportsucceeded: 2, failed: 1, atomicRolledBack: true. -
--best-effortrestores the legacy semantics: successful items persist, failed items are skipped, no rollback (andatomicRolledBacknever 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.
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.
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.docxofficecli batch report.docx --input commands.jsonofficecli batch report.docx --input commands.json --jsonofficecli batch report.docx --input commands.json --best-effortcommands.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"
}
][
{ "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"
}}
][
{ "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"
}}
]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 contentBut 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 finishedUse 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.
- 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-effortto go back to apply-what-succeeds;--stop-on-erroronly 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
pathas fallback whenparentis not set. - A standalone batch (no resident running) opens the file, applies every item, and saves to disk before exiting — no separate
save/closeis 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. (UnderOFFICECLI_RESIDENT_FLUSH=eachthe 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.)
- Command Reference - Overview of all commands
- open / close - Alternative for multi-step workflows
Based on OfficeCLI v1.0.137