Metadata-Version: 2.4
Name: witan
Version: 0.10.0
Summary: Witan spreadsheet CLI and Python SDK for coding agents
Author: Witan Labs
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/witanlabs/witan-cli
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typing_extensions>=4.8
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: setuptools>=70.1; extra == "dev"
Requires-Dist: wheel>=0.43; extra == "dev"
Dynamic: license-file

# witan-cli

The spreadsheet toolkit for coding agents — edit, render, calculate, and lint Excel workbooks.

**[Documentation](https://witanlabs.com/agents)** | **[How we built it](https://github.com/witanlabs/research-log)**

## Install

### Quick Install Script

```bash
curl -fsSL https://witanlabs.com/agents/install.sh | sh
```

### From GitHub Releases

Download the latest artifacts from:

- https://github.com/witanlabs/witan-cli/releases/latest

Example (macOS Apple Silicon):

```bash
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:

```bash
# one-shot run without permanent install
uvx witan --help

# persistent install
pip install witan
```

Python SDK example:

```python
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:

```python
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:

```python
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:

```python
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`:

```python
from witan import AsyncWorkbook

wb = AsyncWorkbook("report.xlsx")
cell = await wb.read_cell("Summary!A1")
await wb.close()
```

### From Source

Requires Go (version from `go.mod`):

```bash
go install github.com/witanlabs/witan-cli@latest
```

## Quick Start

```bash
# Authenticate (recommended)
witan auth login

# Render a range
witan xlsx render report.xlsx -r "Sheet1!A1:F20"

# Recalculate formulas
witan xlsx calc report.xlsx

# Lint formulas
witan xlsx lint report.xlsx

# Run JS against workbook
witan xlsx exec report.xlsx --expr 'await xlsx.readCell(wb, "Summary!A1")'

# Create a new workbook from scratch
witan xlsx exec model.xlsx --create --save --code 'await xlsx.addSheet(wb, "Inputs"); return true'

# 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 calc`
- `witan xlsx exec`
- `witan xlsx lint`
- `witan 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.

## 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 using `witan auth login`)
- `WITAN_API_URL`: API base URL override (default: `https://api.witanlabs.com`)
- `WITAN_STATELESS`: set `1` or `true` to force stateless mode
- `WITAN_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 (`--stateless` or `WITAN_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

```bash
# 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 pushing `v*` tags)
- Artifacts:
  - `witan-darwin-arm64.tar.gz`
  - `witan-darwin-amd64.tar.gz`
  - `witan-linux-amd64.tar.gz`
  - `witan-linux-arm64.tar.gz`
  - `witan-windows-amd64.zip`
  - `witan-windows-arm64.zip`
  - `witan-install.sh`
  - `witan-*.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):

1. Add release notes under `## Unreleased` in `CHANGELOG.md`.
2. Create a GitHub Release in the UI with a new tag `vX.Y.Z` (or prerelease tag `vX.Y.Z-suffix`).
3. Tag push triggers `Witan CLI Release`.
4. The workflow builds artifacts, attaches them to the GitHub Release, and publishes to PyPI for stable tags.
5. On successful release, CI runs `scripts/roll-changelog.sh`, pushes the changelog update to a `chore/changelog-release-X.Y.Z` branch, and opens a PR into the default branch.
6. For stable tags, verify `witan==X.Y.Z` on PyPI, `witan --version`, `python -m witan --version`, and `from witan import Workbook, AsyncWorkbook`.

Manual `git tag ... && git push ...` is equivalent to UI tag creation and triggers the same workflow.

## 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`.
