<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ali Shoaib</title>
    <description>The latest articles on DEV Community by Ali Shoaib (@malishoaib0).</description>
    <link>https://dev.to/malishoaib0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png</url>
      <title>DEV Community: Ali Shoaib</title>
      <link>https://dev.to/malishoaib0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/malishoaib0"/>
    <language>en</language>
    <item>
      <title>Lazy Tidier: I Taught an Open-Weight Model to Clean My Downloads Folder</title>
      <dc:creator>Ali Shoaib</dc:creator>
      <pubDate>Thu, 22 Oct 2026 03:21:31 +0000</pubDate>
      <link>https://dev.to/malishoaib0/lazy-tidier-i-taught-an-open-weight-model-to-clean-my-downloads-folder-4flh</link>
      <guid>https://dev.to/malishoaib0/lazy-tidier-i-taught-an-open-weight-model-to-clean-my-downloads-folder-4flh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Lazy Tidier is a Python CLI that sorts a messy folder using simple rules first and an open-weight model only for ambiguous files. It previews every change before touching anything, and it can undo what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;My Downloads folder is a mess of &lt;code&gt;IMG_2031.pdf&lt;/code&gt;, &lt;code&gt;final_final(1).txt&lt;/code&gt; and installers I forgot about. I kept putting off cleaning it, so I built a tool to do it for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy Tidier&lt;/strong&gt; sorts a folder into categories like Images, Videos, Archives, Installers, Assignments and Lectures, and gives text-based files cleaner names.&lt;/p&gt;

&lt;p&gt;Most of the work needs no AI at all. Rules based on file extension sort the obvious files for free. Only the ambiguous ones (PDFs and text notes) go to an open-weight model.&lt;/p&gt;

&lt;p&gt;It is also safe by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A normal run is a &lt;strong&gt;dry-run&lt;/strong&gt;: it only prints what it &lt;em&gt;would&lt;/em&gt; do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--apply&lt;/code&gt; actually moves the files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--undo&lt;/code&gt; reverses the last run, restoring the original names too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's for anyone who dreads cleaning up their files and wants it done with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fma87ew9t4wsbl422n8g7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fma87ew9t4wsbl422n8g7.png" alt="Diagram of the Lazy Tidier flow: a Downloads folder goes through extension rules, then an open model for ambiguous files only, then a dry-run plan that can be applied or undone" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three commands cover everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py &lt;span class="nt"&gt;--folder&lt;/span&gt; test_downloads            &lt;span class="c"&gt;# dry-run: shows the plan&lt;/span&gt;
python main.py &lt;span class="nt"&gt;--folder&lt;/span&gt; test_downloads &lt;span class="nt"&gt;--apply&lt;/span&gt;    &lt;span class="c"&gt;# moves the files&lt;/span&gt;
python main.py &lt;span class="nt"&gt;--folder&lt;/span&gt; test_downloads &lt;span class="nt"&gt;--undo&lt;/span&gt;     &lt;span class="c"&gt;# puts everything back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here is a real dry-run on a test folder of 19 files (some rows trimmed):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OLD NAME                              CATEGORY      NEW NAME
a b (1).txt                           Other         a_b_1.txt
Artificial_Intelligence_A_Modern_...  Books         ai_modern_approach_summary.md
assignment3_notes.txt                 Assignments   assignment3_notes.txt
bank_statement_jan.pdf                Other         bank_statement_jan.pdf
cs106b_assignment2_recursion.pdf      Assignments   cs106b_assignment2.pdf
lecture5_pointers.txt                 Lectures      lecture5_pointers.txt
photo_vacation.jpg                    Images        photo_vacation.jpg
project_backup.zip                    Archives      project_backup.zip
video_lecture.mp4                     Videos        video_lecture.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;bank_statement_jan.pdf&lt;/code&gt; stayed in "Other" on purpose: its name matched the privacy guard, so its text was never sent to the model.&lt;/p&gt;

&lt;p&gt;To enable the AI step, copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and add your API key. Without a key, the tool still sorts the obvious files and safely puts the rest in "Other".&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ali-scribe" rel="noopener noreferrer"&gt;
        ali-scribe
      &lt;/a&gt; / &lt;a href="https://github.com/ali-scribe/lazy-tidier" rel="noopener noreferrer"&gt;
        lazy-tidier
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Lazy Tidier 🗂️&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Organise a messy Downloads folder in seconds — using extension rules for obvious files and an open-weight LLM for the ambiguous ones.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans&lt;/strong&gt; a folder (non-recursive) for files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules first&lt;/strong&gt; – images, videos, archives, and installers are sorted instantly by extension, no API call needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM for ambiguous files&lt;/strong&gt; – &lt;code&gt;.pdf&lt;/code&gt;, &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt; are sent to a hosted open-weight model (default: Llama 3.1 8B on Groq) along with a short text snippet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run by default&lt;/strong&gt; – prints a table of planned moves without touching anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--apply&lt;/code&gt;&lt;/strong&gt; – writes an undo log then moves files into &lt;code&gt;&amp;lt;folder&amp;gt;/&amp;lt;Category&amp;gt;/&lt;/code&gt; sub-folders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--undo&lt;/code&gt;&lt;/strong&gt; – reverses the last &lt;code&gt;--apply&lt;/code&gt; run.&lt;/li&gt;
&lt;/ol&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 1. Clone &amp;amp; enter the repo&lt;/span&gt;
git clone https://github.com/&lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;you&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;/lazy-tidier.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; lazy-tidier
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. Create and activate a virtual environment&lt;/span&gt;
python -m venv venv
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Windows:&lt;/span&gt;
venv&lt;span class="pl-cce"&gt;\S&lt;/span&gt;cripts&lt;span class="pl-cce"&gt;\a&lt;/span&gt;ctivate
&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ali-scribe/lazy-tidier" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Python, the OpenAI-compatible client library, &lt;code&gt;pypdf&lt;/code&gt; for reading PDF text, and &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-weight model:&lt;/strong&gt; &lt;code&gt;openai/gpt-oss-20b&lt;/code&gt;, served through Groq's free tier. The endpoint, key and model name all come from a &lt;code&gt;.env&lt;/code&gt; file, so the code isn't tied to any one provider or model. My first choice, Llama 3.1 8B, returned a "model not found" error on my account, and swapping models took one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rules first.&lt;/strong&gt; Extensions like &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.mp4&lt;/code&gt;, &lt;code&gt;.zip&lt;/code&gt; and &lt;code&gt;.exe&lt;/code&gt; are sorted locally with no API call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model for the rest.&lt;/strong&gt; For ambiguous documents (PDFs and text files), only the filename and up to ~500 extracted characters are sent. The model replies with a category and a suggested name in JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never trust the output.&lt;/strong&gt; The reply is validated: code fences are stripped, unknown categories become "Other", and if the API fails three times the file keeps its original name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe file handling.&lt;/strong&gt; A filename sanitizer blocks path tricks (like &lt;code&gt;../&lt;/code&gt;) and characters Windows doesn't allow. A unique-path function never overwrites an existing file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo log.&lt;/strong&gt; Every move is recorded in a JSON log &lt;em&gt;before&lt;/em&gt; it happens, so &lt;code&gt;--undo&lt;/code&gt; can reverse it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Privacy guard:&lt;/strong&gt; files whose names contain any of 14 sensitive whole words (such as &lt;code&gt;bank&lt;/code&gt;, &lt;code&gt;cv&lt;/code&gt;, &lt;code&gt;resume&lt;/code&gt;, &lt;code&gt;passport&lt;/code&gt; or &lt;code&gt;password&lt;/code&gt;) are sorted locally and never sent to the model. The matching is whole-word only, so &lt;code&gt;video_lecture.mp4&lt;/code&gt; isn't wrongly blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; 47 &lt;code&gt;pytest&lt;/code&gt; tests cover filename safety, collisions, privacy matching and JSON parsing. The API is mocked, so the tests run offline and need no key.&lt;/p&gt;

&lt;p&gt;I used Antigravity as a coding assistant, then reviewed the output, ran the tests myself and tried to break the file-moving logic on a folder of copies. That caught real issues, like the tool crashing when no API key was set even though most files don't need one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in.&lt;/strong&gt; When my first model failed on my provider, I changed one line in &lt;code&gt;.env&lt;/code&gt; and kept going. The same code works with another host or a local Ollama model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy I can improve.&lt;/strong&gt; Right now my model runs on a hosted API, so short snippets do leave my machine, and I want to be upfront about that. But because the model is open, I can move to local inference later and keep every file on my own computer without rewriting anything. A closed API wouldn't give me that option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; The whole project runs on free tiers, and a local model would cost nothing per run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fit for my hardware.&lt;/strong&gt; I can choose a model size that suits my 8 GB laptop and swap in better open models as they appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A file with a harmless name can still contain private text in its first 500 characters, and that text would be sent to the API.&lt;/li&gt;
&lt;li&gt;A small model can suggest weak or generic names. That's why the dry-run comes first and why undo exists.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--apply&lt;/code&gt; asks the model again instead of reusing the dry-run result, so a suggested name can differ slightly from the preview. The categories matter most, and &lt;code&gt;--undo&lt;/code&gt; always restores the original names.&lt;/li&gt;
&lt;li&gt;Hosted model catalogs change. If &lt;code&gt;LLM_MODEL&lt;/code&gt; is wrong, the tool prints a warning and sorts ambiguous files into "Other" instead of crashing.&lt;/li&gt;
&lt;li&gt;Audio files go straight to "Other" for now, because there is no Audio category yet.&lt;/li&gt;
&lt;li&gt;Hosted free tiers have rate limits, so very large folders are best processed in batches.&lt;/li&gt;
&lt;li&gt;My test folder had 19 small files, so these results are a small sample, not a benchmark.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;On my 19-file test folder, 11 files went to the model and the rest were sorted locally. The whole dry-run took about 8.6 seconds in one run. In a manual review of the 11 model-handled files, every category fit and no name contained details missing from the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Saving the dry-run plan so &lt;code&gt;--apply&lt;/code&gt; reuses it exactly&lt;/li&gt;
&lt;li&gt;A watch mode that tidies new downloads automatically&lt;/li&gt;
&lt;li&gt;A fully local run with Ollama so nothing leaves the machine&lt;/li&gt;
&lt;li&gt;Smarter naming for lecture and assignment files&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>python</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
