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) 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 either directly through TypeSafe's API or through OpenRouter's Decisions 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 | Either a native TypeSafe key or an OpenRouter key (prefix sk-or-). The provider is auto-detected from the prefix. |
JEV_PROVIDER |
no | openrouter or typesafe, overrides the key-prefix auto-detection. |
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 and report.csv, ranked overall and per category. 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. 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] [--only-name-only] [--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.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]- rendersruns/NAME/report.mdandreport.csv, ranked overall and per category.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/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/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 or OpenRouter) 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.0.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.0.0.tar.gz | 39.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hdd_analyzer-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.3 kB
Release files / hdd_analyzer-1.0.0.tar.gz
| Download URL | hdd_analyzer-1.0.0.tar.gz |
|---|---|
| Size | 39.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
638104a20eab9b0c933324fb0f6444b34bb800f62ee00dcaf2376cbe8be1194b
|
|
BLAKE2b-256 checksum How to use checksums |
d997c804880fb2d478c7fe3eec87bfe3583187a70129bd9a63ed40f50ad87700
|
| 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 20, 2026.
Transparency logRelease files / hdd_analyzer-1.0.0-py3-none-any.whl
| Download URL | hdd_analyzer-1.0.0-py3-none-any.whl |
|---|---|
| Size | 47.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
abeae39690fa18cfcb7d5ebff8053f139de6145f54cb4c85cfb2d1c5ba0b724a
|
|
BLAKE2b-256 checksum How to use checksums |
65d14c2a6105d8a381e6e9813efba8d04733c20bf77771513ade1019d7b2be8a
|
| 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 20, 2026.
Transparency log