Skip to main content

CLI tools for cleaning artifacts and builds

Project description

Artifact-Scythe

Reclaim disk space by harvesting the build artifacts you forgot about.

CI PyPI version Python versions License: MIT

Demo

MP4 version

scythe is a Python CLI that walks your projects directory, identifies each project's ecosystem (Node, Python, Rust, Java, Go, Ruby, .NET) from its marker files, and locates the bulky build artifacts that ecosystem leaves behind — node_modules, .venv, __pycache__, target/, build/, and the rest. It reports how much space each one is wasting and, when you tell it to, deletes them.

It is safe by default: every clean previews what is about to happen and asks for confirmation, and a --dry-run mode lets you check the plan before running for real.

Why

A laptop used daily for a year typically holds 30–80 GB of stale node_modules, abandoned virtualenvs, cached compiler output, and CI scratch directories. None of it is load-bearing — but finding and removing it by hand across dozens of project folders is tedious and error-prone. scythe does it in two commands.

How does this compare to npkill / kondo / cleanpy?

Existing tools cover one ecosystem each, well. scythe covers a mixed-stack ~/projects folder in one workflow.

Tool Ecosystems covered UX Filters Reports Distribution
npkill Node only (node_modules) Interactive TUI Sort by size / age None npm
kondo Rust + a handful (Node, JS, Java, Haskell, …) Interactive prompt Older-than None Cargo, Homebrew
cleanpy Python only CLI Cache types None pip
scythe Node, Python, Rust, Java (Maven + Gradle), Go, Ruby, .NET (Swift planned) CLI + interactive TUI (scythe ui) --only, --older-than, --min-size (--ignore planned) JSON / CSV export, dry-run report, recoverable trash + scythe restore pipx, pip, Docker (multi-arch), standalones planned

When to reach for which:

  • Only have a node_modules problem? npkill is purpose-built.
  • Mostly Rust? kondo is great.
  • Mixed-stack folder, want filters / reports / scripting / CI integration, or you also need to clean Python virtualenvs and Java build dirs in the same pass? That's where scythe is meant to live.

Quick start

pipx install artifact-scythe          # one-line global install

scythe scan ~/projects                # see what's eating your disk
scythe clean ~/projects --dry-run     # preview the deletions
scythe clean ~/projects               # do it (with confirmation)

Install

With pipx (recommended)

pipx installs scythe into its own isolated virtual environment and exposes the scythe command globally on your PATH. It's the right tool for Python CLIs: no clash with your project deps, no sudo, one command to upgrade.

pipx install artifact-scythe          # install
pipx upgrade artifact-scythe          # update later
pipx uninstall artifact-scythe        # remove cleanly

Don't have pipx yet? python -m pip install --user pipx && python -m pipx ensurepath (restart your shell once).

With pip

If you don't want the isolated install, plain pip works too:

pip install --user artifact-scythe    # user-level install

Naming note

The PyPI distribution is artifact-scythe (the scythe slot on PyPI was taken), but the installed command and the Python module are both scythe. Scripts written against scythe ... keep working unchanged.

With Docker

If you'd rather not install anything locally (CI agents, throwaway VMs, or just trying it out), the official image is published on GHCR for both linux/amd64 and linux/arm64:

# scan the current directory
docker run --rm -v "$PWD":/work ghcr.io/elielmengue/scythe:latest scan /work

# clean with a dry-run
docker run --rm -v "$PWD":/work ghcr.io/elielmengue/scythe:latest \
    clean /work --dry-run

Tags follow the PyPI release: :latest, :0.7.0, :0.7, :0. The rolling :edge tag tracks main.

From source

git clone https://github.com/elielMengue/scythe.git
cd scythe
pip install -e ".[dev]"

Usage

scythe is a Click group with five subcommands — scan, clean, restore, ui, info — plus a small set of options that apply to every invocation.

Global options

These work with any subcommand and must be passed before the subcommand name (e.g. scythe --verbose scan .).

Option Description
--verbose, -v Enable DEBUG-level logging (per-directory walks, filter decisions, internal state).
--no-log-file Skip writing the timestamped log file under ./logs/. Console output is unaffected.
--version Print the installed version and exit.
--help Show the top-level help and exit. Available on every subcommand too (scythe scan --help).

By default each run drops a logs/scythe_YYYYMMDD_HHMMSS.log file in the current working directory. Use --no-log-file for one-off runs in sandboxes or CI where you don't want the side effect.

scythe scan — discover projects and measure artifacts

Walks a directory tree, identifies project roots, and measures the artifacts each one is sitting on. Read-only: nothing is ever deleted.

scythe scan .                                  # current directory
scythe scan ~/dev --depth 2                    # bound recursion depth
scythe scan ~/dev --only node,python           # filter by ecosystem
scythe scan ~/dev --older-than 30              # only artifacts older than 30 days
scythe scan ~/dev --min-size 500MB             # only artifacts at or above 500 MB
scythe scan ~/dev --format tree                # table | tree | compact | json
scythe scan ~/dev --format json -o report.json # also csv via .csv suffix
scythe scan /opt --follow-symlinks             # traverse symlinked dirs
scythe scan ~/dev --no-artifacts               # hide the artifact detail rows
Flag Type / default Description
PATH (positional) path, default . Directory to scan. Must exist.
--depth, -d int, default -1 (unbounded) Maximum recursion depth. 0 = scan only PATH, 1 = PATH and its immediate children, etc.
--follow-symlinks flag, default off Follow symbolic links during traversal. Off by default to avoid loops on system trees.
--format table | tree | compact | json (default table) How the result is rendered to stdout.
--output, -o path Also save the report to a file. .json → JSON; any other suffix → CSV.
--no-artifacts flag, default off Suppress the per-artifact detail rows in table/tree output (project totals only).
--only TYPES comma-separated list Restrict to the named ecosystems. Canonical names (node, python, rust, java_maven, java_gradle, go, ruby, dotnet) and aliases (py, js, rs, golang, .net, cs) both accepted.
--older-than DAYS int, default 0 (off) Keep only artifacts whose last_modified is older than DAYS days.
--min-size SIZE size string Keep only artifacts at or above SIZE. Accepts raw bytes or human units: 512KB, 100MB, 1GB.

scythe clean — delete detected artifacts

Runs the same scan first, prints a summary, then either prompts before deleting or executes immediately depending on flags. By default deletion is permanent — files are unlinked, not moved to the OS bin. Pass --trash to route them through scythe's recoverable trash instead, then use scythe restore to bring them back.

scythe clean ~/dev --dry-run                   # simulate (always do this first)
scythe clean ~/dev --trash                     # recoverable cleanup (undo with `scythe restore`)
scythe clean ~/dev --interactive               # pick projects manually
scythe clean ~/dev --only rust                 # only Rust target/ directories
scythe clean ~/dev --older-than 30 --dry-run   # only target stale artifacts
scythe clean ~/dev --min-size 1GB --dry-run    # only large artifacts worth deleting
scythe clean ~/dev --force                     # skip the confirmation prompt
scythe clean ~/dev -o run-report.json          # export a JSON report
scythe clean ~/dev --depth 2 --follow-symlinks # bound the pre-clean scan
Flag Type / default Description
PATH (positional) path, default . Directory to clean. Must exist.
--dry-run flag, default off Simulate the deletion. The summary is rendered, sizes are reported, but nothing is touched. Recommended first.
--interactive, -i flag, default off After the scan, open a manual selector and let you pick which projects to clean.
--force, -f flag, default off Skip the "are you sure?" prompt. Useful for scripts and CI.
--trash flag, default off Move artifacts into scythe's recoverable trash instead of unlinking. Pair with scythe restore to undo.
--depth, -d int, default -1 (unbounded) Bound the preliminary scan's recursion depth.
--follow-symlinks flag, default off Follow symlinks during the preliminary scan.
--only TYPES comma-separated list Restrict to the named ecosystems. Same vocabulary as scan.
--older-than DAYS int, default 0 (off) Keep only artifacts older than DAYS days.
--min-size SIZE size string Keep only artifacts at or above SIZE (100MB, 1GB, …).
--output, -o path Save a JSON report of the clean run (artifacts deleted, size freed, errors, skipped).

scythe restore — undo a clean --trash run

scythe restore --list                  # show recoverable runs (id, date, items, size)
scythe restore                         # undo the most recent --trash run
scythe restore 20260502-153000-123456  # undo a specific run by id
Flag / argument Description
RUN_ID (positional) Optional. Restore this specific run. Omit to target the most recent recoverable run.
--list List recoverable runs (id, date, item count, total size, restored?) and exit.

Trashed runs live under the per-user data dir (%LOCALAPPDATA%\scythe on Windows, ~/Library/Application Support/scythe on macOS, $XDG_DATA_HOME/scythe or ~/.local/share/scythe on Linux). A run that's already been restored, has a missing trash payload, or whose destination has been re-created since the clean, is reported as skipped rather than failing. OS-level failures during a restore are errors and exit non-zero.

scythe ui — interactive TUI

Full-screen alternative to scan/clean for exploration. Built on Textual, scythe ui shows two panes side-by-side: a sortable, filterable list of cleanable projects on the left, and the artifact list of the currently focused project on the right. The status header surfaces the running totals (N projects · M/K artifacts · X.YZ GB to free) so you always know what a clean would reclaim.

scythe ui                                       # current directory
scythe ui ~/projects                            # specific path
scythe ui ~/projects --min-size 100MB           # focus on the heavy hitters
scythe ui . --only node,python --depth 3       # narrow the scan
scythe ui ~/dev --older-than 60                 # stale stuff only
scythe ui . --no-trash                          # delete directly instead of trashing

The scan runs inside the TUI itself. The Textual app opens immediately with a Scanning… chip in the status bar and updates a "N dirs · …/path/tail" progress line as directories are walked, on a worker thread; the project list populates as soon as the scan finishes. There's no Rich progress bar in front of the app anymore — on big trees this removes the apparent startup latency.

The same scan filters as scan/clean are accepted and applied before the project list is rendered.

Flag Type / default Description
PATH (positional) path, default . Directory to scan.
--depth, -d int, default -1 (unbounded) Bound the recursion depth.
--follow-symlinks flag, default off Follow symbolic links during the scan.
--only TYPES comma-separated list Restrict to the named ecosystems (same vocabulary as scan).
--older-than DAYS int, default 0 (off) Keep only artifacts older than DAYS days.
--min-size SIZE size string Keep only artifacts at or above SIZE (100MB, 1GB, …).
--no-trash flag, default off Delete directly when cleaning from the TUI. By default cleans go through the recoverable trash.

Keybindings

Key Action
space Toggle the row under the cursor (project or single artifact)
a Toggle every artifact across every project
Tab Switch focus between the two panes
s Cycle sort: size · newest · type · path
/ Filter the project list by path or type substring
Esc Close/clear the filter
c Clean the selected artifacts (asks for confirmation)
u Undo the most recent clean run
q Quit

Trash vs. direct delete

By default, cleans triggered from the TUI use trash mode — artifacts are moved into scythe's recoverable trash dir rather than permanently unlinked, and a per-run manifest is written. u (or scythe restore from a regular shell) brings them back. Pass --no-trash when launching the TUI to delete directly instead; the confirmation dialog and the post-clean notification both reflect the active mode so you can't mistake one for the other.

The CLI commands stay unchanged for scripts and CI; the TUI is for interactive exploration.

scythe info

Prints the installed version, a short feature summary, and the list of supported ecosystems and artifact patterns. Takes no arguments.

scythe info

Output

By default, scythe uses Rich ANSI-colored output for better readability.

Disable colored output using either the standard NO_COLOR environment variable:

NO_COLOR=1 scythe scan ~/projects

or the top-level --no-color flag:

scythe --no-color scan ~/projects

This is useful for:

  • screen readers
  • CI logs
  • terminals without ANSI support
  • piping output to files or other tools

Supported ecosystems

Ecosystem Marker files Artifact patterns
Node.js package.json, yarn.lock, pnpm-lock.yaml node_modules, dist, build, .next, .nuxt, out, .cache, .parcel-cache, .turbo, coverage
Python requirements.txt, setup.py, pyproject.toml, Pipfile, poetry.lock .venv, venv, env, __pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .tox, *.egg-info, dist, build, .eggs, htmlcov
Rust Cargo.toml, Cargo.lock target
Java (Maven) pom.xml target, .m2/repository
Java (Gradle) build.gradle, build.gradle.kts, settings.gradle build, .gradle, out
Go go.mod, go.sum bin, pkg, vendor
Ruby Gemfile, Gemfile.lock, .ruby-version vendor/bundle, .bundle, tmp
.NET *.csproj, *.fsproj, *.vbproj, *.sln bin, obj, packages, .vs

The --only flag accepts both canonical names (node, python, java_maven, java_gradle, dotnet, ...) and short aliases (py, js, rs, golang, .net, cs).

Safety

  • clean deletes via shutil.rmtree / Path.unlink — files are gone, not in the trash bin. Run --dry-run first when in doubt.
  • Source-control dirs (.git, .svn, .hg, .bzr), editor metadata (.idea, .vscode), and OS metadata (.DS_Store, Thumbs.db) are skipped during traversal.
  • Symlinks are not followed unless --follow-symlinks is passed.
  • Use --depth N on very large filesystems to avoid runaway scans.

Development

Python 3.10+ is required.

git clone https://github.com/elielMengue/scythe.git
cd scythe
pip install -e ".[dev]"

pytest -v                       # full test suite
pytest --cov=scythe             # with coverage
pytest tests/test_scanner.py    # a single file

CI runs the test suite on Linux, macOS, and Windows for Python 3.10, 3.11, and 3.12. See .github/workflows/ci.yml.

Contributing

Issues and pull requests are welcome.

To add support for a new ecosystem, extend two places:

Then add a fixture and an assertion in tests/test_detector.py that exercises the new pattern. Keeping these two maps in sync is the core invariant of the codebase.

Roadmap

Shipped

  • Configuration & foundations
  • Directory scanner
  • Artifact detection — 8 ecosystems
  • Rich-based output (table / tree / compact / JSON)
  • Cleaning engine (--dry-run, --interactive, --force, JSON report)
  • Filters: --only, --older-than, --min-size
  • Recoverable trash + scythe restorescythe clean --trash moves artifacts under a per-user data dir and writes a per-run manifest; scythe restore undoes the most recent run (or a specific one by id). (v0.6.0)
  • scythe ui — interactive TUI mode (Textual) — full-screen browse-and-clean experience: project list with sort and filter, per-project artifact pane, item-level toggles, live total-size readout, and a trash-mode clean with in-app undo. (v0.7.0)
  • Distribution: PyPI (pipx), Docker (multi-arch GHCR)

Safety & UX

Filters & customization

  • --ignore PATTERN — extra ignore patterns on top of the built-in defaults (e.g. --ignore "*.archive,~/projects/keepme").
  • Config filepyproject.toml [tool.scythe] and/or ~/.scytherc to persist --only / --ignore / depth defaults per machine.

Distribution & polish

  • Standalone binaries on GitHub Releases for users without Python:
    • macOS — Apple Silicon (arm64) and Intel (x86_64)
    • Linux — x86_64 and arm64 (glibc); musl/static build for Alpine
    • Windows — x86_64 (.exe)
    • Optional: FreeBSD x86_64
  • Package-manager distribution — Homebrew tap, Scoop bucket, winget manifest, and an AUR package.
  • Shell completionsscythe completion {bash,zsh,fish,powershell}.

Telemetry & quality

  • Lifetime stats — track total space reclaimed across runs and surface it in scythe info.
  • Comprehensive integration tests — broader scanner/cleaner coverage (large directory trees, permission edge cases, symlink loops).

See CHANGELOG.md for the release history.

License

MIT © Eliel MENGUE

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

artifact_scythe-0.7.1.tar.gz (49.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

artifact_scythe-0.7.1-py3-none-any.whl (43.6 kB view details)

Uploaded Python 3

File details

Details for the file artifact_scythe-0.7.1.tar.gz.

File metadata

  • Download URL: artifact_scythe-0.7.1.tar.gz
  • Upload date:
  • Size: 49.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for artifact_scythe-0.7.1.tar.gz
Algorithm Hash digest
SHA256 e630b62086424a7549b1da8b2b1c1b9ea7204edfecbc972327e7b506de72ad99
MD5 8bf87df26f9971472cce1b488a15ebc1
BLAKE2b-256 2d4f9a7b437e0ef67019aa9ac74186c4e0eb3c56de006ccd414ce708ea8e3db5

See more details on using hashes here.

Provenance

The following attestation bundles were made for artifact_scythe-0.7.1.tar.gz:

Publisher: release.yml on elielMengue/scythe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file artifact_scythe-0.7.1-py3-none-any.whl.

File metadata

File hashes

Hashes for artifact_scythe-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0f9b035921503d9db346995422dd30020f85b991cb5cf8aa838722f17391c6dc
MD5 a4b63dbeb6a106845a6b10fd15fce8d4
BLAKE2b-256 43ed9aff2bef0cf5873ff5447879424138f07c7ff37da6062060d69350f1654f

See more details on using hashes here.

Provenance

The following attestation bundles were made for artifact_scythe-0.7.1-py3-none-any.whl:

Publisher: release.yml on elielMengue/scythe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page