CHEST System Reference
Complete reference guide covering cross-platform installation, sorting grammar, high-speed fuzzy search, transaction rollbacks, and out-of-process RPC plugins.
Installation
Option 1: Go Install (Recommended)
Compiles directly from the official Go module proxy into your local toolchain:
$ go install github.com/Aswanidev-vs/chest/cmd/chest@latest
Ensure your GOBIN directory (typically ~/go/bin on Unix or %USERPROFILE%\go\bin on Windows) is present in your system PATH.
Option 2: Automated Linux & macOS One-Liner
Downloads and runs the automated bash installation script:
$ curl -sSL https://raw.githubusercontent.com/Aswanidev-vs/chest/main/install.sh | bash
Checks your local Go environment, builds from source, executes a self-test sanity check, and reports your PATH configuration.
Option 3: Automated Windows PowerShell
Downloads and executes the native PowerShell script (PowerShell 5.1+ or PowerShell 7+):
> irm https://raw.githubusercontent.com/Aswanidev-vs/chest/main/install.ps1 | iex
Option 4: Build from Source
Clone the repository and compile using Make or Go:
$ git clone https://github.com/Aswanidev-vs/chest.git
$ cd chest
$ make build
Quick Start
Test sorting your Downloads directory with a safe dry-run (no files are touched):
$ chest sort ~/Downloads -p downloads --dry-run
Apply the plan once satisfied:
$ chest sort ~/Downloads -p downloads
Accidentally sorted the wrong folder? Instantly revert all moved files to their exact previous paths:
$ chest undo
Architecture & Guarantees
CHEST is architected as five decoupled subsystems:
| Subsystem | Underlying Engine | Responsibility |
|---|---|---|
| Scanner | fastwalk concurrency |
Multi-threaded recursive directory traversal with exclusion filters and OS-agnostic hidden file logic. |
| Scorer | fzf/src/algo FuzzyMatchV2 |
Microsecond scoring of filenames, extensions, and content grep matching. |
| Planner | Directed Move Graph | Collision detection and transactional history logging to ~/.chest/history.db. |
| Storage Index | Embedded Pure-Go SQLite | Metadata caching, duplicate hashing, and storage intelligence at ~/.chest/index.db. |
| Metadata | Signature + Extractor Registry | Content-signature format detection (magic bytes first, extension fallback) and embedded metadata extraction — EXIF, QuickTime, PDF, audio tags, and ZIP-based OOXML/ODF/EPUB properties — via an open registry that supports custom formats. |
Command Reference
chest sort
Organizes target directory according to preset rules or custom conditions.
| Flag | Type | Description |
|---|---|---|
| -p, --preset | string | Built-in rule bundle: downloads, media, developer, documents, photos. |
| -n, --dry-run | boolean | Simulate organization without moving any files. |
| --rule | string | Custom predicate rule: "type=image && size>10MB -> HighRes/". Rules can match on extension= (filename extension) or format= (content-detected format — routes a file even if its extension is missing or renamed), plus embedded metadata fields: camera=, artist=, album=, genre=, title=, composer=, encoder=, software=, codec=, duration=, width=, height=, or meta.<key>= for any other extracted field. Custom binary formats can be registered programmatically via the metadata extractor registry. |
| -y, --yes | boolean | Skip interactive confirmation prompt and execute immediately. |
| -i, --into | string | Override destination root directory. |
| -d, --date | boolean | Group files by date. Default source is modification time and default granularity is year, e.g. 2024/, 2025/, and 2026/. |
| --date-source | string | Date source for --date and date placeholders: modified (default), auto (embedded media date with modification-time fallback), or taken (embedded date only), or filename (mine the date from the filename: IMG_20230115_..., WhatsApp Image 2024-05-01..., report_20250126.pdf; modification-time fallback). Embedded dates are extracted from EXIF DateTimeOriginal/CreateDate, QuickTime mvhd, PDF Info dictionary, ID3/Vorbis/FLAC/OGG tags, RIFF-INFO chunks, and ZIP-based OOXML/ODF/EPUB core properties. |
| --collision | string | Destination-collision policy: skip, rename, replace, abort. Default (flag omitted): any occupied destination, whether already on disk or claimed by another source in the plan, is auto-renamed (photo (1).jpg), case-normalized per platform, and listed in the dry-run CONFLICTS block. An explicit skip leaves the later file in place instead. |
| --hash-collisions | boolean | Hash only files in a destination conflict. Identical SHA-256 content is treated as a duplicate and is skipped by default; different content remains protected by the normal auto-rename chain. |
| --keep-identical | boolean | With --hash-collisions, retain byte-identical conflict participants by assigning the next safe name, such as photo (1).jpg, instead of leaving the duplicate in place. The decision is reported in CONFLICTS. |
| --json | boolean | With --dry-run, print the plan as JSON (operations, folders to create, conflicts) instead of the human report. |
| --date-granularity | string | Date folder granularity: year (default), month (YYYY/Jan), or day (YYYY/Jan/05). Month folders use month names unless --month-format number selects YYYY/01 / YYYY/01/05. |
| --month-format | string | How --date and the {month} placeholder render months: name (default, Jan, Feb), or number (zero-padded, 01, 02). Only affects month granularity, day granularity, and {month}; years and days stay numeric. |
| -f, --format | boolean | Group files into folders by their content-detected format (e.g. MP4/, MP3/, PNG/). Detection reads the file's magic-byte signature first and falls back to its extension, so a renamed or extension-less file is still classified correctly. Custom formats registered programmatically (e.g. .gocut) participate too. Use --into to wrap the folders (e.g. --format --into out → out/MP4/). |
| --allow-system | boolean | Bypass system path protection guard. Required when targeting OS-level directories (e.g. C:\Windows, /usr). Use with extreme caution. |
chest.toml configuration
Rules persist in a chest.toml file. CHEST walks up from the target directory to find a project config, and also reads ~/.chest/chest.toml for user-wide defaults. Rule sources stack with precedence flags > --rule > project chest.toml > user chest.toml > preset.
# chest.toml
default_preset = "downloads"
[[rules]]
name = "Large videos"
priority = 100
destination = "Videos/HiFi"
conditions = [
{ field = "type", operator = "=", value = "Video" },
{ field = "size", operator = ">", value = "2GB" },
]
[[rules]]
name = "Camera photos"
priority = 90
destination = "Photos/Camera"
conditions = [{ field = "camera", operator = "=", value = "Canon" }]
Convenience helpers (extensions, type, min_size, max_size) work exactly as in built-in presets, so the same shape is accepted everywhere.
chest rule check
Validates the rule set that would apply to a directory — flags, presets, and any discovered chest.toml — without scanning or moving files. It surfaces the problems that would otherwise be silently ignored at evaluation time: unparseable min_size/max_size values (which used to make a rule match everything), unsupported field/operator combinations, and invalid regexes.
$ chest rule check ~/Downloads -p media
✓ 12 rule(s) validated, no problems found.
$ chest rule check . --rule "name =~ '[unclosed' -> Images"
1 problem(s) found in 1 rule(s):
• rule 'name =~ '[unclosed' -> Images': invalid regex "[unclosed" for field "name"
chest repl
Build a sort rule set interactively, with a preview that re-plans after every edit. The target directory is scanned once; from then on add, edit and rm refine the in-memory rules while preview recomputes the plan from the current set and renders the same dry-run report as sort --dry-run. preview is strictly read-only — it creates no folders and moves nothing. Only apply executes, and it records history so the run can be reverted with chest undo.
$ chest repl ~/Downloads
CHEST rules builder — /home/you/Downloads (128 file(s) scanned)
Type 'help' for commands.
chest> add type=Video -> Videos
added type=Video -> Videos
chest> add size>1GB -> Large Files
added size>1GB -> Large Files
chest> preview
CHEST PLAN
────────────────────────
movie.mkv
FROM: movie.mkv
TO: Videos/movie.mkv
WHY: type = Video
────────────────────────
1 files would be moved.
No changes made.
chest> apply
1 file(s) will be moved.
Continue? [y/N]: y
[DONE] 1 file(s) organized into 1 folder(s).
chest> save media-rules
saved 2 rule(s) to /home/you/.chest/templates/media-rules.toml
chest> quit
Prompt commands
| Command | Description |
|---|---|
| help | List the prompt commands. |
| list | Show the current rule set, numbered as you would type it. |
| add <rule> | Add a rule using the same syntax as sort --rule. Rules added earlier take priority. |
| edit <n> <rule> | Replace the 1-based rule n, keeping its priority. |
| rm <n> | Remove the 1-based rule n. |
| preset [name] | Load a built-in preset, replacing the current rules. With no argument, list the available presets. |
| scan | Re-read the target directory, picking up files added while the session is open. |
| preview | Re-plan from the current rules and print the dry run. Mutates nothing. |
| apply | Execute the current plan after a [y/N] confirmation (skipped with --yes). |
| save <name> | Save the rule set to ~/.chest/templates/<name>.toml. |
| load <name> | Replace the current rules from a saved template. |
| templates | List saved templates. |
| quit / exit | Leave the builder. Ctrl-D also exits. |
| Flag | Type | Description |
|---|---|---|
| -p, --preset | string | Start the session from a built-in rule bundle: downloads, media, developer, documents, photos. |
| -i, --into | string | Base destination directory that category folders are placed inside. |
| -r, --recursive | boolean | Include subdirectories in the scan. |
| -H, --hidden | boolean | Include hidden files and folders. |
| -x, --exclude | string | Skip files or folders, e.g. .git,node_modules. |
| -c, --collision | string | Collision policy: skip (default), rename, replace, abort. |
| -y, --yes | boolean | Skip the confirmation prompt on apply. |
Rules can be saved to ~/.chest/templates/<name>.toml and loaded again later. Templates are stored as TOML rather than as rule strings, so a saved rule keeps every field a built-in preset rule can carry — including size bounds and extension lists that the --rule text syntax cannot express. Template names may not contain path separators. Unlike sort, repl refuses protected system directories outright and has no --allow-system escape hatch.
Walkthrough: build, then update
A complete session in five steps. The directory is scanned once; from then on every command re-plans from the current rules.
1. Build — start broad, check as you go
$ chest repl ~/Downloads
chest> add type=Video -> Videos
added type=Video -> Videos
chest> add type=Image -> Images
added type=Image -> Images
chest> list
2 rule(s):
1. type=Video -> Videos
2. type=Image -> Images
chest> preview
CHEST PLAN
────────────────────────
bigvideo.mp4
FROM: bigvideo.mp4
TO: Videos/bigvideo.mp4
WHY: type = Video
movie.mp4
FROM: movie.mp4
TO: Videos/movie.mp4
WHY: type = Video
photo.png
FROM: photo.png
TO: Images/photo.png
WHY: type = Image
────────────────────────
3 files would be moved.
2 folders would be created.
No changes made.
preview is strictly read-only: it re-plans from the current rules on every call, so it always shows what apply would do, and it never creates a folder or moves a file.
2. Update — mind the priority trap
Rules are evaluated top to bottom and the first match wins, so a narrow rule added after a broad one is dead code. This looks right in list, but it never fires — rule 1 already claimed the file.
chest> add type=Video && size>2MB -> Videos/HiFi
added type=Video && size>2MB -> Videos/HiFi
chest> preview
bigvideo.mp4
TO: Videos/bigvideo.mp4 <-- still rule 1, the HiFi rule is dead
WHY: type = Video
Fix it by re-ordering: drop the broad rule, then add the narrow one first.
chest> rm 1
removed rule 1 (1 remaining)
chest> add type=Video && size>2MB -> Videos/HiFi
chest> add type=Video -> Videos
chest> list
3 rule(s):
1. type=Image -> Images
2. type=Video && size>2MB -> Videos/HiFi
3. type=Video -> Videos
chest> preview
bigvideo.mp4
TO: Videos/HiFi/bigvideo.mp4
WHY: type = Video, size > 2MB
movie.mp4
TO: Videos/movie.mp4
WHY: type = Video
3. Update one rule in place
edit replaces a rule while keeping its position and priority, so it never disturbs the order around it. Reach for it when you only want to retune one line.
chest> edit 1 type=Image -> Photos/Screenshots
replaced rule 1 type=Image -> Photos/Screenshots
4. Reuse the set
chest> save downloads-v2
saved 2 rule(s) to ~/.chest/templates/downloads-v2.toml
chest> templates
- downloads-v2
chest> load downloads-v2
loaded 2 rule(s) from template 'downloads-v2'
load replaces the whole current rule set, and restores the priority order that was saved.
5. Commit
apply is the only command that moves files. It records history, so the run can be reverted with chest undo; pass --yes to skip the confirmation in scripts.
chest> apply
3 file(s) will be moved.
Continue? [y/N]: y
[DONE] 3 file(s) organized into 3 folder(s).
Rule syntax cheat-sheet
| Field | Aliases | Matches |
|---|---|---|
| type | — | Image, Video, Audio, Document, Archive, Executable, Code, Other. |
| extension | ext |
The filename extension, e.g. extension=mp4 or extension=.mp4. |
| format | — | The content-detected format (magic bytes first), so a renamed file still matches. |
| size | — | A size bound such as size>1GB or size<=100MB. |
| name | filename |
The filename. Supports contains, starts_with, ends_with, glob and =~regex. |
| date | modified, modtime |
A date or range, e.g. date>2025-01 or date=2024. |
| taken_date | takendate |
The embedded media date (EXIF, QuickTime, PDF, ID3…). |
| mime | mimetype |
The detected MIME type. |
Destinations support the placeholders {ext}, {date}, {year}, {month} and {day}, and clauses combine with &&. A bare pattern such as *.mp4 -> Videos is shorthand for matching the filename. The type values are exactly the eight category names; anything else simply never matches.
chest search
Ultra-fast filesystem search using fastwalk for parallel multi-core directory traversal and the fzf algorithm for scoring. Patterns match fuzzily by default (switch to exact substring with -x or regular expressions with -r), and filters can be freely combined: file category, extension, size, modification date, and streaming content grep that reports line numbers and snippets. Results are color-coded by category, or emitted as JSON for scripting.
$ chest search "movie" # Fuzzy filename search (default mode)
$ chest search --type video --size ">1GB" # Combine category + size filters
$ chest search -e pdf,docx -m "<30d" ~/Documents
$ chest search -c "TODO" internal/ # Grep inside files, with line numbers
$ chest search -e go,md -i chest -j # JSON output for scripting
| Flag | Type | Description |
|---|---|---|
| [pattern] [path] | arguments | Filename/path pattern to match, and the root directory to traverse (default .). |
| -t, --type | string | Filter by category: image, video, document, audio, code, archive, etc. Plugin-registered categories are included too. |
| -e, --ext | string | Filter by comma-separated extensions: pdf,docx,xlsx. |
| -s, --size | string | Size condition: >100MB, <10KB, >=1GB, =5MB. |
| -m, --modified | string | Modification time condition: >30d (newer than), <7d (older than), or a calendar date like 2026-01-01. |
| -c, --content | string | Grep inside file contents for matching string; returns line number and snippet. |
| -i, --ignore-case | boolean | Case-insensitive matching. Enabled by default. |
| -r, --regex | boolean | Treat the pattern as a regular expression instead of fuzzy text. |
| -x, --exact | boolean | Exact substring matching instead of fuzzy ranking. |
| -H, --hidden | boolean | Include hidden files and folders in the traversal. |
| -l, --limit | int | Stop after returning n matches. |
| -j, --json | boolean | Output matches in structured JSON format for scripting. |
chest watch
Background daemon monitoring real-time filesystem changes using fsnotify. New files are sorted as they arrive, folder creations and removals are reported instantly, and --initial sorts files that are already present before watching begins. Installed plugin classifiers and their GetCustomRules() apply here exactly as in sort, and each debounce window is recorded as a single history entry (one undo reverts the whole batch).
$ chest watch ~/Downloads --preset media --debounce 500ms
$ chest watch ~/Desktop --initial # Sort existing files first, then watch
| Flag | Type | Description |
|---|---|---|
| -p, --preset | string | Rule bundle: downloads, media, developer, documents, photos. |
| --debounce | duration | Wait interval before sorting a newly created file. Default 500ms. |
| -i, --initial | boolean | Sort files already present in the folder first, then keep watching for new ones. |
| --allow-system | boolean | Bypass system path protection when watching OS-level directories. |
chest schedule
Periodic organizing without a foreground process. Schedules live in ~/.chest/schedules.toml and are registered with the OS scheduler: a managed crontab block on Linux/macOS, a scheduled task on Windows (daily cron expressions; more complex expressions can be printed with --print-cron and wired up manually).
$ chest schedule add --cron "0 2 * * *" --preset downloads ~/Downloads
$ chest schedule list
$ chest schedule run-now s1
$ chest schedule remove s1
| Flag | Type | Description |
|---|---|---|
| --cron | string | 5-field cron expression (minute hour dom month dow), e.g. "0 2 * * *". |
| --preset / --rule | string | What the scheduled run applies; falls back to the target's chest.toml / default preset. |
| --print-cron | boolean | Print the crontab line instead of installing anything (also works for expressions schtasks cannot express). |
chest undo & history
Inspect previous operations or rollback a batch of moves. Every run is recorded honestly: Complete when every file moved, Partial when some failed (the failures are listed in the entry), Failed when nothing moved. Undo validates the entire batch before restoring anything and rolls back cleanly if interrupted.
$ chest history # Display recorded operation history
$ chest undo # Undo the most recent run
$ chest undo 4 # Undo specific transaction by ID
| Flag | Type | Description |
|---|---|---|
| --allow-system | boolean | Bypass system path guard when undoing into protected OS directories. |
chest index & clean
Index file paths and hashes into SQLite for fast duplicate discovery. Indexing is incremental (unchanged files are skipped), runs in parallel across CPU cores, shows a live progress bar, and re-indexing prunes rows for files that were deleted/moved or are now covered by --except. The --except flag is available on index, stats, analyze, and duplicates:
$ chest index ~/Documents --hash # Incremental parallel SHA-256 with progress bar
$ chest index ~/Projects --except node_modules,venv # Skip noisy dirs on index
$ chest clean # Empty cached index (re-index to rebuild)
$ chest clean --all # Reset index database completely
$ chest clean --history # Also wipe undo history (~/.chest/history.json)
stats, duplicates & analyze
Storage intelligence commands. A scoped run always refreshes its folder first; a bare run re-populates an empty cache from the current directory instead of reporting all zeros. --except prunes noisy directories (and drops their previously-indexed rows), and --refresh forces a fresh re-scan with stale-row purge. Largest files are reported with absolute paths. All three commands (and history) export machine-readable output with --json, --csv, or --out <file>:
$ chest stats ~/Projects --except node_modules # Auto-refresh scope + category/size breakdown
$ chest stats --refresh # Force a fresh re-scan + purge of the current dir
$ chest duplicates ~/Downloads # Locate byte-for-byte duplicates
$ chest analyze ~/Documents --refresh # Force fresh audit (zero-byte + stale >180d)
$ chest stats --json --out stats.json # Machine-readable export to file
$ chest duplicates --csv # CSV rows for scripts
chest speedtest
Measures connection speed without an API key by running independent Ookla and Cloudflare tests side by side. The report includes download, upload, ping, and jitter. Select a backend with --ookla or --cloudflare, or use --json for machine-readable output. Use --disk for disk benchmark mode, measuring local storage throughput (sequential read/write MB/s plus random 4K IOPS) with direct, unbuffered I/O so reads bypass the OS page cache.
$ chest speedtest # Run both tests and compare
$ chest speedtest --ookla # Run the accurate Ookla test only
$ chest speedtest --cloudflare # Run the Cloudflare reference test only
$ chest speedtest --json # Emit structured JSON instead of the table
$ chest speedtest --disk # Run disk benchmark mode
$ chest speedtest --disk --disk-size 512 --disk-path D: # Larger test file on drive D:
| Flag | Type | Description |
|---|---|---|
| --ookla | boolean | Run only the Ookla test against the nearest available speedtest.net server. |
| --cloudflare | boolean | Run only the Cloudflare test against speed.cloudflare.com. |
| --json | boolean | Output structured JSON instead of the human-readable comparison table. |
| --disk | boolean | Run disk benchmark mode: sequential read/write MB/s and random 4K read/write IOPS, reported in a -- Disk section (and in the detail field of JSON output). |
| --disk-path | string | Directory in which the temporary benchmark file is created. Defaults to the OS temp directory; pass a folder on another drive to test that drive. |
| --disk-size | int (MiB) | Size of the temporary benchmark file. Defaults to 256 MiB and is capped at 8192 MiB. |
Output & diamond gauge
The default report groups successful measurements by backend. Download and upload rows include a 16-cell gauge: ◆ marks a filled cell and ◇ marks an empty cell. Transfer bars are scaled against the fastest successful download or upload in the comparison, making relative throughput easy to scan; ping rows show latency and jitter without a gauge. Failed measurements are marked FAIL with the error detail, while --json returns the same measurements as structured rows.
Disk benchmark mode reports a -- Disk section listing write/read throughput and 4K read/4K write IOPS. Reads use direct (unbuffered) I/O where the platform supports it so results are not inflated by the OS page cache; on platforms without direct I/O support the mode line says buffered instead. Writes are flushed to the device before timing ends. The disk benchmark uses a single temporary file that is removed when the run completes.
$ chest speedtest
CHEST SPEED TEST
Ookla
download 800.00 Mbps [◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆]
upload 200.00 Mbps [◆◆◆◆◇◇◇◇◇◇◇◇◇◇◇◇]
Cloudflare
download 400.00 Mbps [◆◆◆◆◆◆◆◆◇◇◇◇◇◇◇◇]
upload 100.00 Mbps [◆◆◇◇◇◇◇◇◇◇◇◇◇◇◇◇]
chest battery
Reports battery charge, power state, and drain rate. Aliased as power and batt. With no flags it takes a single reading; --record and --daemon build the history database that the period reports (--day, --week, --month, --year) read back. Readers are per-platform, and every report names the source it read from.
Several flags only mean something alongside another one — --apps apportions activity over a window, --top sizes the --apps list, --interval sets a sampling rate, and --db names the history database. Given on their own they say which mode they need rather than printing a report identical to a run without them.
$ chest battery # Single reading
$ chest battery --low 20 --fail-under 10 # Mark 20% or below as LOW, exit 3 at 10%
$ chest battery --seconds 30m --interval 5s # Measure a drain rate over 30 minutes
$ chest battery --record --daemon # Record history continuously until Ctrl+C
$ chest battery --week --apps --top 8 # Period report, with per-app estimates
$ chest battery --realtime # Redraw the reading in place until Ctrl+C
| Flag | Type | Description |
|---|---|---|
| --record | boolean | Append each reading to the history database. Works on its own: battery --record records the single reading it takes. |
| --seconds <duration> | duration | Measure over this long instead of taking a single reading. |
| --interval <duration> | duration | Sampling interval while measuring. Default 2s. Needs a mode that measures: --seconds, --realtime or --daemon. The sampler floors it at 5s and reports the interval actually in force. |
| --json | boolean | Output the reading as machine-readable JSON instead of the table. |
| --low <pct> | int | Report charge at or below this percentage as low. 0 disables the warning. Must be 0–100. |
| --fail-under <pct> | int | Exit with code 3 when charge is at or below this percentage, for scripted checks. 0 disables it. Must be 0–100. |
| --db <path> | string | History database path. Default ~/.chest/battery.db. Names where history is read or written, so it needs --record or a period flag. |
| --apps | boolean | Report per-application activity apportioned from measured CPU and I/O. An estimate, see below. Needs a window to apportion over: a period flag or --seconds. |
| --top <n> | int | How many applications to list with --apps. Default 5, minimum 1. Only meaningful alongside --apps. |
| --daemon | boolean | Keep recording in the foreground until Ctrl+C. |
| --realtime | boolean | Redraw the reading in place until Ctrl+C. |
| --day / --week / --month / --year | boolean | Report that period from recorded history. Mutually exclusive. |
Reading output
The report lists charge, power state, and drain rate, then the fields the platform supplies: power draw, battery health, charge cycles, and time remaining. Anything the platform does not expose is listed under Not reported with the reason — including time remaining, which exists only while discharging — rather than printed as a zero. --json returns the same reading as structured rows, with a *_known flag beside every measurement and a rate_provenance field, so a consumer cannot read an estimate as a reading.
$ chest battery --json
{
"source": "GetSystemPowerStatus",
"full_capacity_wh": 35.74,
"design_capacity_wh": 45,
"health_pct": 79,
"charge_cycles": 1541
}
Excerpt from a real Windows laptop: 45 Wh design capacity, 35.74 Wh full capacity, 79% health, 1541 charge cycles, and time remaining while discharging. The full JSON also carries charge, power state, wattage, and rate fields.
Drain rate is an estimate
n/a with the reason instead of a fabricated number; a computed rate is prefixed with ~ and its detail line names the window and the sample count.
History and period reports
--record appends each reading; --daemon keeps recording in the foreground until Ctrl+C. Readings go to their own SQLite database at ~/.chest/battery.db, separate from the file index at ~/.chest/index.db, so chest clean --all cannot destroy it. chest clean --battery clears it and is opt-in only.
Periods are cut in UTC, and --week is the ISO week, starting Monday. A period report prints its coverage first, then the charge change across the period, the drain rate, the charging and discharging sample counts, the top applications, and a vertical bar chart of their share underneath the table.
Per-application figures are estimates
--apps apportions the measured CPU and I/O into per-application numbers, and the output states this limitation itself. A lit display, a wireless radio, and thermal management draw real power at near-zero CPU and are invisible to the method.
Platform sources
Windows, Linux, and macOS are supported, each with its own reader. Windows takes charge, mains state, and time remaining from GetSystemPowerStatus, then reads cycle count, full and design capacity, health, wattage, and voltage from the battery class driver unprivileged. macOS reads ioreg AppleSmartBattery; Linux reads /sys/class/power_supply.
man, preset & version
$ chest man sort # View built-in Unix man page for command
$ chest preset # List all built-in rulesets
$ chest version # Print binary version, commit, and platform
chest completion
Installs shell tab-completion so your shell autocompletes chest subcommands and flags — type chest s and press Tab to complete to sort. The recommended setup is a single command; it detects your shell from $SHELL, writes the script to the standard location for that shell, and wires it into your shell's startup file so completion loads in every new terminal.
$ chest completion --install # Detect your shell, install, and wire it up (recommended)
$ chest completion zsh --install # Install zsh completion permanently
$ chest completion fish --install # Install fish completion permanently
$ chest completion bash > _chest # Print the raw script to stdout instead
| Flag | Type | Description |
|---|---|---|
| [shell] | argument | Target shell: bash, zsh, or fish. Optional with --install (auto-detected from $SHELL). |
| -i, --install | boolean | Detect your shell (or use the shell argument) and install completion permanently, wiring it into your shell profile. |
| -o, --output <file> | string | Write the completion script to this file instead of stdout. |
| -s, --shell <name> | string | Shell to generate for: bash, zsh, fish. |
Plan, Safety & Integrity
Every command in this section is safe by default. plan, manifest, verify, doctor, tree and rule eval never move, rename or delete a file. The only ones that mutate are rename and plan apply — and both record history, so chest undo reverts them.
chest plan
Turns a sorting plan into a reviewable, replayable JSON artifact. chest plan --out runs the same scan/classify/plan pipeline as sort but moves nothing.
| Flag | Type | Description |
|---|---|---|
| --out <file> | string | Write the plan JSON to FILE. Strictly read-only: no folder is created and no file moves. |
| -y, --yes | boolean | Skip confirmation on apply. |
| -r, --recursive | boolean | Include subdirectories (same meaning as chest sort -r). |
| -p, --preset | string | Built-in preset. Rules resolve with identical precedence to sort. |
chest plan apply is all-or-nothing: it re-validates every source first and refuses the entire batch if any file has moved, rather than half-applying. It records history, so chest undo reverts it.
chest checkpoint & undo --to
Mark a named point in history, then revert everything after it as one atomic batch.
| Flag | Type | Description |
|---|---|---|
| -n, --name <label> | string | Label for the checkpoint (may also be given positionally). Moves no files itself. |
| --to <label> | string | On undo: revert every operation recorded after that checkpoint, as one atomic batch. |
| --file <name> | string | On undo: revert only the operations of that entry matching this basename (repeatable). |
| --prune --keep <n> | integer | On history: drop the oldest entries, keeping the newest n (default 100), so the log cannot grow unbounded. |
chest manifest & chest verify
Hash a folder into a reproducible SHA-256 inventory, then prove it has not drifted.
| Flag | Type | Description |
|---|---|---|
| -o, --out <file> | string | Write the manifest to FILE. Byte-reproducible: no run timestamp, so an unchanged tree regenerates an identical file. |
| --manifest <file> | string | On verify: the manifest to verify against (required). |
| --max-size <size> | string | Skip hashing files larger than this (default 500MB). Skipped files are reported, never silently dropped. |
| --json / --csv | boolean | Machine-readable output for scripting. |
chest verify exits 0 when everything matches and 4 when any file differs, so a script can distinguish "the backup drifted" from "verify could not run". (CHEST reserves 1 for a genuine error and 3 for a battery threshold breach.)
chest rename
Batch-rename files in place from a template. It never moves a file between directories.
| Flag | Type | Description |
|---|---|---|
| --to <template> | string | Rename template (required). Placeholders: {ext} (lowercase extension without its dot — write photo.{ext}), {date}, {year}, {month}, {day}. Must not contain path separators. |
| --date-source | string | Which date the placeholders use: modified (default), auto, taken, filename. |
| -n, --dry-run | boolean | Preview every rename without touching disk. |
| --collision | string | skip, rename (default), replace, abort. The default walks the photo (1).jpg chain so nothing is ever overwritten silently. |
chest doctor & chest tree
Read-only inspection of CHEST's own state, and of a folder's shape.
| Flag | Type | Description |
|---|---|---|
| --strict | boolean | On doctor: exit non-zero when any check fails. Warnings alone exit 0. |
| --depth <n> | integer | On tree: how many levels to descend (default 3). When truncated, the output states what was left unread. |
| --sort <field> | string | On tree: size (default) or name. |
chest rule eval
Explain which rule matches a given file, why, and where it would go — read-only. rule check validates syntax; rule eval validates behaviour.
Content-aware collisions
chest sort --hash-collisions resolves same-destination conflicts by content instead of guessing. CHEST hashes only the files that actually collide: distinct content is preserved through the normal photo (1).jpg rename chain, while an identical SHA-256 match is skipped by default. Use --keep-identical when the second path must be retained too.
| Flag | Type | Description |
|---|---|---|
| --hash-collisions | boolean | Hash only the files involved in a collision — never the whole tree. Same SHA-256 means a genuine duplicate, so the second copy is skipped; different content means both are distinct, so the second is auto-renamed rather than clobbering the first. |
| --keep-identical | boolean | Use with --hash-collisions to retain a byte-identical duplicate under the next safe name, such as photo (1).jpg. This makes the keep decision explicit and visible instead of silently dropping the second planned move. |
| --similar | boolean | On duplicates: group near-duplicate images by perceptual hash (DCT-based, stdlib only), reported separately from exact SHA-256 matches. |
| --threshold <0-64> | integer | Hamming distance tolerance for --similar (default 10). |
| --quarantine <dir> | string | On duplicates: opt-in, moves duplicates into a staging folder after confirmation. Never deletes, and is recorded in history so undo restores them. |
--keep-identical when the duplicate's path still matters. The second file is assigned the next collision-safe name — photo (1).jpg here — and the plan reports it as retained rather than skipped.$ chest sort --format --hash-collisions --keep-identical
CHEST PLAN # hash only the conflicting files
photo.jpg → JPG/photo.jpg
photo-copy.jpg → JPG/photo (1).jpg
CONFLICTS
identical content (sha256 match) retained as JPG/photo (1).jpg
✔ 2 files prepared · 1 identical collision retained · 0 files skipped
Continuous indexing & trends
| Flag | Type | Description |
|---|---|---|
| --watch | boolean | On index: keep the index warm as files change, until Ctrl+C. Reuses the same incremental size + mtime skip. |
| --debounce <dur> | duration | Settle time before re-indexing a burst of changes (default 500ms). |
| --trend | boolean | On stats: report growth against a recorded snapshot. |
| --day / --week / --month | boolean | Trend window (default --month). |
Plugin Architecture & Guide
go-plugin over RPC. Plugins execute in independent operating system processes. A bug or panic in a plugin will not affect CHEST core.
Plugin Structure
A plugin consists of an executable binary and a manifest file (manifest.json or manifest.toml) placed in ~/.chest/plugins/<name>/.
sort, watch, undo) targeting system directories like C:\Windows, /usr, /etc, or drive roots. Use --allow-system to override with an interactive confirmation.
Building a Sample Plugin
1. Initialize project
$ mkdir chest-custom-plugin
$ cd chest-custom-plugin
$ go mod init chest-custom-plugin
2. Implement plugin logic (main.go)
package main
import (
"strings"
hplugin "github.com/hashicorp/go-plugin"
"github.com/Aswanidev-vs/chest/internal/plugin"
)
type CustomClassifier struct{}
func (c *CustomClassifier) GetInfo() (plugin.Manifest, error) {
return plugin.Manifest{
Name: "custom-filter",
Version: "1.0.0",
Description: "Classifies custom test files",
Capabilities: []string{"classifier"},
Binary: "custom-filter",
}, nil
}
func (c *CustomClassifier) Classify(name string, ext string) (string, error) {
if strings.HasSuffix(name, ".spec.ts") {
return "UnitTests", nil
}
return "Other", nil
}
func main() {
hplugin.Serve(&hplugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig,
Plugins: map[string]hplugin.Plugin{
"classifier": &plugin.ClassifierPluginRPC{Impl: &CustomClassifier{}},
},
})
}
3. Define manifest (JSON or TOML)
CHEST discovers either manifest.json or manifest.toml. Use whichever you prefer:
manifest.json
{
"name": "custom-filter",
"version": "1.0.0",
"description": "Classifies custom test files",
"author": "Developer",
"capabilities": ["classifier"],
"binary": "custom-filter"
}
manifest.toml
name = "custom-filter"
version = "1.0.0"
description = "Classifies custom test files"
author = "Developer"
capabilities = ["classifier"]
binary = "custom-filter"
4. Build and install
$ go build -o custom-filter .
$ chest plugin install .
Plugin CLI Management
$ chest plugin install . # Install plugin from local folder
$ chest plugin list # List installed plugins
$ chest plugin info <name> # View plugin metadata
$ chest plugin remove <name> # Uninstall a plugin
Metadata Capability (protocol v2)
Plugins declaring the metadata capability implement
plugin.MetadataService and enrich every scanned file with
sample-based intelligence during chest sort and chest watch.
The host sends a bounded models.FileSample (path, name, extension,
size, plus the first 4 KiB and last 1 KiB of content) and merges the
returned models.FileMetadata — format, MIME type, category,
embedded capture date (TakenDate/DateSource) and
arbitrary Fields — into the file record, where rule conditions
and the index can use them. Enrichment is best-effort: broken plugins are
skipped, empty results change nothing, and the built-in signature extractor
remains the fallback.
func (p *RichMetaPlugin) Inspect(sample models.FileSample) (models.FileMetadata, error) {
// e.g. sniff camera RAW containers or parse XMP capture dates
return models.FileMetadata{
Format: "CR3",
MIMEType: "image/x-cr3",
Category: "Image",
TakenDate: &ts,
DateSource: "xmp:photoshop:DateCreated",
Fields: map[string]string{"raw_container": "ISO-BMFF/CRX"},
}, nil
}
A complete working example — camera RAW sniffing plus XMP sidecar date
extraction — ships in examples/plugins/rich-meta. Protocol
version 2 is enforced by the handshake, so plugins built against v1 must
be recompiled.