WITAN

<a href="/exec">Docs</a> · <a href="/render">Render</a> · <a href="/calc">Calc</a> · <a href="/lint">Lint</a> · <a href="/pricing">Pricing</a> · <a href="/skills">Agent Skills</a> · <a href="/install">Install Witan</a>

---

# Witan Exec — Code Mode for Spreadsheets

One tool to read, write, verify, and search any workbook.

    $ witan xlsx exec report.xlsx --code '
      const result = await xlsx.setCells(wb, [
        { address: "Summary!B5", formula: "=SUM(Revenue!B2:B13)" }
      ])
      const lint = await xlsx.lint(wb, { rangeAddresses: ["Summary!A1:C10"] })
      return { touched: result.touched, errors: result.errors, warnings: lint.total }
    '
    {"ok":true,"result":{"touched":{"Summary!B5":"$1,250,000","Summary!B6":"$187,500","Summary!B10":"$3,750,000"},"errors":[],"warnings":0},"writes_detected":true}

Witan Exec replaces the currently Python scripting approach with a
purpose-built JavaScript runtime that talks directly to a high-fidelity
.NET spreadsheet engine. Currently your agent; writes Python, runs,
reads the file back, writes more Python, and repeats, only ever
approximating the spreadsheet with Python objects. Instead, Witan Exec
enables your agent to write one script that reads, writes, recalculates,
lints, and renders — all in a single execution, with results returned
as structured JSON.

## Four engines in one tool

Witan Exec comes with all the tools you need in a single entry point.

### The Edit Engine

Purpose-built methods for browsing and modifying workbooks,
designed for agents rather than humans.

Reading &amp; Discovery:

Discover table structure automatically, then read by semantic label instead of hardcoded cell addresses.

    readCell, readRange, readRow, readColumn
    readRangeTsv, readRowTsv, readColumnTsv    // token-efficient TSV output
    findCells, findRows                          // text/regex/number search
    detectTables, tableLookup                    // automatic table detection
    getUsedRange, listSheets, listDefinedNames

Writing &amp; Structure:

    setCells                                     // values, formulas, formats
    insertRowAfter, deleteRows
    insertColumnAfter, deleteColumns
    addSheet, deleteSheet, renameSheet

Dependency Tracing:

Trace which inputs drive a bottom-line number, or find every formula that would break if you changed a cell.

    getCellPrecedents, getCellDependents
    traceToInputs, traceToOutputs

Formula Testing:

Test what a formula would return without writing it to the workbook.

    evaluateFormula, evaluateFormulas             // test without writing

Styling:

    getStyle, setStyle
    getSheetProperties, setSheetProperties

This is a custom JavaScript library backed by a .NET RPC server —
combining the best language for agent scripting with the best
runtime for xlsx/xml fidelity.

### The Rendering Engine

    await xlsx.previewStyles(wb, "Sheet1!A1:F20")

Generate PNG screenshots of any cell range, directly in code.

### The Formula Engine

    const result = await xlsx.setCells(wb, [
      { address: "Sheet1!B5", formula: "=SUM(B1:B4)" }
    ])
    print(result.errors)  // New formula errors, if any

Every write triggers automatic recalculation. Errors reported
immediately.

### The Linting Engine

    const diagnostics = await xlsx.lint(wb)
    print(diagnostics.filter(d => d.severity === "Warning"))

Run all 11 semantic rules against the workbook from within your
code.

## How it works

    $ witan xlsx exec report.xlsx --expr 'await xlsx.readCell(wb, "Summary!A1")'

Witan spins up a sandboxed JavaScript runtime with @witan/xlsx
pre-loaded. The workbook is available as the global `wb`. Your code
calls methods on the `xlsx` object with access to 30+ methods for
every spreadsheet operation. The result is returned as structured JSON.

## Example scenario

### Discover, look up, trace, test, write, and verify

    // 1. Discover the workbook's structure
    const tables = await xlsx.detectTables(wb)
    const summary = tables.find(t => t.tableName === "Summary")

    // 2. Look up a value by label, not by hardcoded cell address
    const revenue = await xlsx.tableLookup(wb, {
      table: summary.address,
      rowLabel: "Total Revenue",
      columnLabel: "Q4"
    })

    // 3. Check what depends on this cell before changing it
    const downstream = await xlsx.getCellDependents(wb, revenue.address, 3)
    print(`Updating ${revenue.address} will affect ${downstream.cells.length} cells`)

    // 4. Test a formula before writing it
    const preview = await xlsx.evaluateFormula(wb, "Summary",
      `=${revenue.address}*1.1`)

    // 5. Write the update
    const result = await xlsx.setCells(wb, [{
      address: revenue.address,
      formula: `=${revenue.address.replace("Q4","Q3")}*1.1`
    }])

    // 6. Verify — lint and screenshot
    const lint = await xlsx.lint(wb, { rangeAddresses: [summary.address] })
    await xlsx.previewStyles(wb, summary.address)

    return { updated: revenue.address, downstream: downstream.cells.length,
             warnings: lint.diagnostics }

One tool call. Discover structure, look up by label, check impact,
test a formula, write, lint, and screenshot — all in one execution.

## Access tracking

Every exec call returns an accesses array showing exactly which
cells were read and written:

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

## CLI reference

    witan xlsx exec &lt;file&gt; [flags]

| Flag | Description | Default |
|------|-------------|---------|
| --code | Inline JavaScript source | — |
| --script | Path to a JavaScript file | — |
| --stdin | Read JavaScript from stdin | — |
| --expr | Expression shorthand (wrapped as return (&lt;expr&gt;);) | — |
| --input-json | JSON value passed as input to the script | {} |
| --timeout-ms | Execution timeout in milliseconds (max 90000) | 30000 |
| --max-output-chars | Maximum stdout characters to capture (max 50000) | 20000 |
| --save | Persist writes and overwrite local workbook | false |
| --json | Output full response envelope | false |

Exactly one of --code, --script, --stdin, or --expr is required.

## Output format

Success:

    {"ok":true,"stdout":"...","result":&lt;json&gt;,"writes_detected":&lt;bool&gt;,"accesses":[...]}

Failure:

    {"ok":false,"stdout":"...","error":{"type":"...","code":"...","message":"..."}}

## Usage examples

    # Quick cell lookup
    witan xlsx exec report.xlsx --expr 'await xlsx.readCell(wb, "Summary!A1")'

    # Run a script file with parameters
    witan xlsx exec report.xlsx --script ./analyze.js --input-json '{"threshold":10}'

    # Edit and save
    witan xlsx exec report.xlsx --save --code '
      await xlsx.setCells(wb, [{ address: "Sheet1!A1", value: "Updated" }])
      return { ok: true }
    '

    # Pipe from stdin
    cat analysis.js | witan xlsx exec report.xlsx --stdin

---

&copy; Witan Labs Inc. 2026
<a href="mailto:hello@witanlabs.com">hello@witanlabs.com</a> · <a href="https://api.witanlabs.com/docs">API Docs</a> · <a href="https://github.com/witanlabs/witan-cli">GitHub</a> · <a href="https://github.com/witanlabs/research-log">Research</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a>
