
Disk Cleaner
- 679 installs
- 34 repo stars
- Updated April 2, 2026
- gccszs/disk-cleaner
disk-cleaner is a cross-platform Python disk-management skill that helps developers reclaim space by scanning and removing build artifacts across projects and AI coding environments.
About
disk-cleaner is a self-contained cross-platform disk space toolkit for developers running heavy local builds and AI coding environments. It requires Python 3.7+, needs no pip install, and includes an intelligent bootstrap that auto-detects skill location and imports bundled modules. Scanning supports a quick 1-second sample mode plus progressive scanning for large disks, with cross-platform Unicode and emoji-safe encoding. A bundled `check_skill.py` diagnostic helps verify the skill loads correctly from global, project, or user-level locations. Developers reach for disk-cleaner when node_modules, compiler caches, or IDE artifacts consume disk and slow agent workflows. The skill focuses on reclamation and optimization commands rather than application feature work.
- Progressive scanning with quick 1-second sample mode plus full-disk analysis
- Intelligent bootstrap that auto-detects skill location across 20+ possible paths
- Cross-platform encoding with strict ASCII-safe output using [OK], [X], [!] markers
- 3-5x faster performance via os.scandir(), concurrent scanning and intelligent sampling
- Diagnostic tools including check_skill.py for instant verification
Disk Cleaner by the numbers
- 679 all-time installs (skills.sh)
- Ranked #223 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gccszs/disk-cleaner --skill disk-cleanerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 679 |
|---|---|
| repo stars | ★ 34 |
| Last updated | April 2, 2026 |
| Repository | gccszs/disk-cleaner ↗ |
How do you reclaim disk space from build caches?
Reclaim disk space, remove build artifacts, and keep AI coding environments running efficiently across any project.
Who is it for?
Developers on macOS, Linux, or Windows with bloated `node_modules`, compiler caches, or multi-IDE AI workspaces.
Skip if: Teams needing centralized cloud storage governance or production server capacity planning instead of local workstation cleanup.
When should I use this skill?
Disk space is low, builds are slow from full disks, or AI IDE caches need safe cross-platform cleanup.
What you get
Freed disk space, removed build artifacts, and a verified skill environment via `check_skill.py`.
- Reclaimed disk space report
- Removed artifact targets list
By the numbers
- Requires Python 3.7+
- Quick sample scan completes in about 1 second
- Includes bundled `check_skill.py` diagnostic script
Files
Disk Cleaner Skill - Complete Feature Guide for All Agents
🚨 IMPORTANT: Emoji Usage Policy
For Script Output (Cross-Platform Safety)
- NEVER use emoji in script output - All scripts use ASCII-safe characters
- Scripts use:
[OK],[X],[!],[*],[i],[DIR],[FILE],[PKG],[x] - This ensures compatibility with Windows GBK and other non-UTF-8 consoles
- DO NOT modify script output to add emoji - This will break cross-platform compatibility
For Agent-to-Human Communication (Recommended)
- SHOULD use emoji when reporting results to humans - Makes output more readable
- Use these emoji in your final reports to users:
✅(Success) - Completed operations, successful scans❌(Error) - Failed operations, critical errors⚠️(Warning) - Warnings, issues that need attention🔍(Scanning) - Search/scan operations📊(Statistics) - Analysis results, statistics📁(Directory) - Directory-related information📦(Package) - Package/module information🎉(Success) - Celebratory messages for successful completion🚨(Critical) - Critical warnings💡(Tip) - Suggestions, recommendations📋(List) - Checklists, summaries🔧(Tool) - Tools, utilities🌐(Global) - Cross-platform, universal features🛡️(Safety) - Safety-related information⚡(Performance) - Performance improvements📝(Document) - Documentation🎓(Learning) - Educational content📞(Support) - Help, support information📈(Growth) - Improvements, gains🎯(Target) - Goals, objectives🚀(Launch) - Quick start, fast operations🛠️(Tools) - Tool-related features
Example:
Script output (ASCII): [OK] Scan completed: 50,000 files in 30 seconds
Your report to human: ✅ Scan completed successfully! Found 50,000 files in 30 seconds---
📦 COMPLETE FEATURE LIST (READ THIS FIRST!)
🚨 CRITICAL: Progressive Scanning (MANDATORY for Large Disks)
- ✅ Quick Sample Mode: 1-second estimation of disk size and scan time
- ✅ Progressive Scan Mode: Get partial results in 30 seconds for large disks
- ✅ Smart Time Limits: Prevent users from waiting hours
- ✅ Real-time Feedback: Progress updates every 2 seconds
- ✅ Interruptible: Ctrl+C to get partial results
🔧 Intelligent Bootstrap (Auto-Detection & Import)
- ✅ Auto Location Detection: Searches 20+ common skill package locations
- ✅ Environment Variable Support: DISK_CLEANER_SKILL_PATH override
- ✅ Auto Module Import: Automatically imports diskcleaner modules
- ✅ Fallback Mechanisms: Works even if some features unavailable
- ✅ Cross-Platform Python Detection: Tries both 'python' and 'python3'
🌐 Universal Compatibility
- ✅ All AI IDEs: Cursor, Windsurf, Continue, Aider, Claude Code, etc.
- ✅ All Platforms: Windows, macOS, Linux (including Windows GBK console)
- ✅ All Installation Levels: Global, project, user level
- ✅ No Configuration Needed: Works out of the box
- ✅ Cross-Platform Encoding: All scripts use ASCII-safe output (v2.0+)
🛡️ Safety & Reliability
- ✅ Diagnostic Tool: check_skill.py - verifies all functionality
- ✅ Safe Encoding: All scripts use ASCII characters (no emoji in output)
- ✅ Safe Defaults: --dry-run for cleaning, smart limits for scanning
- ✅ Protected Paths: Never deletes system directories or executables
- ✅ Error Handling: Graceful degradation on errors
- ✅ GBK/UTF-8 Compatible: Works on Windows GBK, UTF-8, and all other encodings
⚡ Performance Optimizations (ALL INCLUDED)
- ✅ QuickProfiler: Fast sampling to estimate scan characteristics
- ✅ ConcurrentScanner: Multi-threaded I/O for 3-5x speedup
- ✅ os.scandir() Optimization: 3-5x faster than Path.glob
- ✅ IncrementalCache: Cache scan results for faster repeat scans
- ✅ Memory Monitoring: Auto-adapts based to available memory
- ✅ Early Stopping: Configurable file/time limits
- ✅ Enhanced Cache Detection: 26+ cache paths (was 8) - 225% improvement
🆕 v2.2+ New Features
- ✅ Duplicate File Detection: Find and remove duplicate files with adaptive strategies
- ✅ Growth Trend Analysis: Track disk usage over time, predict when disk will be full
- ✅ Interactive Wizard: Step-by-step guided cleanup with safety confirmations
- ✅ File Organization: Intelligent file archival and organization (4 strategies)
- ✅ Enhanced Scan Limits: 500K files (was 50K), 120s timeout (was 30s)
- ✅ Deep Scan Mode:
--deep-scanfor unlimited scanning - ✅ Windows Directory Support:
--include-windowsfor system directory scanning
📊 Available Scripts (13+ Total)
Core Analysis: 1. analyze_disk.py - Disk analysis with smart sampling (enhanced v2.2) 2. analyze_progressive.py - Progressive scanning for large disks 3. find_duplicates.py - Duplicate file detection (NEW v2.2) 4. analyze_growth.py - Growth trend analysis (NEW v2.2)
Cleaning & Organization: 5. clean_disk.py - Safe junk file cleaning (enhanced cache detection) 6. interactive_wizard.py - Interactive guided cleanup (NEW v2.2) 7. organize_files.py - File archival and organization (NEW v2.2)
Monitoring & Diagnostics: 8. monitor_disk.py - Disk usage monitoring 9. check_skill.py - Diagnostic tool
Developer Tools: 10. skill_bootstrap.py - Intelligent bootstrap module 11. package_skill.py - Package creation tool 12. scheduler.py - Automated scheduling
🚀 Multi-Agent Acceleration (CRITICAL FOR PERFORMANCE)
🎯 KEY INSIGHT: Use Sub-Agents for Parallel Processing
This is one of the MOST POWERFUL features of disk-cleaner when used by AI Agents!
Why Use Multiple Agents?
Single-threaded scanning:
Agent scans → [Dir1] → [Dir2] → [Dir3] → ... (sequential, slow)Multi-Agent parallel scanning:
Main Agent
├─ Sub-Agent-1 scans → [Dir1, Dir2, Dir3] (parallel)
├─ Sub-Agent-2 scans → [Dir4, Dir5, Dir6] (parallel)
└─ Sub-Agent-3 scans → [Dir7, Dir8, Dir9] (parallel)
Result: 3-10x faster! ⚡When to Use Multi-Agent Mode?
✅ ALWAYS use when:
- Scanning multiple top-level directories (e.g., C:\, D:\, E:\)
- Cleaning multiple user profiles
- Processing large file sets (>100K files)
- Time-critical operations
❌ Don't use when:
- Single small directory (<10K files)
- Quick analysis (<30 seconds)
- Simple cleanup tasks
How to Implement Multi-Agent Scanning
Option 1: Use Agent Tool (Recommended for Claude Code)
# Agent code that spawns sub-agents
from anthropic import Anthropic
def parallel_scan_directories(directories_list):
"""
Scan multiple directories in parallel using sub-agents
"""
# Split directories among sub-agents (3-5 agents optimal)
chunks = split_list(directories_list, n_chunks=3)
# Launch sub-agents in parallel
agents = []
for i, chunk in enumerate(chunks):
agent = Anthropic().agent.create(
name=f"scanner-{i}",
instructions=f"""
Scan these directories: {chunk}
Use disk-cleaner with: --file-limit 50000 --time-limit 60
Return results in JSON format
""",
model="claude-sonnet-4-20250514" # Fast model
)
agents.append(agent)
# Wait for all agents and collect results
results = [agent.wait() for agent in agents]
return merge_results(results)Option 2: Parallel Subprocess (Platform Independent)
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
def parallel_scan_subprocess(directories_list):
"""
Scan multiple directories using parallel subprocess calls
Works with ALL AI IDEs that support subprocess
"""
def scan_directory(directory):
"""Scan a single directory"""
cmd = [
'python',
'skills/disk-cleaner/scripts/analyze_disk.py',
'--path', str(directory),
'--file-limit', '50000',
'--json'
]
result = subprocess.run(cmd, capture_output=True, text=True)
return json.loads(result.stdout)
# Use 3-5 parallel workers
with ThreadPoolExecutor(max_workers=4) as executor:
futures = {
executor.submit(scan_directory, d): d
for d in directories_list
}
results = {}
for future in as_completed(futures):
directory = futures[future]
try:
results[directory] = future.result()
except Exception as e:
results[directory] = {"error": str(e)}
return results
# Usage: Scan C:\, D:\, E:\ in parallel
directories = ['C:\\', 'D:\\', 'E:\\']
results = parallel_scan_subprocess(directories)Option 3: Multi-Platform Agent Team (Advanced)
For Agent-capable systems (Claude Code, Cursor with Agent support):
# Create agent team for massive parallelism
def create_agent_team_scan(top_level_paths):
"""
Create a team of sub-agents, each handling a portion of the work
"""
team_config = {
"name": "disk-cleaner-team",
"members": [
{
"name": "scanner-1",
"task": f"Scan {top_level_paths[0]} using progressive mode",
"tool_use": "subprocess"
},
{
"name": "scanner-2",
"task": f"Scan {top_level_paths[1]} using progressive mode",
"tool_use": "subprocess"
},
{
"name": "scanner-3",
"task": f"Scan {top_level_paths[2]} using progressive mode",
"tool_use": "subprocess"
}
],
"coordination": "parallel", # All run at once
"aggregation": "merge_results" # Combine results at end
}
return launch_agent_team(team_config)Performance Comparison
| Scenario | Single Agent | 3 Sub-Agents | Speedup |
|---|---|---|---|
| 3 disks (1TB each) | ~15 minutes | ~5 minutes | 3x |
| 10 user profiles | ~20 minutes | ~4 minutes | 5x |
| 1M files | ~30 minutes | ~6 minutes | 5x |
| Cache cleanup (26 locations) | ~8 minutes | ~2 minutes | 4x |
Recommended Agent Count
- Small job (<50K files): 1 agent (not worth overhead)
- Medium job (50K-500K files): 2-3 agents
- Large job (500K-2M files): 3-5 agents
- Massive job (>2M files): 5-8 agents
Sweet spot: 3-4 agents (best balance of speed vs overhead)
Best Practices
1. Divide work logically: By disk, by user profile, by directory type 2. Use consistent parameters: All agents use same scan limits 3. Merge results carefully: Deduplicate, sort, format 4. Handle failures gracefully: If one agent fails, continue others 5. Monitor progress: Track which agents completed
⚠️ IMPORTANT: Not all AI IDEs support sub-agents. Fall back to parallel subprocess if unavailable.
📚 Documentation (Essential Files Only)
- SKILL.md - This complete guide (READ THIS FIRST)
- README.md - Project overview and quick start
- README_zh.md - Chinese documentation
- CHANGELOG.md - Version history and changes
- AGENT_QUICK_REF.txt - One-page reference for agents
- INSTALL.md - Detailed installation guide
- NO_PYTHON_GUIDE.md - Help for users without Python
- references/temp_locations.md - Platform-specific cache locations
---
🎯 AGENT CHECKLIST (Must Follow)
For ANY Disk Analysis Request:
[ ] STEP 1: Check Python availability
[ ] STEP 2: Find skill package (auto-detects 20+ locations)
[ ] STEP 3: Run diagnostics: python scripts/check_skill.py
[ ] STEP 4: Run quick sample: --sample (MANDATORY - do this FIRST)
[ ] STEP 5: Choose scan mode based on estimate
[ ] STEP 6: Inform user of expected time
[ ] STEP 7: Execute scan with appropriate limits
[ ] STEP 8: Show results and offer next stepsFor ANY Clean Request:
[ ] STEP 1-3: Same as above
[ ] STEP 4: ALWAYS use --dry-run first (MANDATORY)
[ ] STEP 5: Show preview results
[ ] STEP 6: Ask user confirmation before --force
[ ] STEP 7: Execute actual cleaning if confirmed---
⚡ PROGRESSIVE SCANNING (CRITICAL FOR LARGE DISKS)
🚨 CRITICAL INFORMATION (Read First)
Universal Compatibility
- ✅ Works with ALL AI IDEs: Cursor, Windsurf, Continue, Aider, Claude Code, etc.
- ✅ Platform Independent: Windows, macOS, Linux
- ✅ Installation Agnostic: Works at global, project, or user level
- ✅ Self-Contained: No external dependencies, just Python 3.7+
The Only Requirement
import subprocess
result = subprocess.run(['python', '--version'], capture_output=True, text=True)
# If this succeeds, you can use this skill---
⚡ PROGRESSIVE SCANNING (CRITICAL FOR LARGE DISKS)
🚨 AGENT MUST USE FOR LARGE DISKS (>100GB)
Problem: Full disk scan on large disks (500GB+) can take HOURS. Users will lose patience.
Solution: Use progressive scanning - get results in seconds, not hours!
📋 Three Scanning Modes
Mode 1: Quick Sample (⚡ FASTEST - 1 Second)
When to use: Get instant estimate of directory size and scan time
python scripts/analyze_disk.py --sample
# OR
python scripts/analyze_progressive.py --sampleOutput:
{
"sample_file_count": 7591,
"sample_size_gb": 17.62,
"files_per_second": 7501.0,
"estimated_time_seconds": 2.0
}Agent Decision Logic:
# ALWAYS run quick sample first for unknown disk sizes
result = subprocess.run(
['python', 'scripts/analyze_disk.py', '--sample', '--json'],
capture_output=True,
text=True,
timeout=10,
cwd=skill_path
)
import json
sample = json.loads(result.stdout)
estimated_time = sample.get('estimated_time_seconds', 0)
# Choose next action based on estimate
if estimated_time < 30:
mode = 'full' # Full scan is fine
elif estimated_time < 120:
mode = 'time_limited' # Use time limit
else:
mode = 'progressive' # MUST use progressive modeMode 2: Progressive Scan (🎯 RECOMMENDED FOR LARGE DISKS)
When to use: Get partial results in 30 seconds for large disks
python scripts/analyze_progressive.py --max-seconds 30Features:
- ✅ Real-time progress updates every 2 seconds
- ✅ Shows partial results as they're discovered
- ✅ Can interrupt with Ctrl+C and keep results
- ✅ Smart file count limit (50,000 by default)
Agent Usage:
def analyze_large_disk_safely(path, max_seconds=30):
"""Safely analyze large disk with time limit"""
result = subprocess.run(
['python', 'scripts/analyze_progressive.py',
'--max-seconds', str(max_seconds),
'--path', str(path)],
capture_output=True,
text=True,
timeout=max_seconds + 10,
cwd=skill_path
)
return result.stdoutMode 3: Limited Scan (📊 FAST RESULTS)
When to use: Get results quickly with file/time limits
# Limit by file count (fast)
python scripts/analyze_disk.py --file-limit 10000
# Limit by time (safe)
python scripts/analyze_disk.py --time-limit 30
# Both limits (very fast)
python scripts/analyze_disk.py --file-limit 10000 --time-limit 30🎯 AGENT DECISION TREE (CRITICAL - FOLLOW THIS)
USER REQUEST: "Analyze my disk"
↓
┌─────────────────────────────────────────┐
│ STEP 1: Quick Sample (MANDATORY) │
│ python scripts/analyze_disk.py --sample │
└─────────────────────────────────────────┘
↓
Get estimated_time from sample
↓
├─ estimated_time < 30 seconds
│ ↓
│ ✅ Use FULL SCAN
│ python scripts/analyze_disk.py
│
├─ estimated_time 30-120 seconds
│ ↓
│ ⚠️ Use TIME LIMIT
│ python scripts/analyze_disk.py --time-limit 60
│ Tell user: "This will take ~X minutes"
│
└─ estimated_time > 120 seconds
↓
🚨 MUST USE PROGRESSIVE MODE
python scripts/analyze_progressive.py --max-seconds 30
Tell user: "Large disk detected - using progressive scan (30 seconds)"📝 Required Response Templates
When Quick Sample Shows < 30 Seconds
✅ Disk analysis will take approximately 2 seconds
Starting full scan...When Quick Sample Shows 30-120 Seconds
⚠️ Disk analysis will take approximately 3 minutes
Using time-limited scan for faster results...When Quick Sample Shows > 120 Seconds
🚨 Large disk detected! Full scan would take approximately 5 minutes
Using progressive scan mode to get results in 30 seconds...
(This gives you partial results quickly - you can always do a full scan later)💡 COPY-PASTE CODE (USE THIS)
import subprocess
import json
from pathlib import Path
def smart_disk_analysis(disk_path, python_cmd='python'):
"""
SMART disk analysis - automatically chooses best method
THIS IS THE RECOMMENDED WAY for all agents
"""
# Find skill package
skill_path = find_skill_package() # Use your find function
if not skill_path:
return False, "Skill package not found"
# STEP 1: Quick sample (MANDATORY - do this first!)
print("🔍 Quick sampling disk...")
sample_result = subprocess.run(
[python_cmd, 'scripts/analyze_disk.py', '--sample',
'--path', str(disk_path), '--json'],
capture_output=True,
text=True,
timeout=10,
cwd=str(skill_path)
)
if sample_result.returncode != 0:
return False, "Sample failed"
try:
sample = json.loads(sample_result.stdout)
estimated_time = sample.get('estimated_time_seconds', 0)
except:
estimated_time = 0
print(f" Estimated scan time: {estimated_time:.0f} seconds")
# STEP 2: Choose scanning method based on estimate
if estimated_time < 30:
# Small disk - full scan
print("✅ Using full scan (small disk)")
result = subprocess.run(
[python_cmd, 'scripts/analyze_disk.py',
'--path', str(disk_path)],
capture_output=True,
text=True,
timeout=60,
cwd=str(skill_path)
)
elif estimated_time < 120:
# Medium disk - time limited
minutes = estimated_time / 60
print(f"⚠️ Using time-limited scan (~{minutes:.1f} minutes)")
result = subprocess.run(
[python_cmd, 'scripts/analyze_disk.py',
'--path', str(disk_path),
'--time-limit', '60'], # 1 minute max
capture_output=True,
text=True,
timeout=70,
cwd=str(skill_path)
)
else:
# Large disk - PROGRESSIVE MODE (MANDATORY)
print(f"🚨 Large disk detected! Using progressive scan (30 seconds)")
result = subprocess.run(
[python_cmd, 'scripts/analyze_progressive.py',
'--path', str(disk_path),
'--max-seconds', '30',
'--json'],
capture_output=True,
text=True,
timeout=40,
cwd=str(skill_path)
)
if result.returncode != 0:
return False, result.stderr
print(result.stdout)
return True, result.stdout
# USAGE - Just call this function:
success, output = smart_disk_analysis("C:\\")
success, output = smart_disk_analysis("/home/user")
success, output = smart_disk_analysis("D:\\Projects")⚠️ COMMON MISTAKES TO AVOID
❌ DON'T: Always run full scan without checking disk size first ✅ DO: Always run --sample first to estimate
❌ DON'T: Let users wait hours for large disk scan ✅ DO: Use --max-seconds 30 for large disks
❌ DON'T: Ignore estimated time from sample ✅ DO: Use estimate to choose appropriate scan mode
❌ DON'T: Run analyze_disk.py on 1TB disk without limits ✅ DO: Use analyze_progressive.py with --max-seconds
📊 Command Reference Card
| Situation | Command | Time | Result |
|---|---|---|---|
| Unknown size | --sample | 1s | Estimate + recommendation |
| < 30 seconds | analyze_disk.py | <30s | Full results |
| 30-120 seconds | --time-limit 60 | 60s | Most results |
| > 120 seconds | analyze_progressive.py --max-seconds 30 | 30s | Partial results |
| Very large disk | analyze_progressive.py --max-seconds 60 | 60s | More results |
🔍 How Progressive Scan Works
1. Quick Sample (1 second)
- Scans as many files as possible in 1 second
- Calculates scan rate (files/second)
- Estimates total scan time
- Gives immediate feedback
2. Progressive Scan (N seconds)
- Scans continuously for N seconds
- Shows progress every 2 seconds
- Collects results as it goes
- Returns whatever it found when time is up
- User can interrupt and keep partial results
3. Smart Stopping
- Stops automatically when time/file limit reached
- Graceful handling of interruption
- Partial results are still useful
🎯 BEST PRACTICE FOR AGENTS
1. ALWAYS run --sample first 2. ALWAYS check estimated_time before choosing mode 3. ALWAYS inform user about expected scan time 4. ALWAYS use progressive mode for large disks (>2 minutes) 5. NEVER let users wait more than 2 minutes without feedback
---
---
🎯 UNIVERSAL AGENT WORKFLOW
This workflow works on ANY platform, in ANY AI IDE, at ANY location.
STEP 1: Verify Python
import subprocess
import sys
def verify_python():
"""Check if Python 3.7+ is available - PLATFORM AGNOSTIC"""
try:
# Try 'python' first (Windows, some Unix)
result = subprocess.run(
['python', '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
return True, result.stderr.strip() or result.stdout.strip()
except FileNotFoundError:
pass
try:
# Try 'python3' (macOS, Linux)
result = subprocess.run(
['python3', '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
return True, result.stderr.strip() or result.stdout.strip()
except FileNotFoundError:
pass
return False, "Python not found"
# EXECUTE
python_available, version_info = verify_python()
if not python_available:
print("❌ Python 3.7+ required but not found")
print("Install from: https://www.python.org/downloads/")
print("See: NO_PYTHON_GUIDE.md for platform-specific instructions")
sys.exit(1)
print(f"✅ Python available: {version_info}")STEP 2: Locate Skill Package
from pathlib import Path
import os
def find_skill_package():
"""
Find disk-cleaner skill package - LOCATION INDEPENDENT
Searches in order of priority:
1. Current working directory
2. Project-level: ./skills/, ./.skills/, ./agent-skills/
3. User-level: ~/.skills/, ~/.agent-skills/, ~/skill-packages/
4. Common AI IDE locations
5. Parent directories
"""
# Get python command (python or python3)
python_cmd = 'python' if os.name == 'nt' else 'python3'
# Search locations - PLATFORM & IDE AGNOSTIC
search_locations = []
# 1. Current directory
cwd = Path.cwd()
search_locations.extend([
cwd / 'disk-cleaner',
cwd / 'skills' / 'disk-cleaner',
cwd / '.skills' / 'disk-cleaner',
cwd / 'agent-skills' / 'disk-cleaner',
cwd / '.agent-skills' / 'disk-cleaner',
])
# 2. Parent directories (project root)
for parent in [cwd, *cwd.parents]:
search_locations.extend([
parent / 'skills' / 'disk-cleaner',
parent / '.skills' / 'disk-cleaner',
parent / 'agent-skills' / 'disk-cleaner',
])
# Limit search depth
if len(parent.parts) <= 3:
break
# 3. User home directory
home = Path.home()
search_locations.extend([
home / 'skills' / 'disk-cleaner',
home / '.skills' / 'disk-cleaner',
home / 'agent-skills' / 'disk-cleaner',
home / '.agent-skills' / 'disk-cleaner',
home / 'skill-packages' / 'disk-cleaner',
])
# 4. Platform-specific user directories
if os.name == 'nt': # Windows
appdata = os.environ.get('APPDATA', '')
if appdata:
search_locations.append(Path(appdata) / 'skills' / 'disk-cleaner')
localappdata = os.environ.get('LOCALAPPDATA', '')
if localappdata:
search_locations.append(Path(localappdata) / 'skills' / 'disk-cleaner')
else: # Unix-like (macOS, Linux)
search_locations.extend([
home / '.local' / 'share' / 'skills' / 'disk-cleaner',
home / '.config' / 'skills' / 'disk-cleaner',
Path('/usr/local/share/skills/disk-cleaner'),
])
# 5. AI IDE specific directories (IDE-AGNOSTIC)
# Note: These are examples - actual locations vary
ide_specific = [
# Cursor
home / '.cursor' / 'skills' / 'disk-cleaner',
home / '.cursor' / 'user' / 'skills' / 'disk-cleaner',
# Windsurf
home / '.windsurf' / 'skills' / 'disk-cleaner',
# Continue
home / '.continue' / 'skills' / 'disk-cleaner',
# Generic
home / '.ai-ide' / 'skills' / 'disk-cleaner',
]
search_locations.extend(ide_specific)
# 6. sys.path entries (Python module style)
for path_entry in sys.path:
if path_entry and path_entry not in ['', '.']:
test_path = Path(path_entry) / 'disk-cleaner'
search_locations.append(test_path)
# Remove duplicates and non-existent
search_locations = list(set(search_locations))
# Search for valid skill package
for location in search_locations:
if not location:
continue
try:
# Check for key indicator files
if (location / 'scripts' / 'analyze_disk.py').exists():
return location, f"Found at: {location}"
if (location / 'SKILL.md').exists():
return location, f"Found at: {location}"
except PermissionError:
continue
except Exception:
continue
# Not found - provide helpful message
print("❌ Skill package not found")
print("\nSearched locations:")
for loc in search_locations[:10]: # Show first 10
print(f" - {loc}")
print("\nSolutions:")
print("1. Extract skill package to one of the locations above")
print("2. Or set environment variable: DISK_CLEANER_SKILL_PATH")
print("3. Or provide explicit path when calling functions")
return None, "Not found"
# EXECUTE
skill_path, message = find_skill_package()
if not skill_path:
print(message)
sys.exit(1)
print(f"✅ {message}")STEP 3: Verify Skill Package
def verify_skill_package(skill_path, python_cmd='python'):
"""Verify skill package integrity - LOCATION INDEPENDENT"""
check_script = skill_path / 'scripts' / 'check_skill.py'
if not check_script.exists():
print(f"❌ Check script not found: {check_script}")
return False
try:
result = subprocess.run(
[python_cmd, str(check_script)],
capture_output=True,
text=True,
timeout=30,
cwd=str(skill_path)
)
# Show verification output
if result.stdout:
print(result.stdout)
if result.returncode == 0:
print("✅ Skill package verified successfully")
return True
else:
print("❌ Skill package verification failed")
if result.stderr:
print(result.stderr)
return False
except subprocess.TimeoutExpired:
print("❌ Verification timed out")
return False
except Exception as e:
print(f"❌ Verification error: {e}")
return False
# Determine python command
python_cmd = 'python' if os.name == 'nt' else 'python3'
# EXECUTE
if not verify_skill_package(skill_path, python_cmd):
sys.exit(1)STEP 4: Execute Operation
def execute_operation(skill_path, operation, python_cmd='python', **kwargs):
"""
Execute disk cleaner operation - PLATFORM & IDE AGNOSTIC
Args:
skill_path: Path to skill package
operation: 'analyze', 'clean', 'monitor'
python_cmd: 'python' or 'python3'
**kwargs: Operation-specific arguments
"""
operation_map = {
'analyze': 'analyze_disk.py',
'clean': 'clean_disk.py',
'monitor': 'monitor_disk.py',
}
if operation not in operation_map:
print(f"❌ Unknown operation: {operation}")
print(f"Valid operations: {list(operation_map.keys())}")
return False, None
script = skill_path / 'scripts' / operation_map[operation]
cmd = [python_cmd, str(script)]
# Add operation-specific arguments
if operation == 'analyze':
if kwargs.get('path'):
cmd.extend(['--path', str(kwargs['path'])])
if kwargs.get('top'):
cmd.extend(['--top', str(kwargs['top'])])
if kwargs.get('json'):
cmd.append('--json')
elif operation == 'clean':
# ALWAYS use dry-run for safety
if not kwargs.get('force'):
cmd.append('--dry-run')
else:
cmd.append('--force')
if kwargs.get('temp'):
cmd.append('--temp')
if kwargs.get('cache'):
cmd.append('--cache')
if kwargs.get('logs'):
cmd.append('--logs')
elif operation == 'monitor':
if kwargs.get('watch'):
cmd.append('--watch')
if kwargs.get('json'):
cmd.append('--json')
try:
print(f"🔧 Executing: {' '.join(cmd)}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=kwargs.get('timeout', 300),
cwd=str(skill_path)
)
# Show output
if result.stdout:
print(result.stdout)
if result.returncode != 0:
if result.stderr:
print(f"❌ Error: {result.stderr}")
return False, None
return True, result.stdout
except subprocess.TimeoutExpired:
print("⚠️ Operation timed out")
return False, None
except Exception as e:
print(f"❌ Execution error: {e}")
return False, None
# EXECUTE
success, output = execute_operation(
skill_path,
operation='analyze',
python_cmd=python_cmd,
path='C:\\Users' if os.name == 'nt' else '/home',
json=True
)
if success:
print("✅ Operation completed successfully")---
📋 QUICK REFERENCE (All IDEs)
⚠️ CRITICAL: Use Progressive Scanning for Large Disks!
BEFORE ANY DISK ANALYSIS: 1. Run --sample first to estimate scan time 2. If > 2 minutes estimated, use progressive mode 3. NEVER let users wait more than 2 minutes without feedback
Quick Reference:
# STEP 1: ALWAYS DO THIS FIRST (1 second)
python scripts/analyze_disk.py --sample
# STEP 2: Choose based on estimate
# < 30s: python scripts/analyze_disk.py
# 30-120s: python scripts/analyze_disk.py --time-limit 60
# > 120s: python scripts/analyze_progressive.py --max-seconds 30User Intent → Operation Mapping
User Intent → Operation Mapping
| User Says | Operation | Command Template |
|---|---|---|
| "analyze disk" | analyze | python scripts/analyze_disk.py |
| "check disk space" | analyze | python scripts/analyze_disk.py --top 50 |
| "quick scan" | analyze | python scripts/analyze_disk.py --sample |
| "large disk analysis" | analyze | python scripts/analyze_progressive.py --max-seconds 30 |
| "clean temp files" | clean | python scripts/clean_disk.py --temp --dry-run |
| "preview cleanup" | clean | python scripts/clean_disk.py --dry-run |
| "monitor disk" | monitor | python scripts/monitor_disk.py |
| "disk usage" | monitor | python scripts/monitor_disk.py --json |
Progressive Scanning (For Large Disks)
Problem: Full disk scan can take hours on large disks (1TB+)
Solution: Use progressive scanning with time/file limits
# Quick sample (1 second) - Get instant estimate
subprocess.run(['python', 'scripts/analyze_disk.py', '--sample'])
# Progressive scan (30 seconds) - Get partial results quickly
subprocess.run(['python', 'scripts/analyze_progressive.py',
'--max-seconds', '30'])
# Limited file count (fast)
subprocess.run(['python', 'scripts/analyze_disk.py',
'--file-limit', '10000'])
# Full scan with time limit
subprocess.run(['python', 'scripts/analyze_disk.py',
'--time-limit', '120'])Recommended workflow for large disks:
1. First, run --sample to get estimate (1 second) 2. If estimate is too long, use --max-seconds or --file-limit 3. Or use analyze_progressive.py for real-time feedback
Cross-Platform Python Commands
| Platform | Python Command | Check Command |
|---|---|---|
| Windows | python | python --version |
| macOS | python3 | python3 --version |
| Linux | python3 | python3 --version |
Universal Command Template
# Works on ALL platforms
import os
import subprocess
python_cmd = 'python' if os.name == 'nt' else 'python3'
result = subprocess.run([python_cmd, '--version'], capture_output=True, text=True)---
🌐 Multi-IDE Compatibility
This skill package works with ALL AI IDEs that support:
Supported AI IDEs
- ✅ Cursor - Works as project or user-level skill
- ✅ Windsurf - Works at any location
- ✅ Continue - Works as global or project skill
- ✅ Aider - Works as agent tool
- ✅ Claude Code - Works as skill
- ✅ Any AI IDE - Works via subprocess
Installation by IDE Type
Project-Level Installation
your-project/
├── skills/
│ └── disk-cleaner/ ← Extract here
├── src/
└── README.mdUser-Level Installation
~/.skills/disk-cleaner/ ← Unix-like
~/agent-skills/disk-cleaner/ ← Alternative
C:\Users\You\skills\disk-cleaner\ ← WindowsGlobal Installation
/usr/local/share/skills/disk-cleaner/ ← Linux (system-wide)
C:\ProgramData\skills\disk-cleaner\ ← Windows (system-wide)---
🔧 IDE-Specific Notes
Cursor
# Cursor automatically searches for skills in:
# - .cursor/skills/
# - project/skills/
# - ~/.skills/
# No special configuration neededWindsurf
# Windsurf supports skills at:
# - .windsurf/skills/
# - project/skills/
# Skill works via subprocess, no special setupContinue
# Continue configuration (if needed):
# {
# "skills": ["./skills/disk-cleaner"]
# }
# But auto-detection works in most casesAider
# Use as a tool via subprocess:
# !python skills/disk-cleaner/scripts/analyze_disk.pyGeneric/Other IDEs
# The skill includes intelligent auto-detection
# Just extract and use - no configuration needed---
🎯 Complete Copy-Paste Template
"""
Universal Disk Cleaner Skill Usage
Works with ALL AI IDEs, on ALL platforms, at ANY location
"""
import subprocess
import sys
import os
from pathlib import Path
def use_disk_cleaner(operation='analyze', **kwargs):
"""
Use disk cleaner skill - UNIVERSAL FUNCTION
Works on:
- All platforms (Windows, macOS, Linux)
- All AI IDEs (Cursor, Windsurf, Continue, etc.)
- All installation levels (global, project, user)
Args:
operation: 'analyze', 'clean', 'monitor'
**kwargs: Operation-specific arguments
Returns:
(success: bool, output: str)
"""
# 1. Determine Python command
python_cmd = 'python' if os.name == 'nt' else 'python3'
# 2. Verify Python
try:
result = subprocess.run(
[python_cmd, '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode != 0:
print("❌ Python not found")
return False, "Python 3.7+ required"
except Exception as e:
print(f"❌ Python check failed: {e}")
return False, str(e)
# 3. Find skill package (intelligent search)
def find_skill():
search_paths = [
# Current directory
Path.cwd() / 'disk-cleaner',
Path.cwd() / 'skills' / 'disk-cleaner',
Path.cwd() / '.skills' / 'disk-cleaner',
# Project root
*[(p / 'skills' / 'disk-cleaner') for p in [Path.cwd(), *Path.cwd().parents][:3]],
# User home
Path.home() / 'skills' / 'disk-cleaner',
Path.home() / '.skills' / 'disk-cleaner',
]
for path in search_paths:
if (path / 'scripts' / 'analyze_disk.py').exists():
return path
return None
skill_path = find_skill()
if not skill_path:
print("❌ Skill package not found")
print("Extract to: skills/disk-cleaner/ or ~/.skills/disk-cleaner/")
return False, "Skill package not found"
# 4. Execute operation
scripts = {
'analyze': 'analyze_disk.py',
'clean': 'clean_disk.py',
'monitor': 'monitor_disk.py',
}
if operation not in scripts:
return False, f"Unknown operation: {operation}"
script = skill_path / 'scripts' / scripts[operation]
cmd = [python_cmd, str(script)]
# Add arguments
if operation == 'analyze':
if kwargs.get('path'):
cmd.extend(['--path', str(kwargs['path'])])
elif operation == 'clean':
cmd.append('--dry-run') # Always safe
elif operation == 'monitor':
if kwargs.get('json'):
cmd.append('--json')
# Run
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300,
cwd=str(skill_path)
)
return result.returncode == 0, result.stdout
except Exception as e:
return False, str(e)
# USAGE EXAMPLES:
# success, output = use_disk_cleaner('analyze', path='.')
# success, output = use_disk_cleaner('clean')
# success, output = use_disk_cleaner('monitor', json=True)
if __name__ == "__main__":
# Quick test
success, output = use_disk_cleaner('analyze')
print(output)---
⚡ PROGRESSIVE SCANNING CHECKLIST (CRITICAL)
Agent MUST Follow This for All Disk Analysis Requests:
[ ] Did you run --sample FIRST to estimate scan time?
[ ] Did you inform user about expected scan time?
[ ] Did you choose appropriate mode based on estimate?
[ ] Did you use progressive mode for >2 minute estimates?
[ ] Did you provide feedback during long scans?Required Behavior by Disk Size:
Small Disk (< 30 seconds estimated):
- ✅ Run full scan
- ✅ No special handling needed
Medium Disk (30-120 seconds estimated):
- ✅ Use --time-limit
- ✅ Inform user of expected time
- ⚠️ Consider progressive mode if user seems impatient
Large Disk (> 120 seconds estimated):
- 🚨 MUST USE PROGRESSIVE MODE
- 🚨 MUST Inform user about time limit
- ✅ Explain partial results
- ✅ Offer full scan as option
REQUIRED Code Pattern:
# THIS IS THE REQUIRED PATTERN for all disk analysis:
def safe_disk_analysis(disk_path):
# Step 1: ALWAYS sample first
sample = subprocess.run(
['python', 'scripts/analyze_disk.py', '--sample', '--json'],
capture_output=True,
text=True,
cwd=skill_path
)
import json
data = json.loads(sample.stdout)
estimate = data.get('estimated_time_seconds', 0)
# Step 2: Inform user
if estimate > 120:
print(f"Large disk detected! Estimated: {estimate/60:.1f} minutes")
print("Using progressive scan (30 seconds) for quick results...")
# Step 3: Choose mode
if estimate < 30:
# Full scan
return subprocess.run(['python', 'scripts/analyze_disk.py'])
elif estimate < 120:
# Time limited
return subprocess.run(['python', 'scripts/analyze_disk.py', '--time-limit', '60'])
else:
# Progressive (MANDATORY for large disks)
return subprocess.run(['python', 'scripts/analyze_progressive.py', '--max-seconds', '30'])---
🚨 Error Handling (Universal)
Error: Python not found
# SOLUTION - Platform-independent guidance
import platform
system = platform.system()
if system == "Windows":
print("Install from: https://www.python.org/downloads/")
print("Check 'Add Python to PATH' during installation")
elif system == "Darwin": # macOS
print("Install: brew install python@3.11")
print("Or: https://www.python.org/downloads/macos/")
else: # Linux
print("Install: sudo apt install python3 # Debian/Ubuntu")
print("Or: sudo dnf install python3 # Fedora")Error: Skill not found
# SOLUTION - Show all searched locations
print("Skill package not found.")
print("Extract to ONE of these locations:")
print(" - ./skills/disk-cleaner/")
print(" - ./disk-cleaner/")
print(" - ~/.skills/disk-cleaner/")
print(" - project/skills/disk-cleaner/")
print("\nOr set: DISK_CLEANER_SKILL_PATH=/path/to/skill")Error: Permission denied
# SOLUTION - Platform-specific advice
if os.name == 'nt':
print("Run as Administrator if needed")
else:
print("Some directories may require: sudo")
print("Or adjust: chmod +x scripts/*.py")---
📝 Environment Variables (Optional)
You can optionally set these to help with auto-detection:
# Set skill package location (overrides auto-detection)
export DISK_CLEANER_SKILL_PATH=/path/to/skills/disk-cleaner
# Set Python command (overrides auto-detection)
export DISK_CLEANER_PYTHON_CMD=python3
# Enable debug output
export DISK_CLEANER_DEBUG=true---
🎓 Best Practices for All Agents
1. ALWAYS check Python first - Don't assume it's installed 2. Use 'python3' on Unix - Use 'python' on Windows 3. Search multiple locations - Don't assume single installation path 4. Use subprocess with timeout - Prevent hanging 5. Capture both stdout and stderr - Complete error information 6. Prefer --dry-run for clean - Safety first 7. Handle all exceptions - Graceful degradation 8. Show helpful error messages - Guide users to solutions
---
🔍 Troubleshooting (Universal)
Problem: Skill works in one IDE but not another
Solution: The skill is IDE-agnostic. Check: 1. Python is accessible from that IDE 2. Skill package is in a searchable location 3. File permissions allow execution
Problem: Different behavior on different platforms
Solution: The skill handles platform differences. Check: 1. Python command (python vs python3) 2. Path separators (auto-handled by pathlib) 3. File permissions (Unix may need chmod +x)
Problem: Can't find skill package
Solution: Run diagnostic:
python skills/disk-cleaner/scripts/check_skill.py
# Or
python3 skills/disk-cleaner/scripts/check_skill.py---
📦 Package Contents (Universal)
disk-cleaner/
├── SKILL.md # This file
├── AGENT_QUICK_REF.txt # Quick reference
├── NO_PYTHON_GUIDE.md # Help for users without Python
├── INSTALL.md # Installation guide
├── FIXES.md # What's fixed in v2.0
├── scripts/ # All scripts (universal)
├── diskcleaner/ # Core modules (self-contained)
└── references/ # Platform information---
✅ Universal Checklist
Before using this skill in ANY AI IDE:
- [ ] Python 3.7+ installed
- [ ] Skill package extracted to accessible location
- [ ] Can run:
python --version(orpython3 --version) - [ ] Skill package location known (or let it auto-detect)
---
This skill package works EVERYWHERE - just Python 3.7+ required!
No IDE-specific configuration needed. No platform-specific setup. No installation level restrictions.
Just extract and use!
╔══════════════════════════════════════════════════════════════════════════════╗
║ DISK CLEANER - UNIVERSAL AGENT QUICK REFERENCE CARD ║
║ (Works with ALL AI IDEs on ALL platforms) ║
╚══════════════════════════════════════════════════════════════════════════════╝
┌─ UNIVERSAL REQUIREMENTS ────────────────────────────────────────────────────┐
│ │
│ ✅ Python 3.7+ (ONLY requirement) │
│ ✅ Works on: Windows, macOS, Linux │
│ ✅ Works with: Cursor, Windsurf, Continue, Aider, Claude Code, etc. │
│ ✅ Works at: Global, Project, or User level │
│ ✅ NO pip install needed (self-contained) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ STEP 1: DETECT PYTHON (PLATFORM AGNOSTIC) ───────────────────────────────┐
│ │
│ import subprocess, os │
│ │
│ # Use 'python' on Windows, 'python3' on Unix │
│ python_cmd = 'python' if os.name == 'nt' else 'python3' │
│ │
│ result = subprocess.run([python_cmd, '--version'], │
│ capture_output=True, text=True) │
│ if result.returncode != 0: │
│ print("ERROR: Python 3.7+ required") │
│ print("Install: https://www.python.org/downloads/") │
│ return False │
│ │
│ print(f"OK: Python {result.stderr.strip()}") │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ STEP 2: FIND SKILL PACKAGE (LOCATION INDEPENDENT) ───────────────────────┐
│ │
│ from pathlib import Path │
│ │
│ # Search in order of priority (works everywhere) │
│ search_paths = [ │
│ # Current directory │
│ Path.cwd() / 'disk-cleaner', │
│ Path.cwd() / 'skills' / 'disk-cleaner', │
│ Path.cwd() / '.skills' / 'disk-cleaner', │
│ # Project root (up to 3 levels) │
│ *[p / 'skills' / 'disk-cleaner' │
│ for p in [Path.cwd(), *Path.cwd().parents][:3]], │
│ # User home (universal) │
│ Path.home() / 'skills' / 'disk-cleaner', │
│ Path.home() / '.skills' / 'disk-cleaner', │
│ ] │
│ │
│ # Find valid skill package │
│ skill_path = None │
│ for path in search_paths: │
│ if (path / 'scripts' / 'analyze_disk.py').exists(): │
│ skill_path = path │
│ break │
│ │
│ if not skill_path: │
│ print("ERROR: Skill package not found") │
│ print("Extract to: skills/disk-cleaner/ or ~/.skills/disk-cleaner/") │
│ return False │
│ │
│ print(f"OK: Found at {skill_path}") │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ STEP 3: VERIFY SKILL (OPTIONAL BUT RECOMMENDED) ──────────────────────────┐
│ │
│ result = subprocess.run( │
│ [python_cmd, 'scripts/check_skill.py'], │
│ cwd=str(skill_path), │
│ capture_output=True, text=True │
│ ) │
│ │
│ if result.returncode != 0: │
│ print("ERROR: Skill verification failed") │
│ print(result.stdout) │
│ return False │
│ │
│ print("OK: Skill verified") │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ STEP 4: EXECUTE OPERATION ─────────────────────────────────────────────────┐
│ │
│ operations = { │
│ 'analyze': 'analyze_disk.py', │
│ 'clean': 'clean_disk.py', │
│ 'monitor': 'monitor_disk.py', │
│ } │
│ │
│ script = skill_path / 'scripts' / operations[operation] │
│ cmd = [python_cmd, str(script)] │
│ │
│ # Add operation-specific args │
│ if operation == 'analyze' and target_path: │
│ cmd.extend(['--path', str(target_path)]) │
│ elif operation == 'clean': │
│ cmd.append('--dry-run') # ALWAYS use dry-run first! │
│ │
│ result = subprocess.run(cmd, cwd=str(skill_path), │
│ capture_output=True, text=True) │
│ │
│ print(result.stdout) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ USER INTENT MAPPING ──────────────────────────────────────────────────────┐
│ │
│ User says any of: → Execute: │
│ ───────────────────────────────────────────────── │
│ "analyze disk" analyze_disk.py │
│ "check disk space" analyze_disk.py --top 50 │
│ "what's using space" analyze_disk.py │
│ "disk usage" analyze_disk.py │
│ │
│ "clean temp files" clean_disk.py --temp --dry-run │
│ "preview cleanup" clean_disk.py --dry-run │
│ "remove junk files" clean_disk.py --dry-run │
│ │
│ "monitor disk" monitor_disk.py │
│ "check disk health" monitor_disk.py │
│ "disk space alert" monitor_disk.py │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ CROSS-PLATFORM COMMANDS ──────────────────────────────────────────────────┐
│ │
│ Platform Python Command Check Command │
│ ──────────────────────────────────────────────────── │
│ Windows python python --version │
│ macOS python3 python3 --version │
│ Linux python3 python3 --version │
│ │
│ Universal: │
│ python_cmd = 'python' if os.name == 'nt' else 'python3' │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ INSTALLATION LOCATIONS (ALL WORK) ────────────────────────────────────────┐
│ │
│ User Level (Recommended): │
│ ~/.skills/disk-cleaner/ # Unix-like (macOS, Linux) │
│ ~/agent-skills/disk-cleaner/ # Alternative │
│ C:\Users\You\skills\disk-cleaner\ # Windows │
│ │
│ Project Level: │
│ project/skills/disk-cleaner/ # Any project │
│ project/.skills/disk-cleaner/ # Hidden │
│ │
│ Global/System Level: │
│ /usr/local/share/skills/disk-cleaner/ # Linux (system-wide) │
│ C:\ProgramData\skills\disk-cleaner\ # Windows (system-wide) │
│ │
│ ANY location works! The skill includes intelligent auto-detection. │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ AI IDE COMPATIBILITY ─────────────────────────────────────────────────────┐
│ │
│ ✅ Cursor - Extract to ~/.cursor/skills/ or project/skills/ │
│ ✅ Windsurf - Extract to ~/.windsurf/skills/ or project/skills/ │
│ ✅ Continue - Extract to ~/.continue/skills/ or project/skills/ │
│ ✅ Aider - Use via subprocess: !python skills/.../analyze_disk.py │
│ ✅ Claude Code - Extract to ~/.claude/skills/ or project/skills/ │
│ ✅ Any IDE - Works via subprocess, just extract and use │
│ │
│ NO IDE-specific configuration needed! │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ SAFETY RULES (CRITICAL) ─────────────────────────────────────────────────┐
│ │
│ ⚠️ NEVER run clean_disk.py without --dry-run first │
│ ⚠️ ALWAYS preview before actual cleaning │
│ ⚠️ ALWAYS use absolute paths to skill package │
│ ⚠️ ALWAYS check subprocess returncode │
│ ⚠️ ALWAYS capture and show output to user │
│ ⚠️ ALWAYS handle exceptions gracefully │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ COPY-PASTE UNIVERSAL TEMPLATE ───────────────────────────────────────────┐
│ │
│ import subprocess, os │
│ from pathlib import Path │
│ │
│ # 1. Detect Python │
│ python_cmd = 'python' if os.name == 'nt' else 'python3' │
│ result = subprocess.run([python_cmd, '--version'], │
│ capture_output=True, text=True) │
│ if result.returncode != 0: │
│ print("ERROR: Python required") │
│ return │
│ │
│ # 2. Find skill package │
│ skill_path = None │
│ for candidate in [ │
│ Path.cwd() / 'skills' / 'disk-cleaner', │
│ Path.home() / 'skills' / 'disk-cleaner', │
│ ]: │
│ if (candidate / 'scripts' / 'analyze_disk.py').exists(): │
│ skill_path = candidate │
│ break │
│ │
│ if not skill_path: │
│ print("ERROR: Skill not found") │
│ return │
│ │
│ # 3. Execute operation │
│ result = subprocess.run( │
│ [python_cmd, 'scripts/analyze_disk.py'], │
│ cwd=str(skill_path), │
│ capture_output=True, text=True │
│ ) │
│ │
│ print(result.stdout) │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ ERROR HANDLING (UNIVERSAL) ───────────────────────────────────────────────┐
│ │
│ Python not found → Install: https://www.python.org/downloads/ │
│ Skill not found → Extract to: skills/disk-cleaner/ or │
│ ~/.skills/disk-cleaner/ │
│ Permission denied → Use appropriate permissions for your platform │
│ Timeout → Large directory scanning takes time │
│ Module error → Run: python scripts/check_skill.py │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
╔══════════════════════════════════════════════════════════════════════════════╗
║ SUMMARY: Works EVERYWHERE - All platforms, All IDEs, All locations ║
║ Just Python 3.7+ required. Extract and use. No special setup needed. ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""
disk-cleaner - Cross-platform disk space management toolkit
A comprehensive toolkit for monitoring, analyzing, and cleaning disk space
safely across Windows, Linux, and macOS.
"""
__version__ = "2.0.0-dev"
__author__ = "Disk Cleaner Contributors"
from diskcleaner.config import Config
from diskcleaner.core import DirectoryScanner, FileClassifier, SafetyChecker
__all__ = [
"DirectoryScanner",
"FileClassifier",
"SafetyChecker",
"Config",
]
"""
Configuration management
"""
from diskcleaner.config.defaults import get_default_config
from diskcleaner.config.loader import Config
__all__ = [
"Config",
"get_default_config",
]
"""
Default configuration for disk-cleaner.
This module provides default configuration values that can be overridden
by user config files or command-line arguments.
"""
from typing import Dict, List
def get_default_config() -> Dict:
"""
Get default configuration.
Returns:
Dictionary with default configuration values.
"""
return {
# Protected paths and files
"protected": {
"paths": [
# Windows
"C:\\Windows",
"C:\\Program Files",
"C:\\Program Files (x86)",
"C:\\ProgramData",
# Linux/macOS
"/usr",
"/bin",
"/sbin",
"/lib",
"/System",
"/Library",
],
"patterns": [
"*.database",
"*.db",
"*.sqlite",
"*.sqlite3",
"config.*",
"*.config",
"*.conf",
],
},
# Cleaning rules
"rules": [
{
"name": "Old logs",
"pattern": "*.log",
"category": "Logs",
"risk": "safe",
"age_threshold": 60, # days
},
{
"name": "Build artifacts",
"pattern": "node_modules/",
"category": "Build",
"risk": "safe",
"age_threshold": 0,
},
{
"name": "Python cache",
"pattern": "__pycache__/",
"category": "Cache",
"risk": "safe",
"age_threshold": 0,
},
{
"name": "Python bytecode",
"pattern": "*.pyc",
"category": "Cache",
"risk": "safe",
"age_threshold": 0,
},
{
"name": "Temporary files",
"pattern": "*.tmp",
"category": "Temp",
"risk": "safe",
"age_threshold": 0,
},
],
# Ignore rules (exclude from scanning)
"ignore": [
".git/*",
".svn/*",
".hg/*",
"node_modules/@types",
"*.lock",
],
# Safety settings
"safety": {
"check_file_locks": True,
"verify_permissions": True,
"backup_before_delete": False,
"protected_extensions": [
# Windows executables
".exe",
".dll",
".sys",
".drv",
".bat",
".cmd",
".ps1",
".vbs",
# Unix executables
".sh",
".bash",
".zsh",
".fish",
# macOS
".app",
".dmg",
".pkg",
# Linux
".deb",
".rpm",
# Installers and images
".msi",
".iso",
".vhd",
".vhdx",
".vmdk",
],
},
# Scan settings
"scan": {
"use_incremental": True,
"cache_dir": "~/.disk-cleaner/cache",
"cache_ttl": 7, # days
"parallel_jobs": 4,
"follow_symlinks": False,
"max_depth": None, # None = unlimited
},
# Platform-specific features
"platform_features": {
"enabled": True,
"auto_include": False, # Don't auto-include in cleanup list
},
# Notifications
"notifications": {
"enabled": False,
"webhook_url": "",
"on_completion": True,
"on_error": True,
},
# Display settings
"display": {
"show_hidden": False,
"human_readable": True,
"date_format": "%Y-%m-%d %H:%M",
"max_file_list": 100, # Max files to display in detailed view
},
}
def get_protected_extensions() -> List[str]:
"""
Get list of protected file extensions.
Returns:
List of file extensions that should never be deleted.
"""
return get_default_config()["safety"]["protected_extensions"]
def get_protected_paths() -> List[str]:
"""
Get list of protected system paths.
Returns:
List of paths that should never be scanned/deleted.
"""
return get_default_config()["protected"]["paths"]
"""
Configuration loader with multi-level priority support.
Loads configuration from multiple sources with priority:
1. Command-line arguments (highest)
2. Project directory (.disk-cleaner.yaml)
3. User config (~/.disk-cleaner/config.yaml)
4. Default config (lowest)
"""
from pathlib import Path
from typing import Any, Dict, List, Optional
from diskcleaner.config.defaults import get_default_config
class Config:
"""
Configuration manager with multi-level priority support.
Usage:
config = Config.load(
path="/path/to/project",
cli_args={"age_threshold": 90}
)
"""
def __init__(self, config: Dict[str, Any]):
"""
Initialize configuration.
Args:
config: Configuration dictionary.
"""
self._config = config
@classmethod
def load(
cls,
path: Optional[str] = None,
cli_args: Optional[Dict[str, Any]] = None,
) -> "Config":
"""
Load configuration with proper priority merging.
Args:
path: Project path to look for .disk-cleaner.yaml
cli_args: Command-line arguments (highest priority)
Returns:
Config instance with merged configuration.
"""
# Start with default config
config = get_default_config()
# Load and merge user config
user_config = cls._load_user_config()
config = cls._merge_configs(config, user_config)
# Load and merge project config
if path:
project_config = cls._load_project_config(path)
config = cls._merge_configs(config, project_config)
# Apply command-line arguments (highest priority)
if cli_args:
config = cls._apply_cli_args(config, cli_args)
return cls(config)
@staticmethod
def _load_user_config() -> Dict[str, Any]:
"""
Load user configuration from ~/.disk-cleaner/config.yaml.
Returns:
User config dict, or empty dict if not found.
"""
config_path = Path.home() / ".disk-cleaner" / "config.yaml"
if not config_path.exists():
return {}
return Config._load_yaml_file(config_path)
@staticmethod
def _load_project_config(project_path: str) -> Dict[str, Any]:
"""
Load project configuration from .disk-cleaner.yaml.
Args:
project_path: Path to project directory.
Returns:
Project config dict, or empty dict if not found.
"""
project_dir = Path(project_path)
# Look for .disk-cleaner.yaml in project root
config_path = project_dir / ".disk-cleaner.yaml"
if not config_path.exists():
# Also try disk-cleaner.yaml (without dot)
config_path = project_dir / "disk-cleaner.yaml"
if not config_path.exists():
return {}
return Config._load_yaml_file(config_path)
@staticmethod
def _load_yaml_file(file_path: Path) -> Dict[str, Any]:
"""
Load YAML file without external dependencies.
Args:
file_path: Path to YAML file.
Returns:
Parsed YAML as dict, or empty dict on error.
"""
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# Simple YAML parser for our config format
# This avoids requiring pyyaml dependency
return Config._parse_simple_yaml(content)
except (OSError, IOError):
return {}
@staticmethod
def _parse_simple_yaml(content: str) -> Dict[str, Any]:
"""
Parse simplified YAML format.
This is a basic YAML parser that handles our config format.
It supports:
- Key-value pairs
- Nested dictionaries
- Lists
- Comments (#)
Args:
content: YAML content as string.
Returns:
Parsed configuration as dict.
"""
config: Dict[str, Any] = {}
stack: list = [config]
current = config
indent_size = 0
for line in content.split("\n"):
# Skip empty lines and comments
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
# Calculate indentation
indent = len(line) - len(line.lstrip())
# Adjust stack based on indentation
if indent == 0:
stack = [config]
current = config
elif indent > indent_size:
# Deeper level, but we should have already created the parent
pass
elif indent < indent_size:
# Shallower level, pop stack
while len(stack) > 1 and indent < len(stack[-1]) * 2:
stack.pop()
current = stack[-1]
indent_size = indent
# Parse key-value pair
if ":" in stripped:
key, value = stripped.split(":", 1)
key = key.strip()
value = value.strip()
if not value:
# This is a parent key for nested values
current[key] = {}
stack.append(current[key])
current = current[key]
elif value.startswith("[") and value.endswith("]"):
# List value
list_str = value[1:-1]
current[key] = [item.strip().strip("\"'") for item in list_str.split(",")]
else:
# Simple value
# Try to parse as Python literal
value = value.strip("\"'")
if value.lower() == "true":
value = True
elif value.lower() == "false":
value = False
elif value.lower() == "null" or value.lower() == "none":
value = None
elif value.isdigit():
value = int(value)
else:
# Try float
try:
value = float(value)
except ValueError:
pass
current[key] = value
return config
@staticmethod
def _merge_configs(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
"""
Deep merge two config dictionaries.
Args:
base: Base configuration.
override: Override configuration (higher priority).
Returns:
Merged configuration.
"""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
# Recursively merge nested dicts
result[key] = Config._merge_configs(result[key], value)
else:
# Override with higher priority value
result[key] = value
return result
@staticmethod
def _apply_cli_args(config: Dict[str, Any], cli_args: Dict[str, Any]) -> Dict[str, Any]:
"""
Apply command-line arguments to config.
Args:
config: Current configuration.
cli_args: Command-line arguments.
Returns:
Updated configuration.
"""
# CLI args have highest priority, direct override
for key, value in cli_args.items():
# Support nested keys with dot notation
if "." in key:
parts = key.split(".")
current = config
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
current[parts[-1]] = value
else:
config[key] = value
return config
def get(self, key: str, default: Any = None) -> Any:
"""
Get configuration value.
Args:
key: Configuration key (supports dot notation for nested values).
default: Default value if key not found.
Returns:
Configuration value, or default if not found.
"""
# Support dot notation for nested keys
parts = key.split(".")
current = self._config
try:
for part in parts:
current = current[part]
return current
except (KeyError, TypeError):
return default
def set(self, key: str, value: Any) -> None:
"""
Set configuration value.
Args:
key: Configuration key (supports dot notation).
value: Value to set.
"""
parts = key.split(".")
current = self._config
# Navigate to parent
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
# Set value
current[parts[-1]] = value
@property
def protected_paths(self) -> List[str]:
"""Get protected paths list."""
return self.get("protected.paths", [])
@property
def protected_patterns(self) -> List[str]:
"""Get protected file patterns."""
return self.get("protected.patterns", [])
@property
def protected_extensions(self) -> List[str]:
"""Get protected file extensions."""
return self.get("safety.protected_extensions", [])
@property
def check_file_locks(self) -> bool:
"""Check if file lock detection is enabled."""
return self.get("safety.check_file_locks", True)
@property
def verify_permissions(self) -> bool:
"""Check if permission verification is enabled."""
return self.get("safety.verify_permissions", True)
@property
def use_incremental_scan(self) -> bool:
"""Check if incremental scanning is enabled."""
return self.get("scan.use_incremental", True)
@property
def cache_dir(self) -> str:
"""Get cache directory path."""
return self.get("scan.cache_dir", "~/.disk-cleaner/cache")
@property
def cache_ttl(self) -> int:
"""Get cache TTL in days."""
return self.get("scan.cache_ttl", 7)
def to_dict(self) -> Dict[str, Any]:
"""Get configuration as dictionary."""
return self._config.copy()
"""
Core functionality modules
"""
from diskcleaner.core.cache import CacheManager
from diskcleaner.core.classifier import FileClassifier
from diskcleaner.core.duplicate_finder import DuplicateFinder, DuplicateGroup
from diskcleaner.core.interactive import InteractiveCleanupUI
from diskcleaner.core.process_manager import ProcessInfo, ProcessManager
from diskcleaner.core.progress import IndeterminateProgress, ProgressBar, progress_iterator
from diskcleaner.core.safety import SafetyChecker
from diskcleaner.core.scanner import DirectoryScanner
from diskcleaner.core.smart_cleanup import CleanupReport, SmartCleanupEngine
__all__ = [
"DirectoryScanner",
"FileClassifier",
"SafetyChecker",
"CacheManager",
"DuplicateFinder",
"DuplicateGroup",
"SmartCleanupEngine",
"CleanupReport",
"InteractiveCleanupUI",
"ProcessManager",
"ProcessInfo",
"ProgressBar",
"IndeterminateProgress",
"progress_iterator",
]
"""
Cache management for incremental scanning.
This module provides caching functionality to speed up repeated scans
by storing file metadata and detecting changes.
"""
import hashlib
import json
import time
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
@dataclass
class FileSnapshot:
"""Snapshot of a single file's metadata."""
path: str
size: int
mtime: float
inode: Optional[int] = None
def to_dict(self) -> Dict:
"""Convert to dictionary for JSON serialization."""
return asdict(self)
@classmethod
def from_dict(cls, data: Dict) -> "FileSnapshot":
"""Create from dictionary."""
return cls(**data)
def __hash__(self) -> int:
"""Make FileSnapshot hashable for caching."""
return hash((self.path, self.size, self.mtime, self.inode))
@dataclass
class ScanSnapshot:
"""Snapshot of an entire directory scan."""
path: str
timestamp: float
files: List[FileSnapshot] = field(default_factory=list)
total_size: int = 0
file_count: int = 0
def to_dict(self) -> Dict:
"""Convert to dictionary for JSON serialization."""
return {
"path": self.path,
"timestamp": self.timestamp,
"files": [f.to_dict() for f in self.files],
"total_size": self.total_size,
"file_count": self.file_count,
}
@classmethod
def from_dict(cls, data: Dict) -> "ScanSnapshot":
"""Create from dictionary."""
return cls(
path=data["path"],
timestamp=data["timestamp"],
files=[FileSnapshot.from_dict(f) for f in data.get("files", [])],
total_size=data.get("total_size", 0),
file_count=data.get("file_count", 0),
)
class CacheManager:
"""Manages scan cache for incremental scanning."""
def __init__(self, cache_dir: str = "~/.disk-cleaner/cache"):
"""
Initialize cache manager.
Args:
cache_dir: Directory to store cache files. Supports ~ expansion.
"""
self.cache_dir = Path(cache_dir).expanduser()
self.cache_dir.mkdir(parents=True, exist_ok=True)
def _get_cache_path(self, scan_path: str) -> Path:
"""
Get cache file path for a given scan path.
Args:
scan_path: The path that was scanned.
Returns:
Path to cache file.
"""
# Use MD5 hash of path as cache filename
path_hash = hashlib.md5(str(scan_path).encode()).hexdigest()
return self.cache_dir / f"{path_hash}.json"
def get_scan_cache(self, path: str, max_age_days: int = 7) -> Optional[ScanSnapshot]:
"""
Retrieve cached scan results.
Args:
path: The path to look up in cache.
max_age_days: Maximum age of cache in days (default: 7).
Returns:
ScanSnapshot if cache exists and is valid, None otherwise.
"""
cache_file = self._get_cache_path(path)
# Check if cache exists
if not cache_file.exists():
return None
# Check if cache has expired
cache_age = time.time() - cache_file.stat().st_mtime
max_age_seconds = max_age_days * 24 * 3600
if cache_age > max_age_seconds:
# Cache expired, delete it
cache_file.unlink()
return None
# Load and return cache
try:
with open(cache_file, "r", encoding="utf-8") as f:
data = json.load(f)
return ScanSnapshot.from_dict(data)
except (json.JSONDecodeError, KeyError, ValueError):
# Invalid cache, delete it
cache_file.unlink()
return None
def save_scan_cache(self, path: str, snapshot: ScanSnapshot) -> None:
"""
Save scan results to cache.
Args:
path: The path that was scanned.
snapshot: The scan snapshot to cache.
"""
cache_file = self._get_cache_path(path)
try:
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(snapshot.to_dict(), f, indent=2)
except (OSError, IOError):
# Silently fail if we can't write cache
# This shouldn't break the scan, just disable caching
pass
def is_file_changed(self, current: FileSnapshot, cached: FileSnapshot) -> bool:
"""
Check if a file has changed since last scan.
Args:
current: Current file snapshot.
cached: Cached file snapshot.
Returns:
True if file has changed, False otherwise.
"""
# Check size first (fastest)
if current.size != cached.size:
return True
# Check modification time
if current.mtime != cached.mtime:
return True
# Check inode if available (Unix systems)
if current.inode is not None and cached.inode is not None:
if current.inode != cached.inode:
return True
return False
def clear_cache(self, path: Optional[str] = None) -> None:
"""
Clear cache.
Args:
path: If specified, only clear cache for this path.
If None, clear all cache.
"""
if path is not None:
cache_file = self._get_cache_path(path)
if cache_file.exists():
cache_file.unlink()
else:
# Clear all cache files
for cache_file in self.cache_dir.glob("*.json"):
cache_file.unlink()
def get_cache_stats(self) -> Dict[str, int]:
"""
Get cache statistics.
Returns:
Dictionary with cache stats (total_files, total_size_mb).
"""
cache_files = list(self.cache_dir.glob("*.json"))
total_size = sum(f.stat().st_size for f in cache_files)
return {
"total_files": len(cache_files),
"total_size_mb": int(round(total_size / (1024 * 1024), 2)),
}
"""
File classifier with multi-dimensional categorization.
Classifies files by type, risk level, and age for intelligent
cleanup recommendations.
"""
import fnmatch
from datetime import datetime, timedelta
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional
from diskcleaner.config import Config
from diskcleaner.core.scanner import FileInfo
class RiskLevel(Enum):
"""Risk level for file deletion."""
SAFE = "safe"
CONFIRM_NEEDED = "confirm_needed"
PROTECTED = "protected"
class FileClassifier:
"""
Classifies files into categories for intelligent cleanup.
Provides three-dimensional classification:
1. By type (temp, logs, cache, build artifacts, etc.)
2. By risk level (safe, confirm_needed, protected)
3. By age (recent, mid-term, old)
"""
def __init__(self, config: Optional[Config] = None):
"""
Initialize classifier.
Args:
config: Configuration object.
"""
self.config = config or Config.load()
# Load protected patterns and extensions
self.protected_patterns = self.config.protected_patterns
self.protected_extensions = self.config.protected_extensions
self.protected_paths = self.config.protected_paths
# Load custom rules
self.custom_rules = self.config.get("rules", [])
# Define type categories
self.type_categories = {
"临时/构建产物": [
"*.tmp",
"*.temp",
"*.cache",
"__pycache__",
"node_modules",
".pytest_cache",
".mypy_cache",
"*.pyc",
"*.pyo",
],
"日志文件": ["*.log"],
"缓存文件": [
"*.cache",
".cache",
"Thumbs.db",
".DS_Store",
],
"备份文件": [
"*.bak",
"*.backup",
"*~",
"*.old",
],
"下载文件": None, # Special handling by path
"媒体文件": [
"*.mp4",
"*.mkv",
"*.avi",
"*.mov",
"*.mp3",
"*.flac",
"*.jpg",
"*.jpeg",
"*.png",
"*.gif",
"*.bmp",
],
"文档文件": [
"*.pdf",
"*.doc",
"*.docx",
"*.xls",
"*.xlsx",
"*.ppt",
"*.pptx",
"*.odt",
],
"压缩文件": [
"*.zip",
"*.tar",
"*.gz",
"*.rar",
"*.7z",
],
}
def classify(
self,
files: List[FileInfo],
) -> Dict[str, Dict[str, List[FileInfo]]]:
"""
Classify files by type, risk, and age.
Args:
files: List of FileInfo objects to classify.
Returns:
Dictionary with three classification dimensions:
{
"by_type": {category_name: [files]},
"by_risk": {risk_level: [files]},
"by_age": {age_group: [files]}
}
"""
result = {
"by_type": {},
"by_risk": {
RiskLevel.SAFE.value: [],
RiskLevel.CONFIRM_NEEDED.value: [],
RiskLevel.PROTECTED.value: [],
},
"by_age": {
"最近创建 (7天内)": [],
"近期文件 (30天内)": [],
"陈旧文件 (90天内)": [],
"很旧 (90天以上)": [],
},
}
for file in files:
# Skip directories
if file.is_dir:
continue
# Classify by type
type_category = self._classify_type(file)
if type_category not in result["by_type"]:
result["by_type"][type_category] = []
result["by_type"][type_category].append(file)
# Classify by risk
risk_level = self._classify_risk(file)
result["by_risk"][risk_level.value].append(file)
# Classify by age
age_group = self._classify_age(file)
result["by_age"][age_group].append(file)
return result
def _classify_type(self, file: FileInfo) -> str:
"""
Classify file by type.
Args:
file: FileInfo object.
Returns:
Type category name.
"""
# Check custom rules first
for rule in self.custom_rules:
pattern = rule.get("pattern", "")
if self._matches_pattern(file, pattern):
return rule.get("category", "其他文件")
# Check built-in categories
for category, patterns in self.type_categories.items():
if patterns is None:
continue
for pattern in patterns:
if self._matches_pattern(file, pattern):
return category
# Special handling for downloads
if "downloads" in file.path.lower():
return "下载文件"
return "其他文件"
def _classify_risk(self, file: FileInfo) -> RiskLevel:
"""
Classify file by risk level.
Args:
file: FileInfo object.
Returns:
RiskLevel enum value.
"""
# Check if path is protected
for protected_path in self.protected_paths:
if file.path.startswith(protected_path):
return RiskLevel.PROTECTED
# Check if extension is protected
for ext in self.protected_extensions:
if file.name.lower().endswith(ext.lower()):
return RiskLevel.PROTECTED
# Check if pattern matches protected patterns
for pattern in self.protected_patterns:
if fnmatch.fnmatch(file.name, pattern):
return RiskLevel.PROTECTED
# Classify by file type
file_type = self._classify_type(file)
# Safe to delete categories
safe_categories = ["临时/构建产物", "日志文件", "缓存文件"]
if file_type in safe_categories:
return RiskLevel.SAFE
# Confirm needed for user data
confirm_categories = ["下载文件", "媒体文件", "文档文件"]
if file_type in confirm_categories:
return RiskLevel.CONFIRM_NEEDED
# Default to confirm needed
return RiskLevel.CONFIRM_NEEDED
def _classify_age(self, file: FileInfo) -> str:
"""
Classify file by age.
Args:
file: FileInfo object.
Returns:
Age group name.
"""
now = datetime.now()
file_time = datetime.fromtimestamp(file.mtime)
age = now - file_time
if age < timedelta(days=7):
return "最近创建 (7天内)"
elif age < timedelta(days=30):
return "近期文件 (30天内)"
elif age < timedelta(days=90):
return "陈旧文件 (90天内)"
else:
return "很旧 (90天以上)"
def _matches_pattern(self, file: FileInfo, pattern: str) -> bool:
"""
Check if file matches a pattern.
Args:
file: FileInfo object.
pattern: Pattern to match (can be * wildcards or directory names).
Returns:
True if file matches pattern.
"""
# Check filename
if fnmatch.fnmatch(file.name, pattern):
return True
# Check path components
path_parts = Path(file.path).parts
for part in path_parts:
if fnmatch.fnmatch(part, pattern):
return True
# Check full path
if fnmatch.fnmatch(file.path, pattern):
return True
# Check if pattern is a directory name in path
if pattern.rstrip("/") in path_parts:
return True
return False
def get_type_stats(
self,
files: List[FileInfo],
) -> Dict[str, Dict[str, int]]:
"""
Get statistics by file type.
Args:
files: List of FileInfo objects.
Returns:
Dictionary with stats for each type:
{
"type_name": {
"count": 10,
"total_size": 1024000
}
}
"""
classification = self.classify(files)
stats = {}
for type_name, type_files in classification["by_type"].items():
stats[type_name] = {
"count": len(type_files),
"total_size": sum(f.size for f in type_files),
}
return stats
def get_risk_stats(
self,
files: List[FileInfo],
) -> Dict[str, Dict[str, int]]:
"""
Get statistics by risk level.
Args:
files: List of FileInfo objects.
Returns:
Dictionary with stats for each risk level.
"""
classification = self.classify(files)
stats = {}
for risk_level, risk_files in classification["by_risk"].items():
stats[risk_level] = {
"count": len(risk_files),
"total_size": sum(f.size for f in risk_files),
}
return stats
"""
Duplicate file detector with adaptive strategy.
Detects duplicate files using two strategies:
- Fast: Size + mtime pre-filtering for large directories
- Accurate: SHA-256 hash comparison for smaller directories
"""
import hashlib
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from diskcleaner.core.scanner import FileInfo
@dataclass
class DuplicateGroup:
"""A group of duplicate files."""
files: List[FileInfo]
size: int
hash_value: Optional[str] = None
@property
def count(self) -> int:
"""Number of duplicate files."""
return len(self.files)
@property
def reclaimable_space(self) -> int:
"""Space that can be reclaimed (keeping one copy)."""
return self.size * (self.count - 1)
class DuplicateFinder:
"""
Duplicate file detector with adaptive strategy.
Features:
- Adaptive strategy selection based on file count
- Fast strategy: size + mtime pre-filtering
- Accurate strategy: SHA-256 hash comparison
- Sorted by reclaimable space (descending)
"""
# Threshold for switching strategies
ADAPTIVE_THRESHOLD = 1000
def __init__(self, strategy: str = "adaptive", fast_similarity_threshold: float = 0.95):
"""
Initialize duplicate finder.
Args:
strategy: Detection strategy ('adaptive', 'fast', 'accurate')
fast_similarity_threshold: Min similarity ratio for fast strategy (0-1)
"""
if strategy not in ("adaptive", "fast", "accurate"):
raise ValueError(f"Invalid strategy: {strategy}")
self.strategy = strategy
self.fast_similarity_threshold = fast_similarity_threshold
def find_duplicates(self, files: List[FileInfo]) -> List[DuplicateGroup]:
"""
Find duplicate files in the given list.
Args:
files: List of FileInfo objects to check.
Returns:
List of DuplicateGroup objects, sorted by reclaimable space.
"""
if not files:
return []
# Filter out directories (only check files)
file_list = [f for f in files if not f.is_dir]
if not file_list:
return []
# Determine which strategy to use
use_accurate = self._should_use_accurate(len(file_list))
# Find duplicates
if use_accurate:
duplicates = self._find_by_hash(file_list)
else:
duplicates = self._find_by_fast_strategy(file_list)
# Sort by reclaimable space (descending)
duplicates.sort(key=lambda d: d.reclaimable_space, reverse=True)
return duplicates
def _should_use_accurate(self, file_count: int) -> bool:
"""
Determine whether to use accurate strategy.
Args:
file_count: Number of files to check.
Returns:
True if accurate strategy should be used.
"""
if self.strategy == "accurate":
return True
if self.strategy == "fast":
return False
# Adaptive: use accurate for small directories
return file_count < self.ADAPTIVE_THRESHOLD
def _find_by_hash(self, files: List[FileInfo]) -> List[DuplicateGroup]:
"""
Find duplicates using SHA-256 hash comparison.
This is the most accurate method but slower for large directories.
Args:
files: List of FileInfo objects.
Returns:
List of DuplicateGroup objects.
"""
# Group files by hash
hash_groups: Dict[str, List[FileInfo]] = {}
for file_info in files:
try:
# Calculate file hash
file_hash = self._calculate_hash(file_info.path)
if file_hash not in hash_groups:
hash_groups[file_hash] = []
hash_groups[file_hash].append(file_info)
except (OSError, IOError):
# Skip files we can't read
continue
# Create duplicate groups (only groups with 2+ files)
duplicates = []
for hash_value, file_list in hash_groups.items():
if len(file_list) > 1:
# All files in this group have the same size
size = file_list[0].size
duplicates.append(DuplicateGroup(files=file_list, size=size, hash_value=hash_value))
return duplicates
def _find_by_fast_strategy(self, files: List[FileInfo]) -> List[DuplicateGroup]:
"""
Find duplicates using fast size + mtime strategy.
Strategy:
1. Group by exact size (primary filter)
2. Within size groups, check mtime similarity
3. Hash verification only for likely duplicates
This is much faster for large directories but may miss some duplicates
or have false positives.
Args:
files: List of FileInfo objects.
Returns:
List of DuplicateGroup objects.
"""
# Step 1: Group by exact size
size_groups: Dict[int, List[FileInfo]] = {}
for file_info in files:
if file_info.size not in size_groups:
size_groups[file_info.size] = []
size_groups[file_info.size].append(file_info)
# Step 2: For each size group with 2+ files, check mtime similarity
potential_duplicates: List[Tuple[int, List[FileInfo]]] = []
for size, file_list in size_groups.items():
if len(file_list) < 2:
continue
# Check if files have similar mtimes (within threshold)
# Group by mtime buckets (1-second granularity)
mtime_groups: Dict[int, List[FileInfo]] = {}
for file_info in file_list:
mtime_bucket = int(file_info.mtime)
if mtime_bucket not in mtime_groups:
mtime_groups[mtime_bucket] = []
mtime_groups[mtime_bucket].append(file_info)
# Find files in same mtime bucket (likely duplicates)
for mtime_bucket, same_time_files in mtime_groups.items():
if len(same_time_files) >= 2:
potential_duplicates.append((size, same_time_files))
# Step 3: Hash verification for potential duplicates
# (to avoid false positives from fast strategy)
verified_duplicates: List[DuplicateGroup] = []
for size, file_list in potential_duplicates:
# Calculate hashes for these files
hash_groups: Dict[str, List[FileInfo]] = {}
for file_info in file_list:
try:
file_hash = self._calculate_hash(file_info.path)
if file_hash not in hash_groups:
hash_groups[file_hash] = []
hash_groups[file_hash].append(file_info)
except (OSError, IOError):
continue
# Add verified duplicate groups
for hash_value, same_hash_files in hash_groups.items():
if len(same_hash_files) >= 2:
verified_duplicates.append(
DuplicateGroup(files=same_hash_files, size=size, hash_value=hash_value)
)
return verified_duplicates
def _calculate_hash(self, file_path: str, chunk_size: int = 8192) -> str:
"""
Calculate SHA-256 hash of a file.
Args:
file_path: Path to file.
chunk_size: Read chunk size in bytes.
Returns:
Hexadecimal hash string.
"""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
# Read file in chunks to handle large files
while chunk := f.read(chunk_size):
sha256_hash.update(chunk)
return sha256_hash.hexdigest()
def get_duplicate_stats(self, duplicates: List[DuplicateGroup]) -> Dict[str, int]:
"""
Calculate statistics about duplicate files.
Args:
duplicates: List of DuplicateGroup objects.
Returns:
Dictionary with statistics:
{
"group_count": number of duplicate groups,
"file_count": total duplicate files,
"total_size": total size of all duplicates,
"reclaimable": space that can be reclaimed
}
"""
if not duplicates:
return {
"group_count": 0,
"file_count": 0,
"total_size": 0,
"reclaimable": 0,
}
total_files = sum(d.count for d in duplicates)
total_size = sum(d.size * d.count for d in duplicates)
total_reclaimable = sum(d.reclaimable_space for d in duplicates)
return {
"group_count": len(duplicates),
"file_count": total_files,
"total_size": total_size,
"reclaimable": total_reclaimable,
}
"""
Disk Growth Analyzer - Track and analyze disk space growth trends
This module provides functionality to:
1. Store historical disk usage data in JSON format
2. Calculate growth rates and trends
3. Predict future disk usage
4. Generate growth reports
Features:
- JSON-based persistent storage
- Cross-platform compatibility
- ASCII-safe output
- Trend analysis and prediction
"""
import json
import os
import platform
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Tuple
class GrowthAnalyzer:
"""
Analyze disk space growth trends over time.
Features:
- Store historical disk usage data
- Calculate growth rates (daily, weekly, monthly)
- Predict when disk will be full
- Identify unusual growth patterns
"""
def __init__(self, data_file: str = None):
"""
Initialize growth analyzer.
Args:
data_file: Path to JSON file for storing historical data.
If None, uses default location in user home directory.
"""
if data_file is None:
# Default: store in user's home directory
home_dir = Path.home()
data_dir = home_dir / ".diskcleaner"
data_dir.mkdir(exist_ok=True)
data_file = str(data_dir / "growth_history.json")
self.data_file = Path(data_file)
self.data_dir = self.data_file.parent
# Create data directory if it doesn't exist
self.data_dir.mkdir(parents=True, exist_ok=True)
# Load existing data
self.history = self._load_history()
def _load_history(self) -> Dict:
"""
Load historical data from JSON file.
Returns:
Dictionary with historical data
"""
if not self.data_file.exists():
return {"snapshots": []}
try:
with open(self.data_file, "r", encoding="utf-8") as f:
data = json.load(f)
# Validate data structure
if not isinstance(data, dict) or "snapshots" not in data:
print(f"[!] Invalid data file format, creating new one", file=sys.stderr)
return {"snapshots": []}
return data
except (json.JSONDecodeError, IOError) as e:
print(f"[!] Error loading history: {e}", file=sys.stderr)
return {"snapshots": []}
def _save_history(self) -> bool:
"""
Save historical data to JSON file.
Returns:
True if successful, False otherwise
"""
try:
# Create backup of existing file
if self.data_file.exists():
backup_file = self.data_file.with_suffix(".bak")
import shutil
shutil.copy2(self.data_file, backup_file)
# Save new data
with open(self.data_file, "w", encoding="utf-8") as f:
json.dump(self.history, f, indent=2)
return True
except (IOError, OSError) as e:
print(f"[!] Error saving history: {e}", file=sys.stderr)
return False
def add_snapshot(
self,
path: str,
used_bytes: int,
total_bytes: int,
free_bytes: int,
metadata: Dict = None,
) -> bool:
"""
Add a new disk usage snapshot.
Args:
path: Disk path or mount point
used_bytes: Used space in bytes
total_bytes: Total space in bytes
free_bytes: Free space in bytes
metadata: Optional metadata (e.g., scan time, file counts)
Returns:
True if snapshot was added successfully
"""
snapshot = {
"timestamp": datetime.now().isoformat(),
"path": path,
"used_bytes": used_bytes,
"total_bytes": total_bytes,
"free_bytes": free_bytes,
"used_percent": round((used_bytes / total_bytes * 100), 2) if total_bytes > 0 else 0,
"platform": platform.system(),
}
# Add optional metadata
if metadata:
snapshot["metadata"] = metadata
# Add to history
self.history["snapshots"].append(snapshot)
# Sort by timestamp
self.history["snapshots"].sort(key=lambda x: x["timestamp"])
# Limit history to last 1000 snapshots to prevent file from growing too large
if len(self.history["snapshots"]) > 1000:
self.history["snapshots"] = self.history["snapshots"][-1000:]
# Save to disk
return self._save_history()
def get_snapshots(
self,
path: str = None,
days: int = None,
limit: int = None,
) -> List[Dict]:
"""
Get snapshots from history.
Args:
path: Filter by path (if None, returns all)
days: Only return snapshots from last N days
limit: Maximum number of snapshots to return
Returns:
List of snapshot dictionaries
"""
snapshots = self.history["snapshots"]
# Filter by path
if path:
snapshots = [s for s in snapshots if s["path"] == path]
# Filter by time
if days:
cutoff_time = datetime.now() - timedelta(days=days)
snapshots = [
s for s in snapshots if datetime.fromisoformat(s["timestamp"]) >= cutoff_time
]
# Limit results
if limit:
snapshots = snapshots[-limit:]
return snapshots
def calculate_growth_rate(
self,
path: str = None,
period: str = "daily",
) -> Dict:
"""
Calculate growth rate for a specific period.
Args:
path: Disk path to analyze (if None, uses most common path)
period: Time period ('daily', 'weekly', 'monthly')
Returns:
Dictionary with growth rate statistics
"""
snapshots = self.get_snapshots(path=path, days=365) # Last year
if len(snapshots) < 2:
return {
"error": "Not enough data points (need at least 2 snapshots)",
"snapshots_count": len(snapshots),
}
# Use most common path if not specified
if path is None:
path_counts = {}
for s in snapshots:
path_counts[s["path"]] = path_counts.get(s["path"], 0) + 1
path = max(path_counts.keys(), key=lambda k: path_counts[k])
snapshots = [s for s in snapshots if s["path"] == path]
# Calculate time differences and growth
growth_data = []
for i in range(1, len(snapshots)):
current = snapshots[i]
previous = snapshots[i - 1]
current_time = datetime.fromisoformat(current["timestamp"])
previous_time = datetime.fromisoformat(previous["timestamp"])
time_diff = (current_time - previous_time).total_seconds() # seconds
used_diff = current["used_bytes"] - previous["used_bytes"]
if time_diff > 0:
# Growth rate in bytes per second
growth_rate = used_diff / time_diff
growth_data.append(
{
"timestamp": current["timestamp"],
"time_diff_seconds": time_diff,
"used_diff_bytes": used_diff,
"growth_rate_per_second": growth_rate,
}
)
if not growth_data:
return {
"error": "No growth data available",
"snapshots_count": len(snapshots),
}
# Calculate average growth rate for the period
avg_growth_per_second = sum(g["growth_rate_per_second"] for g in growth_data) / len(
growth_data
)
# Convert to requested period
seconds_per_period = {
"daily": 86400,
"weekly": 604800,
"monthly": 2592000, # 30 days
}
period_seconds = seconds_per_period.get(period, 86400)
avg_growth_per_period = avg_growth_per_second * period_seconds
# Calculate statistics
growth_rates = [g["growth_rate_per_second"] for g in growth_data]
# Find min/max growth rates
min_growth = min(growth_rates)
max_growth = max(growth_rates)
# Calculate trend (acceleration/deceleration)
if len(growth_data) >= 3:
recent_rate = growth_data[-1]["growth_rate_per_second"]
earlier_rate = growth_data[0]["growth_rate_per_second"]
acceleration = recent_rate - earlier_rate
else:
acceleration = 0
return {
"path": path,
"period": period,
"snapshots_analyzed": len(snapshots),
"growth_data_points": len(growth_data),
"avg_growth_bytes_per_period": round(avg_growth_per_period, 2),
"avg_growth_mb_per_period": round(avg_growth_per_period / (1024**2), 2),
"avg_growth_gb_per_period": round(avg_growth_per_period / (1024**3), 2),
"min_growth_bytes_per_period": round(min_growth * period_seconds, 2),
"max_growth_bytes_per_period": round(max_growth * period_seconds, 2),
"acceleration_bytes_per_second_sq": round(acceleration, 2),
"trend": (
"accelerating"
if acceleration > 0
else "decelerating" if acceleration < 0 else "stable"
),
}
def predict_full_date(self, path: str = None) -> Dict:
"""
Predict when the disk will be full based on growth rate.
Args:
path: Disk path to analyze
Returns:
Dictionary with prediction information
"""
snapshots = self.get_snapshots(path=path, days=365)
if len(snapshots) < 2:
return {
"error": "Not enough data for prediction (need at least 2 snapshots)",
}
# Get latest snapshot
latest = snapshots[-1]
# Calculate growth rate
growth_rate = self.calculate_growth_rate(path=path, period="daily")
if "error" in growth_rate:
return growth_rate
# Calculate days until full
free_bytes = latest["free_bytes"]
daily_growth = growth_rate["avg_growth_bytes_per_period"]
if daily_growth <= 0:
return {
"error": "No positive growth detected",
"current_usage_percent": latest["used_percent"],
"message": "Disk usage is stable or decreasing",
}
days_until_full = free_bytes / daily_growth
# Calculate full date
full_date = datetime.now() + timedelta(days=days_until_full)
return {
"path": latest["path"],
"current_used_bytes": latest["used_bytes"],
"current_free_bytes": latest["free_bytes"],
"current_total_bytes": latest["total_bytes"],
"current_usage_percent": latest["used_percent"],
"daily_growth_bytes": round(daily_growth, 2),
"daily_growth_mb": round(daily_growth / (1024**2), 2),
"daily_growth_gb": round(daily_growth / (1024**3), 2),
"days_until_full": round(days_until_full, 1),
"predicted_full_date": full_date.isoformat(),
"predicted_full_date_human": full_date.strftime("%Y-%m-%d"),
"weeks_until_full": round(days_until_full / 7, 1),
"months_until_full": round(days_until_full / 30, 1),
}
def generate_report(self, path: str = None) -> Dict:
"""
Generate comprehensive growth analysis report.
Args:
path: Disk path to analyze
Returns:
Dictionary with complete growth analysis
"""
snapshots = self.get_snapshots(path=path)
if not snapshots:
return {
"error": "No historical data available",
"message": "Run disk analysis multiple times over days/weeks to see trends",
}
# Use most recent snapshot path if not specified
if path is None:
latest = snapshots[-1]
path = latest["path"]
# Calculate growth rates for different periods
daily_rate = self.calculate_growth_rate(path=path, period="daily")
weekly_rate = self.calculate_growth_rate(path=path, period="weekly")
monthly_rate = self.calculate_growth_rate(path=path, period="monthly")
# Predict full date
prediction = self.predict_full_date(path=path)
# Compile report
report = {
"analysis_date": datetime.now().isoformat(),
"path": path,
"snapshots_count": len(snapshots),
"date_range": {
"earliest": snapshots[0]["timestamp"],
"latest": snapshots[-1]["timestamp"],
},
"growth_rates": {
"daily": daily_rate,
"weekly": weekly_rate,
"monthly": monthly_rate,
},
"prediction": prediction,
"latest_snapshot": snapshots[-1],
}
return report
def cleanup_old_data(self, days: int = 365) -> int:
"""
Remove snapshots older than specified days.
Args:
days: Keep only snapshots from last N days
Returns:
Number of snapshots removed
"""
cutoff_time = datetime.now() - timedelta(days=days)
original_count = len(self.history["snapshots"])
self.history["snapshots"] = [
s
for s in self.history["snapshots"]
if datetime.fromisoformat(s["timestamp"]) >= cutoff_time
]
removed_count = original_count - len(self.history["snapshots"])
if removed_count > 0:
self._save_history()
return removed_count
def format_size(bytes_size: int) -> str:
"""
Format byte size to human-readable string.
Args:
bytes_size: Size in bytes
Returns:
Formatted string (e.g., "1.5 GB", "500 MB")
"""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_size < 1024.0:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024.0
return f"{bytes_size:.2f} PB"
"""
File organization rules module.
Provides predefined strategies for organizing files
and custom rule engine for flexible categorization.
"""
from .archive_rules import (
ArchiveRule,
ArchiveStrategy,
DesktopStrategy,
DownloadsStrategy,
GeneralStrategy,
ProjectStrategy,
RuleEngine,
)
__all__ = [
"ArchiveRule",
"ArchiveStrategy",
"DesktopStrategy",
"DownloadsStrategy",
"ProjectStrategy",
"GeneralStrategy",
"RuleEngine",
]
"""
Performance optimization module for disk-cleaner.
This module provides intelligent, layered performance optimizations:
- Concurrent scanning and deletion
- Incremental caching
- Adaptive hash computation
- Performance monitoring
All optimizations are pluggable and can be disabled via configuration.
"""
from diskcleaner.optimization.concurrency import ConcurrencyManager
from diskcleaner.optimization.delete import (
AsyncDeleter,
BatchDeleter,
DeleteResult,
DeleteStrategy,
DeletionManager,
ProgressUpdate,
SmartDeleter,
)
from diskcleaner.optimization.hash import (
AdaptiveHasher,
DuplicateFinder,
DuplicateGroup,
FastFilter,
HashCache,
ParallelHasher,
)
from diskcleaner.optimization.memory import MemoryMonitor, MemoryStatus
from diskcleaner.optimization.profiler import PerformanceProfiler, PerformanceReport
from diskcleaner.optimization.scan import (
ConcurrentScanner,
FileInfo,
IncrementalCache,
QuickProfiler,
ScanProfile,
ScanResult,
ScanSnapshot,
ScanStrategy,
)
__all__ = [
# Infrastructure
"PerformanceProfiler",
"PerformanceReport",
"ConcurrencyManager",
"MemoryMonitor",
"MemoryStatus",
# Scanning
"QuickProfiler",
"ConcurrentScanner",
"IncrementalCache",
"ScanStrategy",
"ScanProfile",
"ScanResult",
"ScanSnapshot",
"FileInfo",
# Deletion
"BatchDeleter",
"AsyncDeleter",
"SmartDeleter",
"DeletionManager",
"DeleteStrategy",
"DeleteResult",
"ProgressUpdate",
# Hash
"AdaptiveHasher",
"ParallelHasher",
"FastFilter",
"HashCache",
"DuplicateFinder",
"DuplicateGroup",
]
"""
Platform-specific functionality
"""
from diskcleaner.platforms.linux import LinuxPlatform
from diskcleaner.platforms.macos import MacOSPlatform
from diskcleaner.platforms.windows import WindowsPlatform
__all__ = [
"WindowsPlatform",
"LinuxPlatform",
"MacOSPlatform",
]
Disk Cleaner Skill
A high-performance cross-platform disk space monitoring, analysis, and cleaning toolkit for Claude Code.
Quick Start
As Claude Code Skill
Install directly from GitHub:
npx add-skill gccszs/disk-cleanerManual Installation
1. Download disk-cleaner.skill from the Releases page 2. Install via Claude Code: /skill install path/to/disk-cleaner.skill
Features
- Disk Space Analysis: Identify large files and directories consuming space
- Smart Cleanup: Safe removal of temporary files, caches, logs
- Duplicate Detection: Find and remove duplicate files
- Disk Monitoring: Real-time monitoring with alert thresholds
- Cross-Platform: Windows, Linux, macOS support
- 3-5x Faster: Optimized scanning with os.scandir()
Usage
Analyze Disk Space
python scripts/analyze_disk.py --top 50Clean Junk Files (Preview)
python scripts/clean_disk.py --dry-runMonitor Disk Usage
python scripts/monitor_disk.py --watchSkill Structure
disk-cleaner/
├── SKILL.md # Skill definition
├── README.md # This file
├── diskcleaner/ # Core Python module
├── scripts/ # Executable scripts
└── references/ # Reference documentationDocumentation
- Full Skill Documentation
- Project Repository
License
MIT License
Related skills
How it compares
Choose disk-cleaner over manual `rm -rf` scripts when you want progressive large-disk scanning and IDE-aware cleanup guidance.
FAQ
Does disk-cleaner require pip install?
disk-cleaner is self-contained and needs no pip install. The skill bundles an intelligent bootstrap that auto-detects its location and imports modules, requiring only Python 3.7 or newer.
Which AI coding agents support disk-cleaner?
disk-cleaner advertises universal compatibility with Cursor, Windsurf, Continue, Aider, Claude Code, and similar AI IDEs, and can run at global, project, or user scope.