# XLSX Toolkit

> xlsx-toolkit

- Skill: `decebal/xlsx-toolkit` (Agent Skill, multi-file: 15 files)
- Install (CLI): `npx skillmds@latest add decebal/xlsx-toolkit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/decebal/xlsx-toolkit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: decebal (https://skillmd.com/u/decebal)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/decebal/xlsx-toolkit

---


# xlsx-toolkit

Single Rust binary with five subcommands, built on `umya-spreadsheet` (read+write) and `clap`. The skill directory **is** a cargo project — source in `src/`, binary at `bin/xlsx-toolkit`.

## First-time setup

Invoke via the shim, which picks a prebuilt for the current platform or builds from source on first use:

```bash
~/.claude/skills/xlsx-toolkit/bin/xlsx-toolkit --help
```

The skill ships a prebuilt only for `aarch64-apple-darwin`. On any other platform the shim runs `cargo build --release` once and caches the result in `target/release/`. Subsequent runs go straight to the cached binary — no cargo overhead.

Optional alias if you'll use this often:

```bash
alias xlsx-toolkit='~/.claude/skills/xlsx-toolkit/bin/xlsx-toolkit'
```

**macOS Gatekeeper:** if the bundled binary arrived with a `com.apple.quarantine` xattr (downloaded `.skill`), the shim strips it automatically. If macOS still blocks it ("unidentified developer"), run `xattr -dr com.apple.quarantine ~/.claude/skills/xlsx-toolkit/bin` and retry.

The rest of this file assumes you can invoke the binary as `xlsx-toolkit`. If the alias isn't set, use the full path.

## When to use what

| Task | Subcommand |
|---|---|
| "What's in this xlsx?" / unknown workbook | `inspect` |
| Dump a sheet (or all sheets) to CSV/TSV | `to-csv` |
| Build a new xlsx from a JSON spec | `write` |
| Compare two workbooks | `diff` |
| Per-column fill % / data quality | `coverage` |

For anything else, read [references/patterns.md](references/patterns.md) — it covers formulas vs cached values, dates, merged cells, multi-row headers, hidden sheets, write-side details, and how to drop down to the `umya-spreadsheet` API when the subcommands don't fit.

## Workflow

1. **Always inspect first.** Run `xlsx-toolkit inspect <file>` before doing anything else. It prints sheet names, dimensions, headers, formula count, merged ranges, and a sample. Without this, you'll guess wrong about what's in the workbook.
2. **Convert to CSV when piping to other tools.** `to-csv --sheet NAME` to stdout is the right move when feeding data to `jq`, `grep`, `awk`, `csvkit`, etc. Don't try to parse xlsx with shell.
3. **For new workbooks, prefer `write` over hand-rolling.** It handles freeze panes, autofilter, column widths, and formulas through a JSON spec. If the layout is unusual, fall back to writing a small cargo binary against the patterns in `references/patterns.md`.
4. **Coverage is the data-quality lens.** Use `coverage` to find columns that are mostly empty (often imported-but-unused fields) or to confirm a migration populated what it should. The HubSpot data-model coverage doc in this monorepo is exactly this shape.

## Quick examples

### Inspect

```bash
xlsx-toolkit inspect ./data.xlsx --sample 5
xlsx-toolkit inspect ./data.xlsx --json | jq '.details[].headers'
xlsx-toolkit inspect ./data.xlsx --sheet Contacts
```

### Convert

```bash
# One sheet → stdout (pipe to anything)
xlsx-toolkit to-csv ./data.xlsx --sheet Contacts | head

# All sheets → sibling .csv files next to the source
xlsx-toolkit to-csv ./data.xlsx

# TSV
xlsx-toolkit to-csv ./data.xlsx --sheet Contacts --delimiter $'\t'
```

### Write

```bash
cat > /tmp/spec.json <<'EOF'
{
  "sheets": [{
    "name": "Report",
    "headers": ["Item", "Qty", "Price", "Total"],
    "rows": [
      ["apples", 3, 1.5,  { "v": 0, "f": "B2*C2" }],
      ["bread",  2, 4.25, { "v": 0, "f": "B3*C3" }]
    ],
    "freeze_header": true,
    "autofilter": true,
    "column_widths": [12, 6, 8, 10]
  }]
}
EOF
xlsx-toolkit write --spec /tmp/spec.json --out report.xlsx
# Or pipe the spec on stdin:
cat /tmp/spec.json | xlsx-toolkit write --out report.xlsx
```

Cell values: plain JSON `string | number | boolean | null` for static data, or `{ "v": <value>, "f": "FORMULA", "t": "n|s|b" }` for formulas / explicit types. Set the value AND the formula together — the cached `v` is what other tools see until Excel re-evaluates.

### Diff

```bash
# Positional row diff (row N vs row N) across shared sheets
xlsx-toolkit diff before.xlsx after.xlsx

# Key-based: match rows by a header column value (stable across reorderings)
xlsx-toolkit diff before.xlsx after.xlsx --key "Property"

# Restrict to one sheet, JSON output
xlsx-toolkit diff before.xlsx after.xlsx --sheet Contacts --json
```

### Coverage

```bash
# Per-column fill % on every sheet
xlsx-toolkit coverage ./data.xlsx

# Only columns with ≥50% fill
xlsx-toolkit coverage ./data.xlsx --threshold 0.5 --sheet Contacts
```

## Output conventions

- Human-readable goes to **stdout**; pass `--json` (where supported) for machine output.
- Status lines from `write` and `to-csv` (file-writes) go to **stderr**, so stdout stays clean for piping.
- All subcommands exit non-zero on usage errors and propagate `anyhow` errors with context on failure.

## When NOT to use this

- **Inside a Rust crate** that already depends on `umya-spreadsheet` or `calamine`. Use the in-tree dep directly — don't shell out to global binaries for production logic.
- **For `.xls` (old binary format)** — umya is xlsx-only. Convert first (`libreoffice --headless --convert-to xlsx file.xls`) or read it elsewhere.
- **For `.xlsm` macros** — umya reads the data but doesn't preserve macros on write. Don't round-trip a macro-enabled workbook through `write`.
- **For 100k+ row workbooks** — umya is fine but loads everything into memory. If memory is tight, dump to CSV and stream from there, or write a one-off using `calamine` (much faster reader, read-only).

## Project layout

```
~/.claude/skills/xlsx-toolkit/
├── SKILL.md
├── Cargo.toml
├── .gitignore                            ← /target/ and *.skill ignored
├── references/
│   └── patterns.md                       ← gotchas, advanced usage, umya API notes
├── src/
│   ├── main.rs                           ← clap entry point
│   └── commands/
│       ├── mod.rs
│       ├── common.rs                     ← shared read/walk helpers
│       ├── inspect.rs
│       ├── to_csv.rs
│       ├── write.rs
│       ├── diff.rs
│       └── coverage.rs
├── bin/
│   ├── xlsx-toolkit                      ← shell shim, dispatches to prebuilt or cargo build
│   └── aarch64-apple-darwin/
│       └── xlsx-toolkit                  ← shipped prebuilt (~4 MB)
└── target/release/xlsx-toolkit           ← cargo-built fallback (gitignored)
```

To add another platform: `rustup target add <triple>`, `cargo build --release --target <triple>`, then copy `target/<triple>/release/xlsx-toolkit` to `bin/<triple>/xlsx-toolkit`. The shim picks it up automatically. For Linux targets from a Mac, use [`cross`](https://github.com/cross-rs/cross) (`cross build --release --target x86_64-unknown-linux-gnu`) to get the right glibc.

