A tool from Excelano

xray

A read-only profiler for tabular data.

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. It never edits a byte.

Orient before you act

Every job on a messy CSV starts the same way: figure out what you're looking at before you change anything. What are the columns, what type is each one 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 — head, csvstat, datamash, a throwaway pandas cell — none of which quite answers the question you actually have, which is not “what's the mean of column four” but “what's going to break when I touch this?”

xray answers that question in three registers. Film is the shape: columns, rows, delimiter, encoding, line endings, and where the header actually sits. Reading is the per-column detail: the real type, how full the column is, how many distinct values, and a sample of the range. Findings is a ranked problem list — not more statistics, but the specific damage that would corrupt a later step: ragged rows, a total row masquerading as data, leading-zero IDs a cast would mangle, currency trapped as text, mixed-type columns, a header buried under three title rows. Every finding names the family tool that fixes it.

It is stringly-typed on purpose. 02134 is text, not the number 2134; an eighteen-digit identifier 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.

See it in action

An application-portfolio export, the kind of file I profile before I clean it: a currency column stored as text, a summary line hiding at the bottom, and one status field spelled six ways. One pass reads the whole thing.

$ xray app-portfolio.csv

FILM
  6 columns × 214 rows       header: row 1       28 KB
  delimiter comma   encoding utf-8   line endings LF

READING
  col  header         type              fill  distinct  detail
  A    app_name       text              100%       213   SAP ERP … TimeTracker
  B    owner          text · MIXED       98%       119   dept names and emails
  C    annual_cost    text · currency   100%       201   $12,000 … $1,250,000   ! not numeric
  D    status         text · MIXED      100%         6   Active, active, IN USE   ! 6 spellings
  E    last_reviewed  text · MIXED       94%       180   ISO and US dates mixed   ! two formats
  F    criticality    text              100%         3   High, Medium, Low

FINDINGS  (4)   1 correctness · 2 type safety · 1 consistency
  correctness
   1  ! total row 215 — pre-aggregated "$18,442,905"; a summary line, not data
  type safety
   2  ! annual_cost (C) is currency text — $ and thousands commas; de-currency before any math
   3  ! last_reviewed (E) mixes 2024-03-15 and 03/15/2024; a parse will disagree with itself
  consistency
   4  ! status (D) has 6 spellings for 3 states — Active/active/IN USE; normalize before you group

Nothing in the file changed — xray read it and handed back a plan. The currency column and the total row go to xled for a clean-up; the normalized, cropped table goes to xql for the query. Add --refer and xray prints exactly that: which tool treats each finding. Add --json and the same profile comes back structured, ready for a script or an agent to read.

The read-only member of a three-tool family

xray is one of three small tools for messy tabular data, and the only one that never writes. xray observes — a fixed whole-file profile, no edits, no filters. xled edits cell values in place, sed and awk for tables. xql queries the row set — filter, aggregate, group. Two of them act on the data; xray only looks.

That boundary is the whole point. xray's findings are a set of hand-offs: leading zeros and total rows go to xled for a crop, currency-cleaned columns go to xql for the query. It profiles first so the other two act on a file you actually understand, and it refuses to grow into either of them — the moment it starts fixing values or filtering rows, it stops being the tool you reach for first.

Install

xray is a single binary for Linux and macOS, with nothing to install alongside it. One naming note carries through every method below: on crates.io the crate is x-ray (the real word, and the free name there), but the apt package, the Homebrew formula, and the command you type are all xray.

On Debian or Ubuntu

Add the Excelano apt repository once, so apt upgrade keeps it current. Both amd64 and arm64 packages ship with every release:

curl -fsSL https://excelano.com/apt/setup.sh | sudo sh
sudo apt install xray

With Homebrew

On macOS or Linux, so brew upgrade keeps it current:

brew tap excelano/tap
brew trust excelano/tap   # one-time: Homebrew gates third-party taps behind explicit trust
brew install xray

On Linux or macOS

A one-line installer downloads the right build for your platform, verifies its checksum, and drops the xray command on your path:

curl --proto '=https' --tlsv1.2 -LsSf https://raw.githubusercontent.com/excelano/xray/main/install.sh | sh

With cargo

If you have a Rust toolchain, install the published crate from crates.io. This compiles from source rather than fetching a prebuilt binary, so it is slower than the installer above but needs nothing else:

cargo install x-ray

Prebuilt binaries

Raw tarballs for every platform are on the GitHub releases page. To build from source you need only a Rust toolchain; the README covers it.

Behind the tool

I built xray because I kept starting the same way on every messy file — squinting at head, guessing at the delimiter, discovering the buried total row only after it had already thrown off a sum. That first-contact orientation deserved a real tool, and the discipline underneath it is one I hold firmly: a tool that profiles your data has no business changing it. xray only ever looks. What it finds, it hands to something else.

Reading a strange export before I trust it — portfolio exports, inventories, the data that falls out of Microsoft 365 — is a real part of what I do for clients. If you have a pile of that work and would rather hand it to someone who treats the data with care, that's the kind of thing I'm glad to take on.

Get in touch

For technical users

xray is open source under the MIT license, written in pure Rust with no C dependencies and no runtime. It gathers everything it needs in a single pass over the file: it auto-detects a buried header, sniffs the delimiter quote-aware, and colours the output for a terminal while emitting plain text to a pipe. The full source, the classification rules, and the findings catalogue live at github.com/excelano/xray; the security policy is in SECURITY.md.

xray was built for AI coding agents as much as for people, so the repository ships an official Claude Code skill. It teaches an agent the three registers, the stringly-typed classification, 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.