@pathcosmos/dleft
v0.1.7
Published
ASCII disk usage (physical disks, partitions, filesystems) for macOS and Linux.
Maintainers
Readme
dleft
ASCII disk usage for macOS and Linux — physical disks, partitions, and mounted filesystems, rendered as dense tables with usage bars. Read-only: never mutates disk state.
$ dleft
PHYSICAL DISKS
ID MODEL SIZE (TiB) USED (TiB) FREE (GiB) USE% BAR
disk3 APPLE SSD AP1024N 0.9 0.4 507.6 45% █████▓▓▓▓▓▓
disk13 External 2TB 1.8 1.0 790.0 58% ██████▓▓▓▓▓
FILESYSTEMS
MOUNT DEVICE FSTYPE SIZE (GiB) USED (GiB) FREE (GiB) USE% BAR
/mnt/data /dev/sdb1 ext4 1863.0 931.3 931.3 50% █████▓▓▓▓▓
/home /dev/mapper/… ext4 367.8 191.9 175.9 52% █████▓▓▓▓▓
/ /dev/mapper/… ext4 98.0 33.0 67.0 33% ███▓▓▓▓▓▓▓Cells turn yellow at ≥75 % use and red at ≥90 %.
Quick start
From source (current path until v0.1.0 hits npm)
git clone https://github.com/pathcosmos/cli-ascii-usage
cd cli-ascii-usage
npm install
npm run build
node dist/cli.js # run once
npm link # optional: expose `dleft` globallyFrom npm
npm install -g @pathcosmos/dleft
# or one-shot:
npx @pathcosmos/dleftThe bin name is dleft (so the global install gives you dleft, not pathcosmos-dleft). Requires Node.js ≥ 22. Single-file ESM bundle, no postinstall hooks.
Usage
dleft [options]| Flag | Description | Default |
|---|---|---|
| -j, --json | Emit JSON (schemaVersion: 1); suppresses color. | off |
| -a, --all | Include pseudo-filesystems (tmpfs, overlay, etc.). | off |
| -s, --sort <field> | Sort by size / used / free / use% / name. | size |
| --si | SI units (KB, GB, TB) instead of IEC (KiB, GiB, TiB). | IEC |
| --no-bars | Hide the bar column. | bars on |
| --no-color | Disable ANSI color. Respects NO_COLOR env. | auto |
| --only <section> | Render only disks or fs. | both |
| -h, --help | Show help. | — |
| -V, --version | Print version. | — |
Recipes
dleft --sort use% # which mounts are full?
dleft -a # show tmpfs, overlay, etc.
dleft --only disks # just the physical-disk summary
dleft --no-color > usage.txt # capture for diff later
dleft --json | jq '.filesystems[] | select(.usedBytes / .sizeBytes > 0.9) | .mountpoint'Environment
NO_COLOR=1— disables color (standard).DLEFT_ASCII=1— forces ASCII bar chars (#,-) instead of unicode blocks.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (may still emit warnings to stderr). |
| 1 | Fatal: unsupported platform, missing required command, parse failure. |
| 2 | Invalid argument. |
How it works
┌──────────────────────┐
│ src/cli.ts │ parseArgs → RenderOptions
└────────────┬─────────┘
│
┌────────────────────┴────────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ collectors/ │ │ render/ │
│ ├ darwin.ts │ diskutil + df │ ├ disks.ts │
│ ├ linux.ts │ lsblk + df │ ├ filesystems.ts│
│ └ index.ts │ │ ├ bar / unit / │
└────────┬─────────┘ │ │ color / width│
│ DiskReport │ └ index.ts │
└─────────────────►─────────────► pure(): string │
└──────────────────┘- Collectors spawn subprocesses through a
CommandRunnerinterface (DI seam) and parse output into aDiskReport. The production runner uses Node'sexecFilewith a fixed argv array — no shell, no injection surface — and a 5-second timeout. Tests use aFakeRunnerseeded from committed fixtures. - Render is a pure function:
render(report, opts) → string. No stdout, no env, no TTY checks. All environmental decisions (color, width, unicode) happen incli.tsand are passed in. - A timeout on one data source emits a warning and the others still render (partial success → exit 0).
Platform notes
| Platform | Required commands |
|---|---|
| macOS | diskutil, df |
| Linux | lsblk, df |
macOS: APFS containers
One physical-disk row per APFS container. Used bytes come from container-level CapacityCeiling - CapacityFree, not Σ per-volume CapacityInUse — summing volume usage double-counts shared space across APFS volumes in the same container. If diskutil apfs list times out, the disk summary is omitted with a warning rather than inferred from df (silently wrong numbers in a disk-space tool are worse than fewer numbers).
Linux: LVM, LUKS, overlay
Filesystems are joined to physical disks by walking the lsblk tree. LVM volumes and LUKS-backed partitions map to their parent block device. Bind mounts and overlay filesystems are flagged as pseudo and hidden by default (--all to show).
JSON output
dleft --jsonEmits a DiskReport document. The shape is stable under schemaVersion: 1; gate downstream consumers on that field:
dleft --json | jq 'if .schemaVersion == 1 then .filesystems else error("unexpected dleft schema") end'schemaVersion will only bump on a breaking change to the report shape — non-breaking additions keep 1.
Why not just df?
dfdoesn't show physical-disk totals or APFS container reconciliation.dfoutput alignment breaks on long device paths (/dev/mapper/…).dfdoesn't know about bind mounts, pseudo-filesystems, or LVM parent disks.dleftisn't a replacement — it's a denser skim for "where is my space?"
Development
npm install
npm test # Vitest: unit + collectors + CLI + smoke (~1.4 s)
npm run typecheck # tsc --noEmit
npm run build # tsup → dist/cli.js (12 KB ESM bundle, shebang)
node dist/cli.js # run the built bundleFixtures live under tests/fixtures/{darwin,linux}/. FakeRunner (tests/helpers/fake-runner.ts) injects fixture strings so collector tests run the real parser without spawning subprocesses. Refresh fixtures via the snippets in tests/fixtures/README.md.
CI matrix: Node 22 / 24 × Ubuntu / macOS. Releases publish to npm via OIDC keyless provenance on v* tag push.
License
MIT. See LICENSE.
