CLI · commit cab95ed

csvsleuth

Ask questions of CSV/Excel files in plain English, answered by safe SQL

Download csvsleuth.zip
Quickstart
unzip csvsleuth.zip && cd csvsleuth
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

csvsleuth

Ask questions of CSV, Excel and JSONL files in plain English. csvsleuth loads your files into an in-memory-style SQLite database, sends only a compact schema card (never your data) to Claude Haiku 5.5, runs the SQL it writes inside a read-only sandbox, and shows the result as a table, optionally with a terminal bar chart and a plain-English explanation of the query.

Features

Install

uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
export ANTHROPIC_API_KEY=sk-ant-...   # or: ant auth login

Quickstart

No key needed for these (dry run and the fixture-backed demo model):

.venv/bin/python examples/make_examples.py        # optional: regenerate the sample data
.venv/bin/csvsleuth examples/sales.csv examples/customers.xlsx "top 5 customers by revenue in Q3" --dry-run
.venv/bin/csvsleuth examples/sales.csv examples/customers.xlsx "top 5 customers by revenue in Q3" \
    --fixtures examples/fixtures.json --sql --chart

With a real key (calls Claude Haiku):

.venv/bin/csvsleuth examples/sales.csv "which product has the highest average discount?" --sql --verbose
.venv/bin/csvsleuth shell examples/sales.csv examples/customers.xlsx examples/expenses.csv

Usage

csvsleuth FILE [FILE...] "question" [options]
csvsleuth shell FILE [FILE...] [options]

  --sql          print the generated SQL
  --chart        unicode bar chart (needs one label column and one numeric column)
  --export PATH  write the full result (up to 100,000 rows) to CSV
  --limit N      rows to display (default 50)
  --timeout S    query time limit in seconds (default 5)
  --dry-run      show system prompt, schema card and estimated tokens; no API call
  --no-cache     skip the response cache
  --model ID     default claude-haiku-5-5 (or env CSVSLEUTH_MODEL)
  --fixtures F   answer from a fixture file instead of the API
  --verbose      token usage and estimated cost

Dry run (real output, no key needed)

USER
SCHEMA
TABLE "sales" (5000 rows)
  order_id TEXT e.g. ORD-00001, ORD-00002, ORD-00003; 5000 distinct
  order_date DATE e.g. 2024-02-07, 2024-03-08, 2024-05-25; range 2024-01-01..2024-12-31; 366 distinct
  customer_id INTEGER e.g. 17, 24, 46; range 1..60; 60 distinct
  product TEXT values: Backpack | Desk Lamp | Headphones | Keyboard | Notebook | Water Bottle
  quantity INTEGER e.g. 2, 1, 4; range 1..6
  unit_price INTEGER e.g. 449, 2499, 899; range 120..2499
  discount_pct INTEGER e.g. 15, 0, 5; range 0..15
  revenue REAL e.g. 763.3, 2499.0, 3056.6; range 102.0..14994.0; 144 distinct

TABLE "customers" (60 rows)
  customer_id INTEGER e.g. 1, 2, 3; range 1..60; 60 distinct

Answer with the fixture-backed demo model (real output)

Fixtures are canned model answers (examples/fixtures.json), so this exercises the loader, sandbox, table and chart for real while the SQL comes from the file. The sections below that use --fixtures do not call any API.

$ csvsleuth examples/sales.csv "month-over-month revenue growth" --fixtures examples/fixtures.json --chart --limit 4
┏━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ month   ┃      revenue ┃ growth_pct ┃
┡━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ 2024-01 │ 1,680,283.60 │            │
│ 2024-02 │ 1,516,847.35 │      -9.70 │
│ 2024-03 │ 1,867,633.50 │      23.10 │
│ 2024-04 │ 1,576,956.80 │     -15.60 │
└─────────┴──────────────┴────────────┘
4 row(s) shown (more available; raise --limit or use --export)
revenue by month
2024-01 │███████████████████████████████████▉ 1,680,283.60
2024-02 │████████████████████████████████▍ 1,516,847.35
2024-03 │████████████████████████████████████████ 1,867,633.50
2024-04 │█████████████████████████████████▊ 1,576,956.80

Explanation Totals revenue per calendar month and compares each month with the previous one as a percentage.

Indian number formats and dd/mm/yyyy dates in expenses.csv load as REAL and DATE:

$ csvsleuth examples/expenses.csv "how much was spent on travel each month" --fixtures examples/fixtures.json --limit 3
note: expenses.date: dates read as dmy
┏━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ month   ┃ travel_spend ┃
┡━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 2024-01 │    90,374.57 │
│ 2024-02 │    31,235.02 │
│ 2024-03 │   122,108.35 │
└─────────┴──────────────┘

Self-repair

The "typo demo" fixture first answers with a nonexistent column; csvsleuth sends the SQLite error back and the fixture's repair_sql is used:

$ csvsleuth examples/sales.csv "typo demo" --fixtures examples/fixtures.json --limit 3

Explanation Totals revenue per product.
(first query failed; repaired automatically)

Sandbox in action

Hostile model output (DROP, multiple statements, PRAGMA, runaway recursion) is refused. Real output from a fixture file containing those queries:

The query could not be run (even after one repair): only SELECT queries are allowed
The query could not be run (even after one repair): multiple statements are not allowed
The query could not be run (even after one repair): query exceeded the 1s time limit

Shell

$ csvsleuth shell examples/sales.csv examples/customers.xlsx
sleuth> top 5 customers by revenue
sleuth> only the ones in the South     # sees the previous SQL as context
sleuth> .sql     # last SQL     .schema     .help     .quit

Configuration

Setting How
API credentials ANTHROPIC_API_KEY or an ant auth login profile
Model --model or CSVSLEUTH_MODEL (default claude-haiku-5-5)
Cache directory CSVSLEUTH_CACHE (default ~/.cache/csvsleuth) keyed by sha256 of model, system prompt and messages
Shell history ~/.csvsleuth_history

Cost: Haiku 5.5 is $0.10 / 1M input and $0.50 / 1M output tokens; a typical question is a few hundred input tokens (a fraction of a cent). --verbose prints the exact numbers; cache hits are free.

Architecture

Module Role
inference.py number/date/boolean inference, date-order resolution, name sanitisation
loader.py file readers, Table, temp-file Database reopened read-only
schema.py schema card and join hints, size bounded
sandbox.py statement scanner, authorizer, timeout, LIMIT injection
llm.py prompt, Haiku call with JSON schema output, cache, usage/cost
engine.py ask, execute, repair once, session history
chart.py unicode bar chart
fixtures.py fixture-backed fake client for offline demos
cli.py click CLI, rendering, shell

See docs/design.md for the algorithms with worked examples.

Tests

.venv/bin/pytest -q

All tests use a fake client; no network or key is needed.

Limitations