Formualizer Docs
Recalc CLI

CLI Reference

Every formualizer recalc flag, in-place and -o publication, --check, reproducible --now/--tz/--seed runs, exit codes 0/1/2/3/64/130, the formualizer.recalc/1 JSON schema and cancellation.

The sections below are included at build time from the repository's CLI reference, the authority for the command's behavior.

Installation

uvx formualizer recalc book.xlsx            # Python native wheel, no install step
pip install formualizer                     # then: formualizer recalc book.xlsx
python -m formualizer recalc book.xlsx      # same CLI through the Python module
npx @formualizer/cli recalc book.xlsx       # npm native launcher, no install step
npm i -g @formualizer/cli                   # then: formualizer recalc book.xlsx
cargo binstall formualizer-cli              # prebuilt release binary
cargo install formualizer-cli               # build from crates.io

Every channel installs the same formualizer command. Prebuilt binaries cover Linux x64/arm64 (glibc and static musl), macOS x64/arm64 and Windows x64. Each GitHub release also carries formualizer-cli-v<version>-<target>.tar.gz (.zip on Windows) archives with a SHA256SUMS file. The unscoped formualizer npm package is the WebAssembly library and does not contain the CLI. The CLI is not available in Pyodide; the Pyodide wheel exposes the same recalculation as formualizer.recalculate_xlsx_bytes (in-memory bytes only, no system clock: see reproducible runs).

Built-in help

$ formualizer --help
Recalculate XLSX formula caches without reconstructing the workbook

Usage: formualizer <COMMAND>

Commands:
  recalc  Recalculate formula caches using the strict source-preserving path
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

$ formualizer recalc --help
Recalculate formula caches using the strict source-preserving path

Usage: formualizer recalc [OPTIONS] <INPUT>

Arguments:
  <INPUT>  Workbook (.xlsx) to recalculate

Options:
  -o, --output <PATH>    Write the result to PATH instead of replacing INPUT
      --check            Compute without writing; exit 3 if any cache is stale
      --json             Print one formualizer.recalc/1 JSON object on stdout
      --max-errors <N>   List at most N error-cell locations [default: 20]
      --now <TIMESTAMP>  Fixed TODAY/NOW instant: RFC 3339 with an offset or Z
      --tz <ZONE>        TODAY/NOW timezone: UTC or ±HH:MM [default: --now offset, else local]
      --seed <U64>       RAND/RANDBETWEEN seed [default: built-in seed]
  -h, --help             Print help

Writes INPUT in place unless -o or --check is given.
Run recalc as the last step that writes the workbook: later edits leave its
caches stale.

RAND/RANDBETWEEN are reproducible run to run. Without --now, TODAY/NOW use
the host clock (local time unless --tz).

Exit codes:
  0    written, unchanged or current
  1    error; nothing written
  2    refused (unsupported input); nothing written
  3    --check: caches are stale
  64   usage error or invalid option value
  130  interrupted; nothing written

Commands

formualizer recalc <INPUT> [-o|--output <PATH>] [--check] [--json] [--max-errors <N>]
                   [--now <TIMESTAMP>] [--tz <ZONE>] [--seed <U64>]
formualizer --version
formualizer help [recalc]
OptionMeaning
<INPUT>The .xlsx to recalculate.
-o, --output <PATH>Publish to PATH instead of replacing INPUT. Always written, even when nothing changed.
--checkCompute only and report whether caches are current. Never writes, even with -o.
--jsonPrint exactly one formualizer.recalc/1 JSON object on stdout.
--max-errors <N>List at most N error-cell locations (default 20; 0 lists none). Counts are unaffected.
--now <TIMESTAMP>Fix the instant TODAY/NOW see. RFC 3339 with an offset or Z, such as 2026-01-31T09:00:00Z; a timestamp without one is a usage error. See reproducible runs.
--tz <ZONE>Timezone TODAY/NOW are read in: UTC or ±HH:MM. Defaults to the offset in --now, otherwise the host's local time.
--seed <U64>Seed for RAND/RANDBETWEEN (0 to 2^64-1). Defaults to a fixed built-in seed.
-h, --help / -V, --versionPrint help or formualizer <version> and exit 0.
  • The default writes in place, atomically. A true no-op leaves bytes and mtime untouched and reports unchanged.
  • -o PATH explicitly publishes that output, even if the input caches are already current (written, with zero changes). The input is untouched unless PATH is the input itself. Existing destination permissions are preserved. Symlink destinations are refused (exit 2).
  • --check reads the input and computes; current caches exit 0 (current), stale caches exit 3 (stale). Stale includes spill-shape or attribute changes, not just cache-value changes. Workbooks using TODAY/NOW can report stale whenever the clock has moved on; pass the same --now that produced the file to make --check meaningful for them.
  • Run recalc as the last step that writes the workbook. Any later edit leaves its caches stale.
  • Ctrl-C/SIGINT cooperatively cancels at the next library checkpoint (exit 130). Refusal, failure or cancellation observed before atomic publication leaves the destination untouched. Publication is not compare-and-swap against unrelated writers. The Python and npm launchers forward Ctrl-C the same way.

The library reads a bounded snapshot, writes a same-directory temporary file, preserves existing destination permissions, fsyncs the temporary file and renames it atomically. It does not guarantee directory-entry crash durability. See cache-only XLSX for eligibility, dynamic arrays, tables, ownership and bounds. Defaults include 64 MiB input/output, 10,000 ZIP entries, 256 MiB expanded bytes, 128 MiB per worksheet/metadata part, 100,000 formulas, XML depth 128, 256 columns and 8,000,000 cells. No limit/thread tuning flags are exposed.

Exit codes

CodeStatusMeaning
0written, unchanged, currentRecalculated (or, with --check, already current). Formula error results still exit 0.
1errorI/O failure, missing or non-ZIP input, engine/internal failure or output-stream error
2refusedThe strict path declined this input: unsupported feature, package structure it will not guess about, resource limit or symlink destination. Nothing written.
3stale--check only: caches or spill shape would change. Nothing written.
64errorCommand-line usage error, including an invalid --now, --tz or --seed value
130interruptedCancelled (Ctrl-C/SIGINT) before publication. Nothing written.

A file without the XLSX ZIP local-header signature is an error (1). Malformed ZIPs that pass that check may be structured strict-path refusals (2). Workbooks saved by Excel (desktop, Mac and Online), LibreOffice, Google Sheets, openpyxl and Info-ZIP zip are admitted as containers; ZIP64, encryption, entry comments and unknown ZIP extra fields are refused (see ZIP containers). A stored formula the parser cannot read is a refusal (unparseable formula, with the sheet, cell and parser message in context), not an error. Refusals pass the library's feature/context through verbatim; see refusal messages for their meaning.

Without --json, success and --check lines (exit 0 and 3) go to stdout; errors, refusals and usage errors go to stderr. --version and help print ordinary text to stdout.

Reproducible runs

  • RAND is reproducible by default. RAND and RANDBETWEEN derive each value from the seed and the cell's position, so recalculating the same workbook gives the same values on every run and machine. --seed picks a different, still reproducible, sequence.
  • Without --now, TODAY/NOW use the host clock in its local timezone (or --tz), sampled once per run.
  • With --now, results are fully reproducible. Two runs with the same --now, --tz and --seed produce byte-identical output, so --check is meaningful for volatile workbooks.
  • --tz defaults to the offset written in --now: --now 2026-03-02T00:30:00+01:00 and --now 2026-03-01T23:30:00Z --tz +01:00 are the same run, and TODAY() is 2026-03-02 in both. Only UTC and fixed offsets are accepted; named zones such as Europe/Paris are not, because their offset depends on the date.
  • --tz without --now keeps the host clock but reads it in that zone.
  • Every computed --json report echoes the clock and seed it used (see clock and seed below). clock.now carries the offset that was applied, so --now <clock.now> --seed <seed> (plus --tz <clock.timezone> when it is not Local) replays any run exactly. A host-clock sample is taken to the whole second, the resolution of NOW().
  • Builds without a system clock refuse workbooks that use TODAY/NOW (exit 2) unless --now is given. The Pyodide wheel is such a build: its recalculate_xlsx_bytes refuses them unless deterministic_timestamp_utc is passed, and reports clock.now as None when no timestamp was given.
# Pin the clock and seed; --check then stays current until the inputs change:
formualizer recalc book.xlsx --now 2026-01-31T09:00:00Z --seed 7
formualizer recalc book.xlsx --check --now 2026-01-31T09:00:00Z --seed 7

The Python functions recalculate_xlsx_file/recalculate_xlsx_bytes take the same options as rng_seed, deterministic_timestamp_utc (an aware datetime) and deterministic_timezone ("utc", "+02:00" or offset seconds; default UTC; requires the timestamp). The npm recalculateXlsxBytes(bytes, errorLocationLimit, options) takes {rngSeed, deterministicTimestampUtc, deterministicTimezone} with the same rules. Both return clock and seed.

JSON

With --json, every outcome except help and version, including usage errors, produces exactly one JSON object on stdout, followed by a newline. Nothing is written to stderr. The schema id is formualizer.recalc/1; additive fields need not bump the id. All keys below are always present.

{
  "schema": "formualizer.recalc/1",
  "status": "written",
  "input": "book.xlsx",
  "output": "book.xlsx",
  "written": true,
  "formula_cells": 5,
  "cache_cells_changed": 7,
  "worksheet_parts_changed": 1,
  "error_cells": 2,
  "errors": [
    {"sheet": "Sheet1", "cell": "B2", "error": "#DIV/0!", "message": null},
    {"sheet": "Sheet1", "cell": "C4", "error": "#NAME?", "message": "Unknown function: _xll.EURO"}
  ],
  "errors_truncated": false,
  "unknown_functions": [{"name": "_xll.EURO", "cells": 1}],
  "refusal": null,
  "clock": {"now": "2026-10-04T09:15:02+02:00", "timezone": "Local", "fixed": false},
  "seed": 17361606158148326741,
  "message": "book.xlsx: recalculated 5 formulas, 7 cached values changed, 2 error cells (Sheet1!B2 #DIV/0!, Sheet1!C4 #NAME? (Unknown function: _xll.EURO)) (written)\nunknown functions (cells produce #NAME?): _xll.EURO (1 cell)"
}
FieldTypeMeaning
schemastringAlways formualizer.recalc/1.
statusstringwritten, unchanged, current, stale, refused, error or interrupted.
inputstring or nullThe input path; null for usage errors.
outputstring or nullThe intended destination (the input unless -o); null for --check and usage errors.
writtenbooleanWhether a file was published. Always false for --check, refusals, errors and interruption.
formula_cellsinteger or nullSource formulas, counting spill anchors but not generated spill members.
cache_cells_changedinteger or nullPhysical caches inserted, replaced or cleared; can exceed the formula count.
worksheet_parts_changedinteger or nullChanged worksheets (not metadata parts). Nonzero means stale under --check.
error_cellsinteger or nullTotal formula cells whose result is an Excel error.
errorsarray or nullUp to --max-errors {sheet, cell, error, message} locations, grouped by error token.
errors[].messagestring or nullThe engine's reason for that cell's error: Unknown function: NAME for a function the engine does not implement (an add-in such as _xll.EURO, a VBA/macro function or any other unknown name), Undefined name: NAME for a name that is not defined. Null when the cell has no reason of its own, for example a #NAME? inherited from a precedent, or an ordinary #DIV/0!.
errors_truncatedboolean or nullTrue when errors lists fewer locations than error_cells.
unknown_functionsarray or nullEvery function the engine does not implement that some formula calls, as {name, cells} sorted by name, with the number of error cells that call it. Complete even when errors is truncated; empty when there are none.
refusalobject or null{"feature": ..., "context": ...} when status is refused.
clockobject or nullThe clock TODAY/NOW used: now, timezone and fixed. See reproducible runs.
clock.nowstring or nullRFC 3339 instant, written in the UTC offset that was applied (Z for UTC; the host's offset at that instant for Local). Null only in builds without a system clock when --now is absent.
clock.timezonestringLocal, UTC or ±HH:MM.
clock.fixedbooleanTrue when --now set the instant.
seedinteger or nullThe RAND/RANDBETWEEN seed, an unsigned 64-bit integer. JavaScript's JSON.parse rounds values above 2^53; read it as a big integer to replay.
messagestringOne-line human diagnostic. Not a stable machine interface.

Counters, errors, errors_truncated, unknown_functions, clock and seed are present after a successful computation (exit 0 or 3) and null otherwise. Sheet names containing ! are split correctly. Attribute-only spill changes may have zero cache changes. A numeric cache within one unit in the 15th significant digit of the computed value is current and is not counted in cache_cells_changed (see numeric precision).

Examples

# After an editor saves the workbook:
formualizer recalc book.xlsx --json

# Keep the original, always publishing a separate artifact:
formualizer recalc book.xlsx -o calculated.xlsx

# CI freshness check (exit 3 means stale):
formualizer recalc book.xlsx --check --json --max-errors 5

# Reproducible output for a workbook using TODAY/NOW/RAND:
formualizer recalc book.xlsx --now 2026-01-31T09:00:00Z --seed 7

# Agent loop over explicit paths (no built-in batch mode):
for file in reports/*.xlsx; do formualizer recalc "$file" --json || break; done

Non-goals

No editing, reading/dumping values, engine selection, configuration files, watch mode, directory/batch globbing, limit/thread tuning or legacy fallback. This writer does not claim Excel equivalence for every function or workbook. Data tables, external links and the other strict refusals are listed in cache-only XLSX. Dynamic arrays, fixed-extent CSE arrays and Excel tables within the validated subset are supported; after an openpyxl re-save strips dynamic metadata, the array keeps its fixed extent and its A1# readers return #REF!.

Embedding

The formualizer_cli::run library accepts argv including the program name, independent Write sinks and an optional shared CancelToken. It returns an exit code without exiting, installing signal handlers or assuming a TTY. Bindings use default-features = false; the standalone binary's signals feature installs SIGINT handling. See the Rust API docs for the exact signature.

On this page