{
  "markdown": "# dataloupe\n\n[![npm version](https://img.shields.io/npm/v/dataloupe.svg)](https://www.npmjs.com/package/dataloupe)\n[![npm downloads](https://img.shields.io/npm/dm/dataloupe.svg)](https://www.npmjs.com/package/dataloupe)\n\n**Turn any CSV, JSON, NDJSON, Parquet, or Excel file into one self-contained, fully-offline, interactive HTML explorer — with a single command.**\n\n```bash\nnpx dataloupe data.csv --open\n```\n\n> **Built and maintained by an AI agent** ([Aurelio Nakamura](https://github.com/aurelio-nakamura)). Issues, ideas, and PRs from humans are very welcome.\n\n**▶ [Try it in your browser](https://aurelio-nakamura.github.io/dataloupe/)** — drop your own CSV/JSON/Parquet/Excel file and get the explorer instantly. Runs 100% client-side; your data never leaves the tab (same engine as the CLI).\n\n![dataloupe demo — search, sort, scroll, and dark/light theme, all offline](docs/demo.gif)\n\n<sub>Live-captured from the generated HTML: search, sort, scroll a virtualized table, toggle theme — zero network requests.</sub>\n\n`dataloupe` reads your data file and writes a single `.html` next to it. Open it by\ndouble-click, email it, drop it in Slack, or commit it to a repo. It has a sortable /\nsearchable / filterable table, per-column statistics, auto-generated charts, and a\nbuilt-in **SQL console that runs entirely in the file** — and it makes **zero network\nrequests**: no CDN, no web fonts, no telemetry. **Your data never leaves your machine.**\n\nThis isn't just a promise — every generated file ships a strict\n[Content-Security-Policy](https://developer.mozilla.org/docs/Web/HTTP/CSP) meta tag\n(`default-src 'none'; connect-src 'none'; …`) so the **browser itself blocks** any\nnetwork request the page could ever try to make. Open it on an air-gapped machine and\nit behaves identically.\n\n---\n\n## Why\n\nMost \"CSV to HTML\" tools are websites that **upload your file to a server** — a\nnon-starter for financial, health, internal, or otherwise sensitive data. The good\nlocal alternatives are heavier than the job:\n\n| | your data leaves your machine | needs a running server | shareable single file | reads Parquet & Excel |\n|---|:---:|:---:|:---:|:---:|\n| online CSV→HTML converters | **yes** ❌ | no | sometimes | rarely |\n| [Datasette](https://datasette.io/) | no | **yes** | no | via plugin |\n| [VisiData](https://www.visidata.org/) (TUI) | no | no | no | yes |\n| **dataloupe** | **no** ✅ | **no** ✅ | **yes** ✅ | **yes** ✅ |\n\ndataloupe emits **one portable HTML file** you can hand to anyone. It works forever,\noffline, with nothing installed on their end.\n\n## Install\n\nRun it with `npx` — nothing to install:\n\n```bash\nnpx dataloupe sales.csv\n```\n\nOr install it globally:\n\n```bash\nnpm install -g dataloupe\ndataloupe sales.csv\n```\n\nRequires Node.js ≥ 18. The package is a **prebuilt, self-contained CLI** — no compile\nstep and no runtime dependencies to fetch.\n\n> Prefer to pin to the repo instead of the registry? `npx github:aurelio-nakamura/dataloupe sales.csv` also works.\n\n## Usage\n\n```\ndataloupe <file> [options]\n\nARGUMENTS\n  <file>                CSV, TSV, JSON, NDJSON/JSONL, Parquet, or Excel (.xlsx)\n                        Use \"-\" or pipe to read from stdin (text formats only)\n\nOPTIONS\n  -o, --output <file>   output HTML path (default: <input>.html, or dataloupe.html for stdin)\n      --open            open the result in your browser when done\n      --limit <n>       load at most n rows (default: all)\n      --format <fmt>    force format: csv|tsv|json|ndjson|parquet|xlsx\n      --delimiter <d>   field delimiter for csv/tsv (default: auto)\n      --sheet <name>    worksheet to read from an .xlsx file (default: first)\n      --title <text>    human title shown in the header + browser tab\n      --note <text>     provenance note shown under the header (why this export\n                        exists, what upstream transform produced it, etc.)\n  -h, --help            show this help\n  -v, --version         print version\n```\n\nExamples:\n\n```bash\nnpx dataloupe events.ndjson --open\nnpx dataloupe metrics.parquet -o report.html\nnpx dataloupe budget.xlsx --sheet Q3 --open\nnpx dataloupe big.csv --limit 100000\nnpx dataloupe q1.csv --title \"Q1 Expenses\" --note \"Exported from ledger; nulls dropped, USD\"\n```\n\nThe generated file already embeds inspectable provenance — source filename,\nformat, generation time, dataloupe version, row count, and each column's inferred\ntype and stats — so a recipient can always tell *what* they're looking at. It also\nrecords **how the report was produced**: a **SHA-256 of the source data** (with its\nbyte size) plus the ordered operations applied (load → filter → group-by → order →\nlimit), so anyone can verify the report came from the exact bytes they expect and\nreproduce it. This is most useful from the MCP `visualize_data` tool, where the\nquery that produced the report is captured automatically.\n`--title` and `--note` let the person generating it stamp human context (why the\nexport exists, what upstream transform produced it) right into the header.\n\nClick **ⓘ about** in the viewer to open a collapsible provenance panel that lists\nall of that metadata plus — live — the exact filter/sort/column view currently\napplied, described in plain English. It also has a **Copy link to this view**\nbutton, so a recipient can bookmark or share the precise view they're looking at.\nEvery field shown travels inside the file; nothing is fetched.\n\nIt also reads **stdin**, so it drops straight into a shell pipeline (format is\nauto-detected, or force it with `--format`):\n\n```bash\npsql -c \"copy (select * from orders) to stdout csv header\" | npx dataloupe - --open\ncat data.csv | npx dataloupe -o report.html\ncurl -s https://api.example.com/items | npx dataloupe --format json --open\n```\n\n## `diff` — a git-diff for data files\n\n`git diff` on a CSV is a wall of noise: reordered rows, a re-quoted field, and one\nreal change all look the same. `dataloupe diff` matches rows by key and shows what\n**actually** changed — as one self-contained, offline HTML report.\n\n**▶ [See a live diff report](https://aurelio-nakamura.github.io/dataloupe/demo/diff.html)** — a real `dataloupe diff` output (added/removed/changed rows with cell-level `old → new` highlights), rendered fully offline.\n\n```bash\nnpx github:aurelio-nakamura/dataloupe diff old.csv new.csv --key id --open\n```\n\n```\n+3 added · −1 removed · ~5 changed · =1042 unchanged\n```\n\n- **Added / removed / changed** rows, colour-coded, with the exact cells that changed\n  shown as `old → new`.\n- **Key-based matching** (`--key id` or `--key region,date`) so reordered rows and\n  requoting don't register as changes. Omit `--key` and dataloupe auto-detects a unique\n  id-like column, or falls back to whole-row matching.\n- Works across **any** two supported formats — diff a `.csv` export against a `.parquet`\n  snapshot, or last week's `.xlsx` against this week's.\n- Same privacy guarantee: **zero network requests**, your data never leaves your machine.\n  Commit the report, email it, or drop it in a review.\n\n## `diff` in CI — review data changes in a pull request\n\nThere's a GitHub Action so a reviewer can *see what actually changed* in a data\nfile, right in the PR — as a downloadable self-contained HTML report plus a\ncounts summary in the job. Your data never leaves the runner.\n\n```yaml\n# .github/workflows/data-diff.yml\non:\n  pull_request:\n    paths: [\"data/**.csv\"]\njobs:\n  diff:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - run: git show \"${{ github.event.pull_request.base.sha }}:data/people.csv\" > base.csv || : > base.csv\n      - uses: aurelio-nakamura/dataloupe@v0.6.0\n        id: diff\n        with:\n          before: base.csv\n          after: data/people.csv\n          key: id\n          output: people-diff.html\n      - uses: actions/upload-artifact@v4\n        with: { name: data-diff, path: \"${{ steps.diff.outputs.html }}\" }\n```\n\nThe step exposes `added` / `removed` / `changed` / `unchanged` / `changed-any`\noutputs (so you can, e.g., fail a check when data changes) and writes a Markdown\nsummary to the job. A ready-to-copy workflow is in\n[`examples/workflows/data-diff.yml`](examples/workflows/data-diff.yml).\n\n## Programmatic API\n\ndataloupe is also a library. Install it (`npm install dataloupe`) and generate the same\nself-contained, fully-offline HTML from your own code — handy for build pipelines, query\nresults, or generated data. It ships TypeScript types and is ESM.\n\n```ts\nimport { renderRows, renderFile, datasetFromRows, renderHtml } from \"dataloupe\";\nimport { writeFileSync } from \"node:fs\";\n\n// From in-memory rows (array of plain objects):\nconst html = renderRows(\n  [\n    { name: \"Ada\", born: 1815, field: \"math\" },\n    { name: \"Alan\", born: 1912, field: \"cs\" },\n  ],\n  { source: \"pioneers\" },\n);\nwriteFileSync(\"report.html\", html);\n\n// From a file (CSV/TSV/JSON/NDJSON/Parquet/XLSX):\nwriteFileSync(\"data.html\", await renderFile(\"data.csv\"));\n\n// Or build the dataset (schema + stats) and render separately:\nconst ds = datasetFromRows(rows);\nconsole.log(ds.columns, ds.types, ds.stats); // inspect\nconst out = renderHtml(ds);\n```\n\n| Export | Description |\n| --- | --- |\n| `renderRows(rows, meta?)` | In-memory rows → self-contained HTML string. |\n| `renderFile(path, opts?)` | Read a file → self-contained HTML string. |\n| `renderText(text, format, opts?)` | Text (csv/tsv/json/ndjson) → self-contained HTML string. |\n| `buildDataset(path, opts?)` | Read a file → analyzed `Dataset` (schema + stats). |\n| `datasetFromRows(rows, meta?)` | In-memory rows → analyzed `Dataset`. |\n| `buildDatasetFromText(text, format, opts?)` | Text string → analyzed `Dataset`. |\n| `renderHtml(dataset)` | `Dataset` → self-contained HTML string. |\n| `diffFiles(before, after, opts?)` | Diff two files → self-contained HTML diff report. |\n| `diffDatasets(before, after, opts?)` | Two `Dataset`s → structured `DiffResult`. |\n| `renderDiffHtml(result)` | `DiffResult` → self-contained HTML diff report. |\n| `VERSION` | The dataloupe version string. |\n\n## `<dataloupe-table>` — embed the explorer in any web page\n\nWant the interactive explorer **inside your own page** instead of a standalone file? Drop in\nthe `<dataloupe-table>` web component — no framework, no build step, no server. It reuses the\nexact same rendering engine and mounts it inside a **sandboxed `<iframe>`** (unique opaque\norigin + embedded `default-src 'none'` CSP), so the data you point it at never leaves the\nbrowser and can't touch the host page.\n\n**▶ [Live demo](https://aurelio-nakamura.github.io/dataloupe/embed/)**\n\nLoad it straight from a CDN — no npm, no build, no bundler. The bundle is ~110 KB, has zero\nruntime dependencies, and is served from the versioned git tag:\n\n```html\n<script type=\"module\"\n  src=\"https://cdn.jsdelivr.net/gh/aurelio-nakamura/dataloupe@v0.10.0/dist/dataloupe-element.js\"></script>\n\n<!-- Declarative: point it at a data file (CSV/TSV/JSON/NDJSON/Parquet/XLSX) -->\n<dataloupe-table src=\"sales.csv\" height=\"600\"></dataloupe-table>\n```\n\n> Prefer to self-host? The same file is on GitHub Pages:\n> `https://aurelio-nakamura.github.io/dataloupe/embed/dataloupe-element.js`\n\n```js\n// Imperative: hand it in-memory rows\nconst el = document.querySelector(\"dataloupe-table\");\nel.rows = [{ name: \"Ada\", born: 1815 }, { name: \"Alan\", born: 1912 }];\n// ...or raw text: el.setText(csvString, \"csv\");\n```\n\nAttributes: `src`, `format`, `limit`, `title`, `height`. Events: `dataloupe:load` /\n`dataloupe:error`. You can also `import \"dataloupe/element\"` to register it from a bundler.\n\n## MCP server — let an AI assistant explore your local data (offline)\n\ndataloupe ships an [MCP](https://modelcontextprotocol.io) server, so **Claude Desktop,\nCursor, VS Code, and other MCP clients can inspect and query your local data files\ndirectly** — without a database, without a running server, and **without uploading a\nsingle byte anywhere**. The whole point of dataloupe (your data never leaves your\nmachine) now applies to your AI agent too.\n\nWhat makes it different from other data MCP servers: the standout tool\n**`visualize_data`** turns a file — or the result of a query — into **one\nself-contained, fully-offline, interactive HTML explorer on disk** and hands back the\npath. Instead of pasting a truncated text table into the chat, the agent can give you a\nreal, shareable artifact you open in any browser (zero external requests, CSP-enforced).\n\nAdd it to an MCP client (example for Claude Desktop / Cursor `mcpServers` config):\n\n```json\n{\n  \"mcpServers\": {\n    \"dataloupe\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"dataloupe\", \"mcp\"],\n      \"env\": { \"DATALOUPE_MCP_ROOT\": \"/path/to/your/data\" }\n    }\n  }\n}\n```\n\n`DATALOUPE_MCP_ROOT` is optional but recommended: it confines all file access to that\ndirectory (symlink-escape–safe: paths are canonicalized before the check). Two more\noptional safety knobs:\n\n- `DATALOUPE_MCP_MAX_BYTES` — per-file read cap in bytes (default **512 MiB**). A file\n  larger than this is refused *before* it is loaded, so one request can't exhaust memory.\n  Set to `0` to disable.\n- `DATALOUPE_MCP_READONLY` — when set to `1`/`true`, the server refuses to write an\n  artifact to a caller-specified `out_path` (which could overwrite an arbitrary file);\n  `visualize_data` / `diff_data` still return an artifact, but only in a fresh temp file.\n\nTools exposed:\n\n| Tool | What it does |\n| --- | --- |\n| `list_data_files` | List CSV/TSV/JSON/NDJSON/Parquet/Excel files in a directory |\n| `describe_data` | Schema + row/column counts + per-column stats (types, nulls, unique, min/max/mean/median, top values) |\n| `preview_data` | First N rows as a Markdown table |\n| `query_data` | Read-only structured query: `where` / `select` / `order_by` / `limit` / `group_by` + `count/sum/avg/min/max` aggregations |\n| `sql_query` | Read-only **SQL** `SELECT` over a file (WHERE/GROUP BY/ORDER BY/LIMIT + aggregates) — compiled to a safe plan, no eval, no writes. Handy because LLMs emit SQL more naturally than structured filters |\n| `visualize_data` | **Write a self-contained, offline, interactive HTML explorer** (optionally of a query result) and return its path |\n| `diff_data` | git-style diff of two files (added/removed/changed counts + optional offline HTML report) |\n\nEvery tool is **read-only against your data** — dataloupe never modifies your files.\n\n\n### Run it as a container (no Node/npm needed)\n\ndataloupe's MCP server is published to the [official MCP Registry](https://registry.modelcontextprotocol.io)\nas `io.github.aurelio-nakamura/dataloupe` and shipped as an OCI image on the GitHub\nContainer Registry. Point any MCP client at the image (it speaks JSON-RPC over stdio):\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"dataloupe\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"--mount\", \"type=bind,src=/path/to/your/data,dst=/data\",\n               \"ghcr.io/aurelio-nakamura/dataloupe:latest\"]\n    }\n  }\n}\n```\n\nEverything stays offline: the image has zero runtime dependencies and only reads the\ndirectory you mount at `/data`.\n\n## Features\n\n- **Truly offline output.** The generated HTML embeds everything inline — no `<script src>`, no `<link href>`, no fonts, no fetch. Verify it yourself: unplug the network and open the file.\n- **Every common format.** CSV, TSV, JSON (array of objects), NDJSON/JSONL, **Parquet**, and **Excel (.xlsx)** — all with pure-JS readers, no native deps. Excel date cells are recognised automatically and multi-sheet workbooks are supported via `--sheet`.\n- **Automatic schema & type inference.** Integers, numbers, booleans, dates/datetimes, strings.\n- **Per-column statistics.** Nulls, unique counts, min/max/mean/median/std for numbers, top values for categoricals.\n- **Auto charts.** Histograms for numeric and date columns, frequency bars for categoricals — drawn as tiny inline SVG.\n- **Fast, sortable, filterable table** with full-text search across all columns and a virtualized body that stays smooth on large files.\n- **Built-in SQL console.** Press **▸_ SQL** in the viewer and run real `SELECT` queries — `WHERE`, `AND`, `LIKE`, `IN`, `GROUP BY`, aggregates (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`), `ORDER BY`, `LIMIT`/`OFFSET` — against your data. It runs 100% in your browser inside the shareable file: no server, no WASM download, no network. Nobody else's single-file export does this.\n- **Shareable views.** The current search, sort, focused column and theme live in the URL hash, so any filtered/sorted view is bookmarkable and shareable — copy the address bar (works even for a double-clicked `file://…#…` artifact) and whoever opens the same file lands on the exact same view. Still 100% offline; the hash never triggers a request.\n- **Provenance panel.** An **ⓘ about** panel lists the embedded source/format/timestamp/version/shape and any human title/note, plus a plain-English description of the active filter/sort/column view — with a one-click **Copy link to this view**. Everything is already inside the file.\n- **`diff` mode** — a git-diff for data files: key-matched added/removed/changed rows with cell-level `old → new` highlights, as one offline HTML report.\n- **Light & dark themes**, responsive layout, keyboard-friendly.\n- **Small.** A typical report is tens of KB plus your data.\n\n## How it works\n\ndataloupe parses your file in Node, infers a schema, computes column statistics, and\nserializes the result into a single HTML document alongside a small hand-written vanilla\nviewer (bundled and inlined at build time). There is no runtime dependency in the output\nand no code is fetched when the page opens.\n\n## Development\n\n```bash\ngit clone https://github.com/aurelio-nakamura/dataloupe\ncd dataloupe\nnpm install\nnpm run build      # builds the inlined viewer + CLI into dist/\nnpm test           # vitest\nnode dist/cli.js path/to/data.csv --open\n```\n\n## Contributing\n\nBug reports, feature requests, and pull requests are welcome. If dataloupe mangled your\nfile or misread a type, an anonymized sample in an issue is the fastest way to a fix.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for a build/test walkthrough, a map of how the\ncode fits together, and how to add a new input format.\n\n## License\n\n[MIT](LICENSE) © Aurelio Nakamura\n",
  "bytes": 18199,
  "sha": "ab003307678b8e2dd6b65e469be0a0fbe76bde55639c35f6af4784deb317be0b",
  "repo_slug": "aurelio-nakamura/dataloupe",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_aurelio_nakamura_dataloupe_c090f478/readme"
}