Repository navigation
Releases: DuongNAD/smart-drive-os
Release list
SmartDrive-OS 1.4.2 — on PyPI, with safer offload, clean and path guesses
Safety and packaging fixes from the product-readiness review of 1.4.1: a list of running programs that cannot be read no longer counts as "nothing is running", purging the quarantine keeps the recent batches, Tier 3 junk can be quarantined on its own, paths --broken no longer takes a folder inside installed packages or a folder that only has the right name for a moved one, and the package is on PyPI: run it with uvx or pipx and register the MCP server from either; a server.json is ready for the official MCP Registry. The shipped folder lists no longer carry one user's folder name. #15 is the pull request of this release.
This is the first release on PyPI. Install it with pipx install smart-drive-os (or run it with uvx smart-drive-os <subcommand>), then add the MCP server to your coding agents with smart-drive mcp register --all. The wheel and the sdist were built by the new Release workflow, smoke-tested from the built wheel on Windows, macOS and Linux with Python 3.9 and 3.13 (a fresh virtual environment, uvx and pipx), and uploaded through Trusted Publishing after the owner approved the deployment; both files carry PEP 740 attestations. The full suite passed with 1950 tests on Windows, and CI passed on all three systems.
Added
clean --only-tier Nlists and applies a single junk tier.--tier Nstill means tiers 1 to N, soclean --tier 3 --applyalso deletes every Tier 1-2 item permanently;clean --only-tier 3 --applymoves only the Tier 3 junk (temp files, swap files, crash dumps) to the restorable quarantine and deletes nothing else.--only-tiercannot be combined with--tier, its dry run says what--applywill do, and--jsonhasonly_tier. The READMEs andAGENTS.md,CLAUDE.mdandGEMINI.md(and the copiesinitwrites) mention it.- A release workflow,
.github/workflows/release.yml. Pushing a tagv<version>builds the source distribution and the wheel, checks them withtwine check --strict(and that the tag is the version inpyproject.toml), installs the wheel in a fresh virtual environment on Linux, macOS and Windows with Python 3.9 and 3.13 and smoke-tests it (packaging/smoke_test.py:status --json,organize --dry-run, and starting the MCP server thatmcp registerwrote; also run throughuvxandpipx), and then uploads both files to PyPI through Trusted Publishing (OpenID Connect with attestations; no token is stored anywhere), but only after the owner approves the deployment in the protectedpypienvironment. Branch pushes and pull requests that touch the packaging files, and manual runs, only build and test. The one-time PyPI and GitHub setup, the steps of a release and what to do when one goes wrong are indocs/RELEASING.md. uvx smart-drive-os <subcommand>andpipx run smart-drive-os <subcommand>work: the package now installs a console script named like itself,smart-drive-os(smart-driveand the other names stay).- README install options (
uvx,pipxand a source checkout; the first two once the first release is on PyPI) and ready-to-paste MCP entries for Claude Desktop, Cursor, Antigravity, Windsurf and a project's.mcp.json, for Claude Code and for Codex. server.jsonfor the official MCP Registry (io.github.DuongNAD/smart-drive-os, the PyPI packagesmart-drive-os, started withuvx smart-drive-os mcp) and themcp-namecomment inREADME.mdthat the registry looks for in the package description. Nothing is published to the registry yet;docs/RELEASING.mdhas the steps.
Changed
clean --purge-quarantinekeeps the quarantine batches younger than 30 days unless--older-thansays otherwise. It used to default to--older-than 0, soclean --purge-quarantine --applypermanently deleted every batch at once, the one made a minute ago included, although the hint printed after a quarantine says--older-than 30. Purging every batch now takes an explicit--older-than 0, a negative age is refused with exit code 2 before anything is touched, and the text report says how many newer batches were kept.purge_quarantine_batches()defaults to the same 30 days (QUARANTINE_RETENTION_DAYS).- The package is classified as
Development Status :: 4 - Betainstead of5 - Production/Stable, andREADME_INTERNAL.mdshows the live CI badge instead of a static "968/968 tests passed" one. - Package metadata: the description says what the tool mainly does (organize a drive and relink the paths coding agents and IDEs remember, clean junk safely, search files instantly), the license is the SPDX expression
MIT(PEP 639; building from source now needs setuptools 77 or newer, which pip fetches by itself), and the emptysetup.pyshim is gone (pip 21.3 and newer build frompyproject.tomlalone).
Removed
DBI202_VuPT\MSSQL16.MSSQLSERVER(in both spellings), the name of one user's own course folder, from the protected and the scan-excluded folder lists. The genericMSSQL16.MSSQLSERVERentries already protect and skip a SQL Server instance folder under any parent, so nothing is less protected.32837andfo4stay, now with a comment: they are the install folders of FC Online (Garena's app ID and the game's short name), generic for players in Vietnam. There is still no list of your own folders to protect; the READMEs now say what keeps one in place:organizeonly moves what lies directly in the drive root, and on Windows a folder that a shortcut on the Desktop, in the Start Menu or on the taskbar points to isprotected_by_system.
Fixed
paths --brokenandrelink --findno longer take a folder inside installed packages for a moved one. Looking for a missing folder by its name one and two levels below its parent, they went intonode_modules,site-packages,vendorand the like, so the stalePATHfolderC:\Users\<you>\binwas reported as moved toC:\Users\<you>\node_modules\playwright-core\bin. The search now neither enters nor returns a folder namednode_modules,bower_components,jspm_packages,.pnpm-store,site-packages,dist-packages,__pypackages__,.venv,venvorvendor, unless the missing folder itself lay inside one.paths --brokenno longer counts one folder of the same name as a sure match when nothing below the missing folder can confirm it, as for aPATHfolder or a shortcut to a folder. Such a shortcut was marked ✓, andpaths --broken --fixrelinked the old folder to the match, and with it every agent and IDE path below the old folder, although the match may be another program's folder and the old one deleted; such aPATHfolder was reported as "moved to" the match. These matches now have the kindname-only(also in--json), are shown with?and "maybe moved to" next to thesmart-drive relink "OLD" "NEW"command to run if you did move the folder there, and--fixleaves them alone, asrelink --find --applydoes. A folder that holds what the shortcut orPATHentry pointed to inside the old one is stillsame-name. Agent and IDE entries matched by their name alone, which the report listed as "not found", are shown the same way.relink --dry-runno longer asks to close coding agents the run does not touch, andrelinkasks to restart them only after it changed one of their config files. The dry run listed every running agent under "Close these before --apply", and the real run ended with "Restart the coding agents that are open...", also when all it changed were a virtual environment's console or activation scripts, editable installs, Windows shortcuts or git worktrees, which no running agent holds. The hint now names only the running agents whose own config files would change (the ones the real run warns about) and reads "Close these before running it without --dry-run" (relinkapplies by default;--applyonly goes with--find), and the restart lines appear only when an agent's config file was rewritten.organize,undo,classifyandpathsprint what they printed before, andbusy_appsin the--jsonoforganizeandundostill lists every running agent.offload --moveandoffload --revertno longer move a cache while the list of running programs cannot be read. Whentasklistorpsfailed or passed its 10 s timeout, the list came back empty, so Docker Desktop's WSL data, Ollama's models or Cargo's registry could be copied and relinked while the program using them was open. Such a cache is now refused with a message saying that the list could not be read:--waitkeeps checking until the list can be read and the program is closed (still unreadable at the deadline: exit code 3), and--forcemoves it anyway with a warning. npm and Gradle caches, which only warn while node or java runs, warn that this could not be checked. A dry run says it would be blocked and hasprocess_list_readablein--json.organize,undoand the guidedSmart_Organizeflow say when they could not check which coding agents and IDEs are open, instead of reportingbusy_apps: []as if none were. Next to everybusy_appstheir--jsonoutput now hasbusy_apps_unknown, the text dry run asks to quit open coding agents and IDEs before--apply, andSmart_Organizestops at step 2 and asks for them to be closed (Enter checks again,Ccontinues,Qcancels) instead of moving at once.--applyitself is unchanged, andrelinkalready counted an unreadable list as "still running".mcp registerandsetuprun throughuvxorpipx runwrote the interpreter of the temporary environment the package runs from (uv's cache, or pipx's<PIPX_HOME>/.cache/<hash>, which pipx deletes after 14 days without use) into every agent config, which broke the server whenever that environment was cleaned up. They now registeruvx smart-drive-os mcpandpipx run smart-drive-os mcpinstead (with the absolute path ofuvxorpipxwhen it is known). Apipx install, `uv ...
SmartDrive-OS 1.4.1 — refused moves explained, moved venvs repointed
A patch release for what the first moves on a real drive turned up: a refused move now says what holds the folder, relink reports only what it changed, and a moved virtual environment works again without a reinstall. #14 is the pull request of this release.
These fixes come from moving E:\project, a folder of 17 git repositories, on the same 167 GB NTFS drive as 1.4.0: Windows refused the move because Explorer's Quick Access held a folder deep inside it open, and the moved virtual environments' launchers and activation scripts still named the old folder. With this release, relink repointed the four virtual environments moved on that drive (76 console scripts and 12 activation scripts): every launcher names an interpreter that exists, pip --version runs in each of them, and a second relink --dry-run ends with "Nothing left to change". CI passes on Windows, macOS and Linux with Python 3.9 and 3.13.
Added
relinknow repoints the console scripts of a moved virtual environment instead of only warning about them: the interpreter path in the shebang line of each pip/distlib launcher (pip.exe,fastapi.exe, ... on Windows; the zip archive in them stays byte for byte the same and the result is checked to open and read back) and in the shebang of text scripts inScripts/andbin/(including distlib's#!/bin/shform) follows the move, with a backup<file>.bak.smart-driveof every file it rewrites. This also covers virtual environments that hold no.pthfile.--jsonhasconsole_scriptsper venv. A launcher it cannot parse or rewrite safely, such as one made by uv, is left untouched and is the only thing that still gets the console-scripts warning.relinkalso repoints the activation scripts of a moved virtual environment.activate,activate.bat,activate.fish,activate.csh,activate.nuand the like hold the venv's path as text, so after a move activating the venv put its old folder onPATH. relink writes the new path into them, replacing only whole occurrences of the old venv path (also its forward-slash, doubled-backslash, Git Bash/e/...and Cygwin/cygdrive/e/...spellings) and keeping line endings, encoding and every other byte, with a.bak.smart-drivebackup of each rewritten script. The venv line readsPython venv updated: <venv> (15 console scripts and 3 activation scripts repointed), and--jsonhasactivation_scriptsper venv.
Fixed
- A move that Windows refuses (
[WinError 5] Access is denied) now names the folder that is in use.organize,undoandclassifylisted the usual suspects and asked the Restart Manager about the files directly in the folder, so a handle on a nested folder (E:\projectcould not be moved because...\datathon-2026-round-1\Nop_bai\Imageswas open) was named by nothing. NTFS refuses to rename a folder while anything below it is open, so after a refused move on Windows a read-only probe (8 levels, 20,000 entries, 5 seconds; it renames nothing and keeps nothing open) tries to open every folder and file exclusively and reports the deepest entries that are in use. The hint saysin use: E:\project\...\Images, asks the Restart Manager about the files that were found open, and when no program can be named it namesexplorer.exe(an open Explorer window or Quick Access) as a common holder and says how to find a system service or an elevated program: Resource Monitor as administrator (resmon) → CPU → Associated Handles, or a restart. The paths also come back asin_usein the--jsonoutput oforganize,undoandclassifyand in the errors of the MCP toolssd_auto_organize. - A folder pinned in Explorer's Quick Access keeps every folder above it from being moved: Explorer holds a change-notification handle on each Quick Access folder and opens it again at every logon, so unpinning alone does not release it and neither does restarting Windows while the folder is still pinned. When something is in use below the folder being moved, the hint now asks Windows for the Quick Access list (one short PowerShell call, 5 seconds, skipped silently when it fails) and, if a folder inside is listed, says so: unpin it (right-click → Unpin from Quick access), restart Windows Explorer (Task Manager → Windows Explorer → Restart) and run the command again.
relinkno longer claims work it did not do. Runningsmart-drive relinkagain for a move that was already relinked (menu[11]) listed every repository under "git worktrees repaired" and every virtual environment under "Python venv updated" with a console-scripts warning each, although the dry run said "Nothing left to change" and nothing had changed:git worktree repairexits 0 whether or not it repaired anything, and the venv check was always true in a real run. A repository is now listed as repaired only when git printed arepair:line or the.git/worktrees/*/gitdirand.gitfiles it rewrites changed, and a virtual environment only when one of its files (.pth,.egg-link, editable finder, console script, activation script) was rewritten, or in a dry run would be. A secondrelinkof the same move now ends with "Nothing left to change",--jsonreports emptyworktreesandvenvs, andrelink --dry-runno longer adds "Close these before --apply" when it has nothing to apply.
Full changelog: CHANGELOG.md · plan for what comes next: docs/UPGRADE_PLAN.md (section 6)
SmartDrive-OS 1.4.0 — safe for everyday users
The safety release for everyday users. Commands that delete or move things keep away from what only looks like clutter (installed apps, dependency trees, files git tracks, folders Windows depends on), the folders that do move are followed by the coding agents, IDEs and shortcuts that remember them, and the slowest commands no longer need many minutes on a large drive (the measured times are under Changed). #10, #11, #12 and #13 are the pull requests of this release.
Everything below was also run for real on a 167 GB NTFS drive with 342,000 files: a sandbox apply/undo test (42/42 checks), a real organize --apply that moved a folder of 17 git repositories and relinked 13 IDE/agent config files followed by undo --apply (19/19 checks, no new broken references), the same round trip with a git fsmonitor daemon running, and the guided Smart_Organize launcher. CI passes on Windows, macOS and Linux with Python 3.9 and 3.13.
Added
- Launcher menu option
[11]"Relink Coding Agents to the Latest Move" inSmartDrive.bat,.ps1,.shand.command. It showssmart-drive relink --dry-run, asks[y/N], and only then runssmart-drive relink --wait, which re-applies the latestorganize/classifybatch once the open coding agents have closed. Use it afterorganize --applywas run from inside a coding agent and that agent has quit: a running agent can write its old paths back. Option[8](relink --find) stays for folders moved by hand.AGENTS.md,CLAUDE.mdandGEMINI.md, and the copiesinitwrites to a drive, tell coding agents the same. .gitattributespins the line endings of the launchers:.shand.commandare LF,.bat,.cmdand.ps1are CRLF, on every system. A Windows checkout no longer gives the macOS and Linux launchers CRLF ("bad interpreter"), and a ZIP of the repository keeps CRLF in the batch files.dupgains--min-size BYTES, progress lines on stderr about every 2 seconds (stdout stays valid JSON), and a plan that counts what was scanned and what was skipped (folders_scanned,files_scanned,skipped_dependency_dirs,skipped_protected_dirs,elapsed_seconds).cleansays what it left alone although it looks like junk, and why (inside a dependency tree, used by the system, not at a project root, tracked by git): a "Kept N look-alike item(s) that are not junk" block in the text report, andkeptandprotected_by_systemin--json.setupasks which MCP servers to install (none by default; without a terminal it installs nothing and exits with code 4 and aconsentlist that the coding agent puts to the user), and asks before adding a server that is already downloaded to a coding agent. (#10, #12)- Since 1.3.1:
relinkfollows moved folders in Claude Code, Claude desktop, Codex, Gemini CLI, Antigravity, Cursor, Windsurf, VS Code and JetBrains IDEs, Obsidian vaults, Windows.lnkshortcuts, git worktrees and virtual environments, with--find(where did a forgotten folder go),--ask,--waitand--dry-run;undoreverses the latestorganize/classifybatch;cleanquarantines Tier 3 junk so it can be restored;checkupscores the drive 0-100 and lists next steps;paths --brokenlists stale shortcuts, IDE entries, PATH folders and agent paths;audit --htmlwrites an offline treemap; theSmart_Organizelaunchers guide a move step by step; andprotected_by_systemkeepsorganizeandclassifyaway from folders that Windows services, PATH, installed apps, startup entries, shortcuts and game libraries depend on.
Changed
classifylooks only at the files lying directly in the scanned folder;--recursiveopts in to subfolders (--no-recursiveis still accepted). It never sorts the files of a folder that holds a program, library, launcher script, project manifest,.gitor Obsidian vault, of a folder that holds such a program folder, or of a folder the system depends on: that folder is reported as kept whole. This changes the default: a plainclassifyused to plan hundreds of moves that pulled files out of the apps and projects they belong to.clean --applydeletes Tier 1-2 junk permanently and at once; only Tier 3 goes to the restorable quarantine. The dry-run footer,checkup, the READMEs and the agent instructions now say so, andcheckuprecommends the dry run (smart-drive clean --tier 2) before the cleanup instead of jumping to--apply.duphashes up to 8 files at a time, hands the results back in discovery order, and stops promptly on Ctrl-C.dup,checkup --dupand the MCP toolssd_find_duplicateswork out the slack at the volume's own allocation unit (4 KB on NTFS) instead of always 512 KB; 512 KB stays the fallback when the size cannot be read.auditmodels the volume's own cluster size in every output mode instead of always 512 KB, and the docs call exFAT with 512 KB clusters the design target, not a fact about every folder. (#10)checkupscores and recommends only the stale references that point into the drive being checked; the rest stay listed inpaths --brokenas information. (#11)- Speed, measured on a 167 GB NTFS drive with 342,000 files: a
cleandry run took 491 s and takes 8-9 s now, andcheckuptook 571 s and takes 40-65 s now (#11).dupran for more than 15 minutes without any output; it takes 28 s on the first run (Windows Defender scans a file the first time it is opened) and 2-3 s when run again.
Fixed
- Junk rules matched part of a name, so
Photos/buildings_2024counted asbuild,Docs/my_dist_notesasdistandlodash._basecopy/index.jsas._*, and Tier 1-2 junk is deleted without a quarantine. Name rules now match only the name, and rules with a/match the trailing path components. (#10) undoreverses onlyorganizeandclassifybatches, not the folders thatrelinkorpaths --fixjournaled, and items that failed to move back stay undoable instead of being marked as undone. (#10)Smart_Organizeapplies only the plan the user confirmed, a retry only re-runs the relink instead of journaling the batch again, andUundoes only the batch of that run. (#10)initno longer falls back to an ordinary parent folder as the drive root; it asks for--root. (#10)relinkescapes the quote when it rewrites the finder of an editable install, so a project moved intoBob's stuffcan still be imported. (#10)- Folders that something on the computer depends on are found in more cases: shortcuts without a LinkInfo block (60 of 269 on one machine), scripts and DLLs passed as command-line arguments, environment variables such as
JAVA_HOME, an app folder inside a protected top-level folder (classify), and.lnkfiles at the drive root. HoYoPlay and Genshin Impact are protected as the README says. Only runnable files directly in a PATH folder pin that folder's files, so a drive root on PATH no longer freezes every loose PDF. (#10) - The READMEs no longer describe
undo --batch,paths --waitor a checkup letter grade, which never existed. (#10) cleannever flags Tier 2-3 junk inside dependency trees (node_modules,site-packages, virtualenvs,vendor), inside a folder that Windows or an installed app depends on, forbuild,distand.cachefolders that are not at a project root, or when git tracks something at or under it (at most 300 questions to git per scan).- Junk directories are sized by the files inside them instead of 0 bytes, so
checkup,clean --applyandssd_cleanreport the real space. (#11) dupdoes not enter dependency, environment and cache trees (including the caches tagged withCACHEDIR.TAG) or the folders Windows and installed apps depend on, and no longer scans for more than 15 minutes in silence.- The Tier 3 rule for Unix core dumps (
core.*) also matched source and data files such ascore.py,core.jsandcore.css(334 files on the test drive), soclean --tier 3 --applywould have moved them to the quarantine. It now matches onlycoreandcore.<pid>. organize,undoandclassifyfailed with "Access is denied" when a gitfsmonitordaemon (a repository withcore.fsmonitor=true, started by anygit status,ls-filesordiff) held a folder inside the one being moved. They now stop the daemons of the repositories inside, and of their linked worktrees, before renaming; every git command smart-drive runs passes-c core.fsmonitor=false, so it never starts one; dry runs list the daemons that would be stopped; and a refused move says what may hold the folder (on Windows, with the process names the Restart Manager reports).ssd_cleanandssd_find_duplicatescalled with asub_dirinside an installed app or a dependency tree now judge the folders above it the way a scan of the whole drive does instead of scanning it;ssd_cleanreports what it kept, andssd_find_duplicatesdrops files belowmin_sizebefore hashing them.- The MCP tools accept absolute paths when the storage root is a drive root (
E:\or/). Containment is decided on the resolved path, so/data/drive/../xis no longer called safe for the root/data/drive. SmartDrive.batprinted "'Presets' is not recognized as an internal or external command" and cut five menu lines at an unescaped&;SmartDrive.ps1drew the menu again after option[0]instead of exiting.Quick_Audit.batprinted "'Cluster' is not recognized as an internal or external command" and cut its window title and banner at an unescaped&, andQuick_Search.batwrote a file named10MBinto the program folder instead of showing the examplesize:>10MB. No.batlauncher holds such a line any more.- The
.batlaunchers turned delayed expansion on, which makes cmd.exe delete every!in a path or a typed text it has just expanded. A launcher in a folder with a!in its name lost the!from its own path (cdfailed with "The system cannot find the path s...