Spreadsheets

Witan Spreadsheets is an API-backed workbook engine for Excel files. The CLI sends workbook bytes, or a cached workbook revision, to Witan Cloud or your self-hosted Witan API. The API runs spreadsheet operations in xlsx-serve, including sandboxed JavaScript for exec, and returns structured JSON plus updated workbook bytes when requested.

The design goal is bidirectional compatibility with Microsoft Excel and the Office Open XML .xlsx workbook format. Workbooks created or edited by Witan should remain ordinary Excel workbooks that can be opened and edited in Excel, and workbooks created or edited in Excel should remain usable through Witan, within the supported feature set documented on these pages.

For coding agents, the important workflow is a verification loop: make a workbook change, inspect what was read and written, check recalculation output, run semantic linting, render the visible range, and save only when the result is acceptable.

Architecture

┌──────────────────────────┐
│   witan CLI / SDKs       │
│                          │
│  • local file path       │
│  • code or command flags │
│  • optional --save       │
└────────────┬─────────────┘
             │ HTTPS

┌──────────────────────────┐
│   Witan API              │
│                          │
│  • auth / rate limits    │
│  • file cache / stateless│
│  • starts xlsx-serve     │
└────────────┬─────────────┘
             │ JSON-RPC

┌──────────────────────────┐
│   xlsx-serve             │
│                          │
│  • JS sandbox for exec   │
│  • Formula recalculation │
│  • Rendering             │
│  • Linting               │
└──────────────────────────┘

TypeScript or JavaScript code runs in the workbook session. The CLI packages that code with the workbook request and sends it to the configured Witan API. The API runs it inside the xlsx-serve JavaScript sandbox with the xlsx API preloaded and the workbook exposed as a global wb. Each method call on xlsx invokes the workbook engine inside that session. The return value becomes the CLI's JSON output.

This split is deliberate:

  • Local CLI for ergonomics — install one binary, pass file paths, and get JSON or updated files back.
  • API-backed execution — Witan Cloud or a self-hosted API handles authentication, stateless uploads, cached workbook revisions, and xlsx-serve lifecycle.
  • Sandboxed TypeScript/JavaScript for exec — no filesystem access, no network, no imports. The script can only manipulate the open workbook through wb.
  • Spreadsheet engine in xlsx-serve - Excel-compatible formula calculation, dynamic arrays, data validation, conditional formatting, workbook rendering, and linting for supported workbook features.

The four engines, one entry point

Witan has four CLI subcommands, but they are not four separate tools:

CLI command What it does Available inside exec?
witan xlsx exec Runs TypeScript or JavaScript against the workbook with read, write, annotation, search, trace, workbook structure, formatting, image, data validation, chart, table, calc, lint, and render APIs -
witan xlsx render PNG/WebP image of a sheet-qualified range for supported worksheet, drawing, and chart features Yes: xlsx.previewStyles(wb, range)
witan xlsx calc Updates cached formula values; full workbook by default, or seeded from ranges with downstream recalc Yes: writes recalc automatically; Office Script calculate methods are also available
witan xlsx lint Runs 16 semantic rules Yes: xlsx.lint(wb, …)

render, calc, and lint exist as standalone commands for direct checks and CI. Agent-driven workbook edits usually run through exec, where a single script can write, recalculate, lint, and render in one request.

The workbook handle: wb

Inside the exec runtime, the workbook is a global called wb. Every method that operates on the workbook takes wb as its first argument:

await xlsx.readCell(wb, "Summary!A1")
await xlsx.setCells(wb, [{ address: "B5", formula: "=SUM(B1:B4)" }])
await xlsx.lint(wb, { rangeAddresses: ["Summary!A1:C10"] })

wb is not the file on disk. It's a handle to a live representation of the workbook held by the .NET engine. Writes go through this handle:

  • In-memory by default. xlsx.setCells(...) modifies the handle but does not touch the file on disk. The script can experiment freely.
  • Persisted with --save. Pass --save to the CLI and the modified workbook is written back to the file when the script returns.

This means a script can try an edit, lint it, and decide whether to commit before the on-disk file is overwritten.

Reads, writes, and access tracking

Every exec call returns an accesses array listing exactly which cells the script read and wrote:

{
  "ok": true,
  "result": { },
  "writes_detected": true,
  "accesses": [
    { "operation": "read", "address": "Summary!B2:B10" },
    { "operation": "write", "address": "Summary!B5" }
  ]
}

This is the audit trail your agent needs to:

  • Verify it only touched the cells it meant to.
  • Show its work to a human reviewer.
  • Detect unintended writes from a buggy script before --save persists them.

writes_detected is the high-level boolean — if it's false, the script was effectively read-only regardless of intent.

Error filtering

witan xlsx calc runs a full-workbook calculation by default. With one or more --range values, it starts from formula cells in those ranges and still follows downstream dependents. For ordinary Excel error values, it reports errors the recalculation just introduced. Pre-existing #N/A and #REF! cells in the affected calculation scope are filtered out. Circular-reference and convergence diagnostics are reported separately.

This matters for live spreadsheets. A workbook may already contain #N/A errors from upstream data issues. Without filtering, a script has to diff before and after to determine which ordinary formula errors are new.

The same principle runs through exec: when xlsx.setCells triggers automatic recalculation, the errors field on the return value contains only new errors.

Targeted reads and renders

Witan exposes targeted read and render operations so scripts do not need to dump whole sheets when a smaller range or semantic lookup is enough:

  • Range-targeted rendering. render produces an image of exactly the range you ask for. Auto DPR chooses 2 for smaller ranges and drops to 1 when the estimated 2x output would exceed 1568 px on either axis.
  • TSV reads. readRangeTsv, readRowTsv, readColumnTsv return tab-separated text instead of JSON arrays — significantly fewer tokens for the same data.
  • Semantic table access. describeSheet + tableLookup can look up "Total Revenue, Q4" without reading and parsing a whole region to find it.

Use these operations when a task only needs a range, row, column, table, or label-based lookup.

Recalculation Is Automatic

Formula-affecting writes recalculate automatically:

  • xlsx.setCells and other mutating exec APIs recalculate affected formulas before returning.
  • The result includes recalculated touched values, changed addresses, and new formula errors.
  • Format-only changes do not trigger formula recalculation because they do not affect formula results.
  • Standalone witan xlsx calc updates cached formula values for a full or seeded calculation pass, unless you use --verify.

Excel stores a cached value alongside each formula: the last computed result. Witan write APIs refresh cached values affected by an edit before returning, and witan xlsx calc is the standalone command for refreshing cached formula values in a workbook you received from elsewhere.

Dependency tracing

The workbook engine builds a formula dependency graph and exposes dependency queries:

const precedents = await xlsx.getCellPrecedents(wb, "Summary!B5", 3)
const dependents = await xlsx.getCellDependents(wb, "Inputs!B2", 3)
  • Precedents - what feeds into a cell. Useful before reading a number, so the script knows what assumptions it rests on.
  • Dependents - what depends on a cell. Useful before changing a cell, so the script knows what will move downstream.

The depth parameter limits how far the trace walks. traceToInputs and traceToOutputs are the same idea but walk all the way to leaf cells.

Pixel diff: verifying visual changes

When a workbook edit changes formatting, colors, alignment, charts, or visible values, the render --diff workflow compares the visible result against a baseline:

  1. Render the region before the edit, save as before.png.
  2. Make the edit.
  3. Render the same region with --diff before.png.

The output is a diff image with changed pixels highlighted at full color and unchanged areas desaturated. The metadata line summarizes:

Sheet1!A1:F20 | ~768×600px | dpr=2 | diff: 1,204 pixels changed (3.2%)

This gives an agent loop a concrete visual regression check.

Stateless vs. files-backed operation

Witan chooses its transport mode from the credential context:

  • Unauthenticated / Personal — stateless by default. Every command sends the workbook bytes for that request and does not reuse a server-side file cache.
  • Authenticated / organization-backed — files-backed by default. The CLI uploads workbook revisions and reuses them across commands for speed.
  • Forced stateless — set WITAN_STATELESS=1 or pass --stateless to send workbook bytes on every request even when credentials are available.

Stateless mode is slower for repeated operations, but it avoids server-side file caching. See CLI Reference for setup.

When to use which command

Use this as a starting point:

  • Reading a workbook? exec with readCell / readRange / tableLookup. Often a single --expr is enough.
  • Editing? exec with setCells + --save. Check accesses and errors in the return value.
  • Need a visual? xlsx.previewStyles inside exec, or standalone witan xlsx render.
  • Checking a file whose cached formula values may be stale? Standalone witan xlsx calc. Use it to refresh those cached values without writing an exec script.
  • Verifying a finished edit? witan xlsx lint for semantic bugs, witan xlsx render --diff for visual regression.

What Witan does not do

Knowing the boundaries upfront:

  • Limited external workbook references. Formulas like =[Budget.xlsx]Sheet1!A1 are resolved from cached external-link values in the workbook. Witan does not open arbitrary referenced workbooks from disk.
  • No VBA / macro execution. .xlsm files can be opened and recalculated, but VBA code is not run.
  • No add-in functions. Custom functions from Bloomberg, Power Query, etc., are not evaluated.

See the individual reference pages for the full lists.

Next steps