Witan CLI
witan xlsx exec is the main spreadsheet interface for agents. It opens an Excel workbook, starts a sandboxed JavaScript runtime with the xlsx API preloaded, and exposes the live workbook as wb.
Use it when a task needs more than a single verifier command: read workbook structure, search for labels, trace dependencies, test formulas, write cells, recalculate, lint, and render from one script. The standalone render, calc, and lint commands remain useful for quick checks and CI, but exec is the workflow tool for agent-driven spreadsheet edits.
$ 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 current Python scripting approach with a purpose-built JavaScript runtime that talks directly to a high-fidelity .NET spreadsheet engine. Today, your agent writes Python, runs it, reads the file back, writes more Python, and repeats — only ever approximating the spreadsheet with Python objects. With Witan Exec, your agent writes one script that reads, writes, recalculates, lints, and renders — all in a single execution, with results returned as structured JSON.
Installation
One binary, zero dependencies. Witan runs anywhere a shell does.
Quick install (macOS & Linux)
Detects your platform and installs the binary to /usr/local/bin:
curl -fsSL https://witanlabs.com/install.sh | sh
PyPI
Ideal for sandboxed agent environments (Codex, Claude Code) where Python and uv are available but writing to /usr/local/bin is not:
# Zero-install one-shot
uvx witan xlsx render report.xlsx -r "Sheet1!A1:Z50"
# Persistent install
pip install witan
uvx downloads and runs the binary in a single command with no permanent install required — the fastest path when your agent can't modify system directories.
npm
Install the bundled CLI and Node.js SDK from npm. This requires Node.js 22 or later:
# Zero-install one-shot
npx witan xlsx render report.xlsx -r "Sheet1!A1:Z50"
# Persistent install
npm install witan
Use npx witan ... when you just need the CLI, or install witan when your project also uses the JavaScript SDK.
Manual download
If you prefer to download directly, pick your platform:
| Platform | Binary |
|---|---|
| macOS, Apple Silicon | witan-darwin-arm64 |
| macOS, Intel | witan-darwin-amd64 |
| Linux, x86_64 | witan-linux-amd64 |
| Linux, ARM64 | witan-linux-arm64 |
| Windows, x86_64 | witan-windows-amd64.zip |
| Windows, ARM64 | witan-windows-arm64.zip |
Download and install in one line (example for macOS Apple Silicon):
curl -fsSL https://witanlabs.com/dl/witan-darwin-arm64 -o /usr/local/bin/witan && chmod +x /usr/local/bin/witan
Replace witan-darwin-arm64 with the binary name for your platform.
Windows (PowerShell)
Invoke-WebRequest https://witanlabs.com/dl/witan-windows-amd64.zip -OutFile witan.zip
Expand-Archive witan.zip -DestinationPath "$env:LOCALAPPDATA\witan"
$env:Path += ";$env:LOCALAPPDATA\witan"
Verify your installation
witan --version
witan xlsx exec report.xlsx --expr 'await xlsx.readCell(wb, "Summary!A1")'
If both commands succeed, you're ready to go.
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 & 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 & 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 <file> [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 (<expr>);) |
— |
--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":<json>,"writes_detected":<bool>,"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
Supported file formats
.xlsx(Office Open XML).xls(Excel 97–2003).xlsm(macro-enabled — macros are preserved in the file but not executed)
Next steps
- Quickstart — run your first commands.
- Concepts — understand how the runtime, the workbook handle, and access tracking fit together.
- Skills — install the skills that teach your agent how to use Witan.