hdd-analyzer
Old hard drives pile up faster than anyone can manually sort them, and most of what is on them is "dark data": junk, caches, and installers with a handful of genuinely irreplaceable files buried inside. hdd-analyzer walks a drive for free, estimates the token and dollar budget to have Jev (TypeSafe's System One decision model, also served through OpenRouter and Vercel AI Gateway) read every file, then runs a capped classification pass and produces a ranked report so a human can decide what is worth keeping before the drive gets wiped. Cheap, fast inference has made this kind of exhaustive per-file triage practical in a way it was not a few years ago.
What is Jev?
Jev is TypeSafe's System One decision model: instead of free-text generation, it answers a fixed set of yes/no questions (nouls), scored questions, and multiple-choice questions against a piece of state, returning calibrated probabilities instead of prose, which makes it well suited to structured classification like this tool's per-file rubric. hdd-analyzer talks to Jev directly through TypeSafe's API, through OpenRouter's Decisions endpoint, or through Vercel AI Gateway's TypeSafe-compatible endpoint, auto-detected from your API key.
Install
pip install hdd-analyzer
Or as an isolated tool with uv:
uv tool install hdd-analyzer
Requires Python 3.13+. To run the latest unreleased code instead, install from GitHub with pip install git+https://github.com/ajmeese7/hdd-analyzer.git.
Configuration
Create a .env file in the working directory you'll run hdd-analyzer from; it is loaded automatically.
| Variable | Required | Description |
|---|---|---|
TYPESAFE_API_KEY |
yes | A native TypeSafe key, an OpenRouter key (prefix sk-or-), or a Vercel AI Gateway key (prefix vck_). The provider is auto-detected from the prefix. |
JEV_PROVIDER |
no | typesafe, openrouter, or vercel, overrides the key-prefix auto-detection. |
Vercel AI Gateway's free tier rate-limits each model to 30 requests per minute; pass scan --rpm 28 there, or buy any amount of gateway credits to move to the paid tier, which lifts the limit.
| TESSERACT_CMD | no | Full path to tesseract.exe if it is not on PATH. Needed only for the optional ocr command. |
TYPESAFE_API_KEY=your-key-here
Do not double-quote TESSERACT_CMD in .env. python-dotenv treats a double-quoted value as an escaped string, so "\tesseract.exe" becomes a literal tab followed by esseract.exe. Leave it unquoted or single-quoted.
Quickstart
Everything below assumes an old drive mounted at D:\ (or /mnt/olddrive on Linux/macOS) with the interesting content under Users.
hdd-analyzer walk D:\ --run olddrive --include Users
hdd-analyzer estimate --run olddrive
hdd-analyzer scan --run olddrive --cap 5
hdd-analyzer report --run olddrive
hdd-analyzer manifest --run olddrive
walk inventories the drive for free. estimate prices out a full scan with no network calls. scan extracts local content and sends one classification call per file to Jev, prompting for confirmation and stopping at the cap. report renders runs/olddrive/report.md, report.csv, and report.html, ranked overall and per category. Open report.html in a browser for a navigable directory tree that shows where the notable files cluster, so you know where to look first when going through the drive by hand. manifest turns the results into a salvage copy list in runs/olddrive/manifest/, ready to hand to robocopy or rsync before the drive is wiped.
How it stays cheap and safe
walkis a pure filesystem pass: no network calls, no API spend.- Duplicate files are deduped by content hash (text/code/doc) or by size and filename (everything else), so Jev never classifies the same file twice.
- Dependency caches, build output, and other junk directories (
node_modules,.git,AppData\Local, and dozens more) are skipped before they ever hit the candidate list. scan --prefilterasks Jev about each directory listing first (only directories with 10 or more files) and skips the files of directories it judges not worth reading. Measured on a 50k-file Windows profile: half the calls, with 214 of 218 content-verified credential hits (every one valued 2.0 or higher), every file valued 2.5 or higher, and 97.7% of notable rows still found; on a curated Documents archive it skips nothing and costs under 1%. Decisions land inruns/NAME/prefilter.jsonlso you can see exactly what was skipped.estimateandscanprint the candidate count, estimated tokens, and estimated dollar cost, andscanasks for interactiveyconfirmation before spending anything (skip with--yes).scanenforces a hard spend cap (--cap, default $5) before dispatching each batch, using actual spend so far plus a worst-case estimate of the in-flight batch.- A preflight canary call and a circuit breaker abort the scan immediately on authentication or billing errors (HTTP 401/403/402), instead of burning through the candidate list on a broken key.
scanis resumable: re-running it skips files whose dedupe key is already inresults.jsonl, and failed files retry automatically.- Every result is labeled
content(Jev read the actual file),ocr(Jev read a Tesseract transcription), orname-only(Jev only saw the file name and metadata), and reports list content-verified hits first so a suggestive filename likepassport.pdfwith no extractable text can't be mistaken for a verified hit. - The tool never writes outside its own
runs/directory, and drives are only ever opened read-only.
Optional: OCR for images and scanned PDFs
scan never runs OCR itself; images and no-text PDFs get judged by Jev on filename alone. The separate ocr command is local, free, and makes zero API calls: it revisits an existing run's results, OCRs the highest-ranked name-only images and no-text PDFs with Tesseract, and writes runs/NAME/ocr.jsonl. Then scan --from-ocr re-classifies those rows using the OCR excerpt instead of re-running extraction.
Install Tesseract first:
winget install UB-Mannheim.TesseractOCR
On Linux or macOS:
sudo apt install tesseract-ocr
brew install tesseract
Then:
hdd-analyzer ocr --run olddrive
hdd-analyzer scan --run olddrive --from-ocr --cap 0.25
hdd-analyzer report --run olddrive
Commands
walk ROOT --run NAME [--include SUBPATH ...]- free, deterministic inventory walk. Writesruns/NAME/inventory.jsonl.estimate --run NAME- token and dollar estimate for a scan. No network calls.scan --run NAME [--cap USD] [--limit N] [--yes] [--dry-run] [--concurrency N] [--rpm N] [--prefilter] [--only-name-only] [--outdated-rubric] [--exclude-ext EXT[,EXT...]] [--from-ocr]- local extraction plus one Jev call per file, under a hard spend cap. Appends toruns/NAME/results.jsonland is resumable.--rpmpaces requests for rate-limited providers (Vercel AI Gateway's free tier allows 30 per minute; use--rpm 28).--outdated-rubricre-scores every row still carrying an older rubric version, since scores from different rubric versions are not comparable.--prefiltertriages directories before any per-file call (see above).annotate --run NAME [--all] [--top N] [--min-prob P]- backfillsmetadata_only/extraction_statusonto existing results by re-running local extraction only, zero API calls.ocr --run NAME [--top N] [--all] [--min-value V] [--limit N]- local, free OCR pass over an existing run's name-only image and no-text-PDF rows.report --run NAME [--top N] [--min-prob P] [--min-value V]- rendersruns/NAME/report.md,report.csv, andreport.html, ranked overall and per category.--min-valueand--min-probset which files count as notable in the HTML report; the defaults matchmanifest.manifest --run NAME [--min-value V] [--min-prob P] [--out DIR]- the salvage deliverable: read-only overresults.jsonl/inventory.jsonl, writes copy lists and summaries toruns/NAME/manifest/.
Output files
runs/NAME/inventory.jsonl- one row per walked file: path, size, mtime, extension, category, dedupe key.runs/NAME/walk-errors.log- files the walker could not read (permissions, long paths).runs/NAME/results.jsonl- one row per scanned file: all Jev probabilities, value score, extraction status, tokens used.runs/NAME/prefilter.jsonl- withscan --prefilter, one row per directory asked about: itsworth_scanningprobability,kind, and file count. Directories below 0.05 were skipped.runs/NAME/ocr.jsonl- OCR excerpts for name-only images and no-text PDFs; contains real file content, keep it out of version control.runs/NAME/report.md/report.csv- the ranked report, overall and per category.runs/NAME/report.html- self-contained interactive report: summary tiles, the directories holding the most notable files, a collapsible directory tree with notable/scanned counts per subtree, and a searchable, filterable table of every notable file. Contains paths and scores only, never file content.runs/NAME/manifest/copy-list.txt,copy-list-verified.txt,copy-list-name-only.txt- one absolute source path per line, ready forrobocopyorrsync.runs/NAME/manifest/credentials.md- every row that scored high on credentials, paths only, with a reminder to rotate anything still valid.runs/NAME/manifest/by-category.md- a table per category plus a top-30 directory rollup.runs/NAME/manifest/summary.txt- counts, total bytes, and a documented copy-tool invocation.
Privacy
scan sends excerpts of file content to whichever API provider you configure (TypeSafe, OpenRouter, or Vercel AI Gateway) so Jev can classify them. runs/ocr.jsonl and runs/results.jsonl hold those excerpts locally, which can include credentials or other personal information pulled straight from your files; runs/ is gitignored for this reason and should never be committed or shared as-is.
Development
uv sync
uv run pytest
License
BSD 3-Clause. See LICENSE.
Release files for hdd-analyzer 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hdd_analyzer-1.2.0.tar.gz | 56.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hdd_analyzer-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 122.8 kB
Release files / hdd_analyzer-1.2.0.tar.gz
| Download URL | hdd_analyzer-1.2.0.tar.gz |
|---|---|
| Size | 56.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6c904aaab63557177148ffbf2d14579e131c659b5361f48de3e717a8c03e49c3
|
|
BLAKE2b-256 checksum How to use checksums |
0b20e66e35012ea9bed07fefcc19361eb7baec17977ec0ee20c40351aa593aa6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / hdd_analyzer-1.2.0-py3-none-any.whl
| Download URL | hdd_analyzer-1.2.0-py3-none-any.whl |
|---|---|
| Size | 66.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f0c02ffaff6149b1513bd9e71d9812fc89c6be6d1ee8a1dc4e3169be745ff65
|
|
BLAKE2b-256 checksum How to use checksums |
8fa50cd51cbf32f2387b5e7875841ec26fab07ab7c4f9c208d9551790cdff8f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency log