witan-cli
The Office toolkit for coding agents — edit, render, calculate, and lint Excel workbooks, plus render, script, and lint PPTX files.
Documentation | How we built it
Install
Quick Install Script
curl -fsSL https://witanlabs.com/install.sh | sh
Or try without installing: npx witan / uvx witan
From GitHub Releases
Download the latest artifacts from:
Example (macOS Apple Silicon):
curl -fsSL https://github.com/witanlabs/witan-cli/releases/latest/download/witan-darwin-arm64.tar.gz | tar -xz
install -m 0755 witan /usr/local/bin/witan
From PyPI
Install the bundled CLI and Python SDK from PyPI:
# one-shot run without permanent install
uvx witan --help
# persistent install
pip install witan
Python SDK example:
from witan import Workbook
with Workbook("report.xlsx") as wb:
sheets = wb.list_sheets()
tsv = wb.read_range_tsv("Summary!A1:F20")
Create and save a new workbook:
from witan import Workbook
with Workbook("model.xlsx", create=True) as wb:
wb.add_sheet("Inputs")
wb.set_cells([{"address": "Inputs!A1", "value": "Revenue"}])
wb.save()
Async sessions are available for asyncio applications:
from witan import AsyncWorkbook
async with AsyncWorkbook("report.xlsx") as wb:
cell = await wb.read_cell("Summary!A1")
Notebook and REPL sessions can use an explicit close instead of a context manager:
from witan import Workbook
wb = Workbook("report.xlsx")
tsv = wb.read_range_tsv("Summary!A1:F20")
wb.close()
In Jupyter/IPython, async sessions can use top-level await:
from witan import AsyncWorkbook
wb = AsyncWorkbook("report.xlsx")
cell = await wb.read_cell("Summary!A1")
await wb.close()
From npm
Install the bundled CLI and Node.js SDK from npm (requires Node.js 22+):
npm install witan
Node.js SDK example:
import { Workbook } from 'witan';
await using wb = await Workbook.open('report.xlsx');
const sheets = await wb.listSheets();
const tsv = await wb.readRangeTsv('Summary!A1:F20');
Create and save a new workbook:
import { Workbook } from 'witan';
await using wb = await Workbook.open('model.xlsx', { create: true });
await wb.addSheet('Inputs');
await wb.setCells([{ address: 'Inputs!A1', value: 'Revenue' }]);
await wb.save();
Alternative: explicit close
import { Workbook } from 'witan';
const wb = await Workbook.open('report.xlsx');
const tsv = await wb.readRangeTsv('Summary!A1:F20');
await wb.close();
From Source
Requires Go (version from go.mod):
go install github.com/witanlabs/witan-cli@latest
Quick Start
Run any command with npx witan or uvx witan without installing.
# Authenticate (recommended)
witan auth login
# Render a PPTX slide
witan pptx render deck.pptx --slide 1 -o slide-1.png
# Check a PPTX for chart-integrity, layout, and hidden-text issues
witan pptx lint deck.pptx
# Run Office.js-compatible JavaScript against a PPTX file
witan pptx exec deck.pptx --expr 'PowerPoint.run(async context => { const count = context.presentation.slides.getCount(); await context.sync(); return count.value })'
# Create a workbook from scratch
witan xlsx exec quickstart.xlsx --create --save --stdin <<'WITAN'
await xlsx.addSheet(wb, "Summary")
await xlsx.setCells(wb, [
{ address: "Summary!A1", value: "Metric" },
{ address: "Summary!B1", value: "Q1" },
{ address: "Summary!C1", value: "Q2" },
{ address: "Summary!A2", value: "Revenue" },
{ address: "Summary!B2", value: 1200, format: "$#,##0" },
{ address: "Summary!C2", value: 1500, format: "$#,##0" },
{ address: "Summary!A3", value: "Costs" },
{ address: "Summary!B3", value: 700, format: "$#,##0" },
{ address: "Summary!C3", value: 850, format: "$#,##0" },
{ address: "Summary!A4", value: "Profit" },
{ address: "Summary!B4", formula: "=B2-B3", format: "$#,##0" },
{ address: "Summary!C4", formula: "=C2-C3", format: "$#,##0" }
])
return await xlsx.readRange(wb, "Summary!A1:C4")
WITAN
# Render a range
witan xlsx render quickstart.xlsx -r "Summary!A1:C4"
# Recalculate formulas
witan xlsx calc quickstart.xlsx
# Lint formulas
witan xlsx lint quickstart.xlsx
# Run JS against workbook
witan xlsx exec quickstart.xlsx --expr 'await xlsx.readCell(wb, "Summary!C4")'
# Author a ListObject table in one call
witan xlsx exec model.xlsx --save --stdin <<'WITAN'
await xlsx.addListObject(wb, "Sheet1", {
name: "SalesTable",
ref: "A1:C4",
showTotalsRow: true,
columns: [
{ name: "Region", totalsRowLabel: "Total" },
{ name: "Sales", totalsRowFunction: "sum" },
{ name: "DoubleSales", calculatedColumnFormula: "=B2*2" }
],
rows: [
[{ value: "North" }, { value: 10 }, {}],
[{ value: "South" }, { value: 20 }, {}]
]
})
return await xlsx.readRange(wb, "SalesTable")
WITAN
# Author a What-If Data Table block
witan xlsx exec model.xlsx --save --stdin <<'WITAN'
await xlsx.addDataTable(wb, "Sheet1", {
type: "oneVariableColumn",
ref: "E1:F4",
columnInputCell: "H1",
inputValues: [5, 10, 15],
formulas: ["=H1*2"]
})
return await xlsx.getDataTable(wb, "Sheet1!E1:F4")
WITAN
# Author a chart from workbook data
witan xlsx exec dashboard.xlsx --save --stdin <<'WITAN'
await xlsx.addChart(wb, "Summary", {
name: "Revenue",
position: { from: { cell: "F2" }, to: { cell: "N18" } },
groups: [
{
type: "column",
series: [
{
name: { ref: "Data!B1" },
categories: "Data!A2:A9",
values: "Data!B2:B9"
}
]
}
],
title: { text: "Revenue" },
legend: { position: "right" }
})
await xlsx.previewStyles(wb, "Summary!F2:N18")
WITAN
What This CLI Covers
witan-cli exposes four spreadsheet commands:
witan xlsx calcwitan xlsx execwitan xlsx lintwitan xlsx render
The PyPI package also exposes witan.Workbook and witan.AsyncWorkbook, backed by witan xlsx rpc subprocess sessions. Public SDK methods use snake_case names matching the xlsx exec operation surface, such as read_range_tsv, find_cells, sweep_inputs, set_cells, add_chart, and set_conditional_formatting.
The lower-level Witan spreadsheet runtime supports broader workbook operations; this CLI focuses on the four agent-facing workflows above.
For presentations, the CLI provides witan pptx exec, witan pptx render, and witan pptx lint.
Auth, Config, and Modes
Authentication can be done via witan auth login, --api-key, or WITAN_API_KEY.
Use witan auth status to inspect the active credential, validation state, and selected organization.
Environment variables:
WITAN_API_KEY: API key (optional when usingwitan auth login)WITAN_API_URL: API base URL override (default:https://api.witanlabs.com)WITAN_STATELESS: set1ortrueto force stateless modeWITAN_CONFIG_DIR: override config directory (default:~/.config/witan)WITAN_MANAGEMENT_API_URL: management API override for auth login/token exchange
Modes:
- Stateful (default when authenticated): uploads workbook revisions and reuses them across commands
- Stateless (
--statelessorWITAN_STATELESS=1): sends workbook bytes on every request, no server-side file reuse
In stateful mode, load-balancer affinity cookies are persisted at ~/.config/witan/cookies.json
or $WITAN_CONFIG_DIR/cookies.json when WITAN_CONFIG_DIR is set.
witan xlsx exec --create always uses the stateless exec endpoint and only supports new .xlsx targets.
Limits:
- Workbook inputs must be
<= 25MB.
Development
# build local binary
make build
# run test suite
make test
# static checks
make vet
make format-check
# build release artifacts into dist/
make dist VERSION=v0.1.0
# build PyPI wheels (stable tags only)
make pypi-wheels VERSION=v0.1.0
The local binary is written to ./witan.
Release Process
Releases are handled by GitHub Actions:
- Publish workflow:
.github/workflows/witan-cli-release.yml(triggered by pushingv*tags) - Artifacts:
witan-darwin-arm64.tar.gzwitan-darwin-amd64.tar.gzwitan-linux-amd64.tar.gzwitan-linux-arm64.tar.gzwitan-windows-amd64.zipwitan-windows-arm64.zipwitan-install.shwitan-*.whl(PyPI wheels for supported platforms; stable tags only)witan-checksums.txt
PyPI publishing:
- Stable tags (
vX.Y.Z) publish wheels to PyPI using GitHub OIDC trusted publishing. - Pre-release tags (for example
v1.2.3-rc.1) skip PyPI publish.
GitHub release publishing:
- The workflow uploads artifacts directly to the matching GitHub Release tag.
- If the release already exists (for example, created in the GitHub UI), assets are attached with
--clobber.
Cutting a release (UI-driven):
- Add release notes under
## UnreleasedinCHANGELOG.md. - Create a GitHub Release in the UI with a new tag
vX.Y.Z(or prerelease tagvX.Y.Z-suffix). - Tag push triggers
Witan CLI Release. - The workflow builds artifacts, attaches them to the GitHub Release, and publishes to PyPI for stable tags.
- On successful release, CI runs
scripts/roll-changelog.sh, pushes the changelog update to achore/changelog-release-X.Y.Zbranch, and opens a PR into the default branch. - For stable tags, verify
witan==X.Y.Zon PyPI,witan --version,python -m witan --version, andfrom witan import Workbook, AsyncWorkbook.
Manual git tag ... && git push ... is equivalent to UI tag creation and triggers the same workflow.
Skills under skills/ are published independently of CLI releases — merging to main makes them live for npx skills add witanlabs/witan-cli. Each skill carries its own version; see skills/README.md.
CI
Go and Python CI runs in .github/workflows/golang.yml on pushes to main and pull requests. The workflow runs go test, go vet, pytest, mypy, and python -m compileall python/witan, plus skill checks (SKILL.md size limit, per-skill version bump on change).
Release files for witan 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| witan-0.14.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| witan-0.14.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| witan-0.14.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| witan-0.14.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| witan-0.14.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| witan-0.14.0-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| witan-0.14.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| witan-0.14.0-py3-none-macosx_10_15_x86_64.whl | Python 3 | none | macOS 10.15+ x86-64 | Details |
Total release size:29.1 MB
Release files / witan-0.14.0-py3-none-win_arm64.whl
| Download URL | witan-0.14.0-py3-none-win_arm64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
ed3b964313ef7e77ed640145229ede00ec6c183a340f349de0241083bf9976c5
|
|
BLAKE2b-256 checksum How to use checksums |
0ff56b4a64abd20890121feb1f8b920e0bcb1ef4600af5001b9631cadd253338
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-win_amd64.whl
| Download URL | witan-0.14.0-py3-none-win_amd64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7bb4bbef83a0c894a377857b2c9fbc1e59a6fa5a971f38932b4da22cc0dbbe17
|
|
BLAKE2b-256 checksum How to use checksums |
d6e699a794e9a85fb7161fc2df887892c2bcf998dd2a4baf3b0166254e27f2ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | witan-0.14.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
005417aa17860420dcdee14a5030eee3fed44d6d1c487c93dcab0054d6d3b39e
|
|
BLAKE2b-256 checksum How to use checksums |
c55a3ba446c3b323d6c64c89df939984f431f35581bbd0ef127a2d80e8713187
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | witan-0.14.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
e6c99327ea11a52d93c45a06539121a42ee86985a4aef13ec5cf065a47fd8047
|
|
BLAKE2b-256 checksum How to use checksums |
49d876b8737fc5cd5f9a10cb135e0f0accc946a37e993a450086b9e2d6e90f7f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | witan-0.14.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
93d4cfb9fd93901a7d867d4a3dc03cb605b28aed8fe1bbfb0fb0cec2f791f1b9
|
|
BLAKE2b-256 checksum How to use checksums |
0416ff31ba6a6972b4de771ddf8f1d20993686a7202dc371f643d69d66766ac1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-manylinux_2_17_aarch64.whl
| Download URL | witan-0.14.0-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 3.4 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
9c2067bff2083e4f10ea90e5afa1f8d892b0e5ca26763e345f168ca63ca59d31
|
|
BLAKE2b-256 checksum How to use checksums |
68a03d90026e357bd457a74814de585301c740c915878188a79d6972f21ffa37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-macosx_11_0_arm64.whl
| Download URL | witan-0.14.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
1026fd038b91855338eaa6c4805aeb6df0d1461b594a05ed7f97b85f9b2025db
|
|
BLAKE2b-256 checksum How to use checksums |
7a0c1261f12d086f4bb73e6bb68e86ff470fb76fcd0b30339bd3fbccb5ae22f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency logRelease files / witan-0.14.0-py3-none-macosx_10_15_x86_64.whl
| Download URL | witan-0.14.0-py3-none-macosx_10_15_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | Python 3 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
5f282e11899d81050226db403b789ce4909a63acd7633f18278dbe2079291eb7
|
|
BLAKE2b-256 checksum How to use checksums |
6e0aa8b92c713b856af354c7a16d7fb89a828cfddb902c01c03046e24a7940b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 17, 2026.
Transparency log