xray is the first move on an unfamiliar CSV or DSV. One pass, one command, and it tells you what the file is before you touch it: its shape, what each column really holds, and — the part that earns it a place in your toolkit — what is going to bite you when you clean or query it. You x-ray the file, you read the film, and you know your next move.
Project page: https://excelano.com/xray/
$ xray contracts.csv
FILM
20 columns × 4,812 rows header: row 1 6.4 MB
delimiter comma encoding utf-8 line endings CRLF
READING
col header type fill distinct detail
A contract_id text · leading-0 100% 4812 0007 … 9944 ! keep as text
E fy25_spend text · currency 100% 4120 $0 … $1,204,880.00 ! not numeric
H renewals int · MIXED 96% 14 0 … 37 ! 3 non-numeric values
...
FINDINGS (5) 1 correctness · 3 type safety · 1 structure
correctness
1 ! total row 4813 — pre-aggregated "$18,442,905.00"; a summary line, not data
type safety
2 ! contract_id (A) is leading-zero text — 0007, 0044; a numeric cast strips the zeros
3 ! fy25_spend (E) is currency text, not a number — $ and thousands commas; de-currency before math
...
Every job on a messy CSV starts the same way: orient before acting. What are the columns, what type is each really, how many rows, how many blanks, what's the delimiter, is a header even on the first row? That first-contact profiling has been scattered across half a dozen general tools — qsv, csvstat, datamash, head — none of which quite answers the question you actually have, which is not "what's the mean of column 4" but "what's going to break when I touch this?"
xray answers that. Its findings register is a ranked problem list, not more statistics: ragged rows, total rows masquerading as data, leading-zero IDs a cast would mangle, currency trapped as text, mixed-type columns, headers buried under title rows. Everything it flags is damage that would corrupt a later step, and every finding names the tool that fixes it.
It is stringly-typed on purpose. 02134 is text, not the number 2134; an 18-digit ID is text, not a rounded float — because silently coercing those is exactly the surprise xray exists to catch. And it only ever observes: it never edits a value, drops a row, or filters a result. The day it grows a --where it has become a worse query tool.
xray is the read-only member of the tabular family, four small tools for messy tabular data split by what changes:
- xray observes — a fixed whole-file profile, and never writes.
- xled edits cell values in place (sed and awk for tables).
- xshape changes the shape of the grid, and never touches the values.
- xql queries the row set — filter, aggregate, group, join.
The other three act on the data; xray only looks. Its findings hand you off to them: leading zeros and total rows go to xled for a crop, a column trapped as one delimited cell goes to xshape, currency-cleaned columns go to xql for the query.
Add the Excelano apt repository once:
curl -fsSL https://excelano.com/apt/setup.sh | sudo shThen install it, so apt upgrade keeps it current:
sudo apt install xrayBoth amd64 and arm64 packages ship with every release.
brew install excelano/tap/xraycargo install x-raycrates.io is the one place the name is hyphenated: the bare xray crate was taken (a dormant 2018 crate), so the crate publishes as x-ray. Everywhere else — the command, the apt package, the Homebrew formula — is xray. The installed binary is always xray.
curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/excelano/xray/main/install.sh | shTo remove it: swap install.sh for uninstall.sh in that line.
xray data.csv # the full profile: film, reading, findings
… | xray # profile piped data (`xray -` spells it explicitly)
xray --refer data.csv # also print which family tool treats each finding
xray --json data.csv # the same profile as structured JSON
xray --header 6 data.csv # force the header to row 6 (0 = no header)
xray --no-header data.csv # the first row is data, not a header
xray -d '|' data.csv # force the delimiter (use \t for tab)
xray --color never data.csv # plain output (also automatic when piped)xray auto-detects a buried header, sniffs the delimiter (quote-aware), and colours the output for a terminal while emitting plain text to a pipe. Everything it needs, it gathers in a single pass over the file. Both guesses can be overruled: --header or --no-header for the header row, -d/--delim for the delimiter.
Input comes from a file argument or from stdin, so xray sits in a pipe like the rest of the family; a piped profile reports its source as (stdin) and is otherwise identical to the same bytes read from disk.
xray was built for AI coding agents as much as for people, so the repo ships an official Claude Code skill under skills/xray/. It teaches an agent the three registers (film, reading, findings), the stringly-typed classification (why leading zeros and long IDs stay text), how to read the ranked findings, and the hard boundary — xray only observes; fixing values is xled's job and querying is xql's — so an agent profiles a file first instead of guessing at it. Drop it into your personal skills directory:
mkdir -p ~/.claude/skills/xray
for f in SKILL.md reference.md; do
curl -fsSL "https://raw.githubusercontent.com/excelano/xray/main/skills/xray/$f" \
-o ~/.claude/skills/xray/$f
doneOr, from a clone of this repo, cp -r skills/xray ~/.claude/skills/.