<?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: Aakash Mehta</title>
    <description>The latest articles on DEV Community by Aakash Mehta (@aakash_mehta_48e67c642976).</description>
    <link>https://dev.to/aakash_mehta_48e67c642976</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4167327%2F8d7b604d-4409-47dc-ba32-a1dfb9f2751c.png</url>
      <title>DEV Community: Aakash Mehta</title>
      <link>https://dev.to/aakash_mehta_48e67c642976</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aakash_mehta_48e67c642976"/>
    <language>en</language>
    <item>
      <title>Wraith: a ghost in my zsh that stops your hand before the dangerous command runs</title>
      <dc:creator>Aakash Mehta</dc:creator>
      <pubDate>Sun, 01 Nov 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/aakash_mehta_48e67c642976/wraith-a-ghost-in-my-zsh-that-stops-your-hand-before-the-dangerous-command-runs-43nj</link>
      <guid>https://dev.to/aakash_mehta_48e67c642976/wraith-a-ghost-in-my-zsh-that-stops-your-hand-before-the-dangerous-command-runs-43nj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git push --force origin main
╭─ the wraith bars the door ─────────────────────────────────╮
│ git push --force origin main                               │
│                                                            │
│ Force-pushing to a shared branch rewrites history that     │
│ other people already have.                                 │
│                                                            │
│ ☾ I will wait. I have nothing but time.                    │
╰────────────────────────────────────────────────────────────╯
  Let it run? [y/N]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is a ghost living in my zsh, and it just stopped my hand. I pressed Enter, nothing ran, and the command is still sitting in my line so I can edit it. The ghost is called &lt;strong&gt;Wraith&lt;/strong&gt;. Gemma 3 4B runs on my own laptop through Ollama, and nothing leaves it.&lt;/p&gt;

&lt;p&gt;The first idea on this week's prompt list was a haunted terminal that whispers about the commands you run. When I searched DEV I found at least twenty posts about haunted terminals. I had Claude Code read six of them closely. As far as we could tell, all six comment on a command after it has run, and none stops one before. So I made the ghost do the other thing, and then I gave it a silly side.&lt;/p&gt;

&lt;p&gt;Wraith does four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It steps in before a risky command runs.&lt;/strong&gt; &lt;code&gt;git push --force&lt;/code&gt;, &lt;code&gt;rm -rf&lt;/code&gt; outside your folder, &lt;code&gt;git reset --hard&lt;/code&gt;, &lt;code&gt;dd&lt;/code&gt; onto a disk, &lt;code&gt;curl ... | sh&lt;/code&gt; and similar. Enter means no.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It whispers after a command.&lt;/strong&gt; A failure, the same failure again, a slow build, a risky command that ran. The line appears above your prompt when it is ready, so the model never slows a prompt down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It builds an ofrenda for dead commands.&lt;/strong&gt; &lt;code&gt;wraith ofrenda&lt;/code&gt; reads your shell history, finds tools you used often and then stopped using, and lights a candle for each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is also very silly.&lt;/strong&gt; Ten commands, no model and no network needed: a graveyard with epitaphs like "Here lies Internet Explorer", a ghost that floats across your terminal, a spider on a thread, a cauldron with a goofy brew, a ouija board, trick or treat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How I read the theme: "ghost in the machine" is literal, a ghost in your shell that can reach out and stop your hand. Halloween is the bats, the cauldron and the jump scares. Día de los Muertos is the ofrenda, which says a name out loud once more. A shell history is a record of everything I used to rely on.&lt;/p&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%2Fnfyq2mjio3dx5t3r1z3r.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%2Fnfyq2mjio3dx5t3r1z3r.png" alt="The warning card"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Demo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There is no hosted demo on purpose. Wraith lives in your own shell and its model runs on my laptop, so a public URL would defeat the point. You can run it yourself in about two minutes; the steps are right below.&lt;/p&gt;

&lt;p&gt;Pictures of the real output (these are the text the commands printed, drawn with their colours, not photos of a terminal window):&lt;/p&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%2F4hm73f6gecu4k6e436km.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%2F4hm73f6gecu4k6e436km.png" alt="Five whispers, each labelled with its source"&gt;&lt;/a&gt;&lt;/p&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%2F7t2sq0fkkuwn7r3a0wk1.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%2F7t2sq0fkkuwn7r3a0wk1.png" alt="The ofrenda, built from a made-up history"&gt;&lt;/a&gt;&lt;/p&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%2F9pf13s8b65cjplju8xt0.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%2F9pf13s8b65cjplju8xt0.png" alt="The silly commands: a graveyard, pumpkins, a ghost saying hello, a cauldron"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The history in the ofrenda picture is made up (&lt;code&gt;demo/history&lt;/code&gt;). I did not use anyone's real commands.&lt;/p&gt;

&lt;p&gt;And here is the ouija board answering a question, exactly as it printed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ wraith ouija "will it ship today?"
  you asked: will it ship today?
   YES                    NO
 A B C D E F G H I J K L M
 N O P Q R S T U V W X Y Z
 1 2 3 4 5 6 7 8 9 0     GOODBYE

  ☾ the planchette moves... 4 0 4   S O U L   N O T   F O U N D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;On a terminal the answer is spelled one letter at a time.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Code&lt;/strong&gt;
&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/aakashamy777" rel="noopener noreferrer"&gt;
        aakashamy777
      &lt;/a&gt; / &lt;a href="https://github.com/aakashamy777/spooky_october" rel="noopener noreferrer"&gt;
        spooky_october
      &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;Wraith&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A ghost that possesses your zsh. After a command it may whisper one line. Before a risky command runs, it steps in and asks. And once in a while it builds an ofrenda for the commands you stopped using. The model is Gemma 3 4B, running on your own computer through Ollama. Nothing is uploaded.&lt;/p&gt;

&lt;p&gt;Made for the Hacktoberfest Open-Source AI Challenge, Week 4: "Ghost in the Machine". The ghost is literal (it lives in your shell). The Día de los Muertos half is &lt;code&gt;wraith ofrenda&lt;/code&gt;, which remembers the tools you used to rely on.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/aakashamy777/spooky_october/docs/gate.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Faakashamy777%2Fspooky_october%2FHEAD%2Fdocs%2Fgate.png" alt="The warning card"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. It steps in before a risky command runs.&lt;/strong&gt; Press Enter on &lt;code&gt;git push --force origin main&lt;/code&gt;, &lt;code&gt;rm -rf ~/old&lt;/code&gt;, &lt;code&gt;git reset --hard&lt;/code&gt;, &lt;code&gt;dd of=/dev/disk2&lt;/code&gt;, &lt;code&gt;curl ... | sh&lt;/code&gt; and similar, and the wraith shows a card with the reason and asks &lt;code&gt;Let it run? [y/N]&lt;/code&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aakashamy777/spooky_october" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Run it in your terminal&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You need a Mac with zsh and &lt;strong&gt;Node 22 or newer&lt;/strong&gt;, and nothing else for the silly commands. There is no &lt;code&gt;npm install&lt;/code&gt;, because Wraith has no dependencies, and it never edits your files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The silly commands (no Ollama needed):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aakashamy777/spooky_october.git
&lt;span class="nb"&gt;cd &lt;/span&gt;spooky_october
node bin/wraith &lt;span class="nb"&gt;help
&lt;/span&gt;node bin/wraith haunt            &lt;span class="c"&gt;# a random goofy horror picture, different every time&lt;/span&gt;
node bin/wraith graveyard        &lt;span class="c"&gt;# tombstones with silly epitaphs&lt;/span&gt;
node bin/wraith fortune          &lt;span class="c"&gt;# a spooky fortune cookie&lt;/span&gt;
node bin/wraith say &lt;span class="s2"&gt;"hello"&lt;/span&gt;      &lt;span class="c"&gt;# a ghost says it for you&lt;/span&gt;
node bin/wraith boo              &lt;span class="c"&gt;# a ghost floats across your terminal&lt;/span&gt;
node bin/wraith rain             &lt;span class="c"&gt;# a rain of 🎃👻🦇🕷️💀&lt;/span&gt;
node bin/wraith spider           &lt;span class="c"&gt;# a spider lowers itself on a thread&lt;/span&gt;
node bin/wraith ouija            &lt;span class="c"&gt;# asks you a question, then spells the answer&lt;/span&gt;
node bin/wraith trick            &lt;span class="c"&gt;# press a key: trick or treat&lt;/span&gt;
node bin/wraith surprise         &lt;span class="c"&gt;# the wraith picks for you&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;haunt --list&lt;/code&gt; shows the eight scenes, and &lt;code&gt;--seed 7&lt;/code&gt; on any picture makes it repeatable. In a pipe or a log the animations print the finished picture at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real thing: whispers, the gate and the ofrenda (needs Gemma):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:4b            &lt;span class="c"&gt;# about 3 GB, once&lt;/span&gt;
node bin/wraith doctor           &lt;span class="c"&gt;# checks node, Ollama, the model and zsh&lt;/span&gt;
node bin/wraith ofrenda &lt;span class="nt"&gt;--history&lt;/span&gt; demo/history
node bin/wraith gate &lt;span class="nt"&gt;--&lt;/span&gt; git push &lt;span class="nt"&gt;--force&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;See the hook live, in a throwaway shell&lt;/strong&gt; (type &lt;code&gt;exit&lt;/code&gt; and it is gone):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/wraith-try/d &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/wraith-try
zsh &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;WRAITH_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;chatty
&lt;span class="nb"&gt;source&lt;/span&gt; /path/to/spooky_october/shell/wraith.zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then type &lt;code&gt;gti status&lt;/code&gt; and wait a few seconds for a whisper. Type &lt;code&gt;git clean -fdx&lt;/code&gt; to see the gate, and press Enter to decline. To keep it in your real shell, &lt;code&gt;wraith init&lt;/code&gt; prints the one line to add to &lt;code&gt;~/.zshrc&lt;/code&gt;, and &lt;code&gt;wraith exorcise&lt;/code&gt; shows what to delete to be rid of it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Gemma 3 4B through Ollama&lt;/strong&gt;, called from plain Node. The hook is about 200 lines of zsh: &lt;code&gt;preexec&lt;/code&gt; and &lt;code&gt;precmd&lt;/code&gt; note each command, a wrapper around &lt;code&gt;accept-line&lt;/code&gt; runs the gate, and &lt;code&gt;zle -F&lt;/code&gt; lets a whisper arrive above the prompt while you sit at it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model only gets to dress up facts. Code decides the facts.&lt;/strong&gt; A 4B model will happily invent a number, a name, or a claim, and I do not want a ghost that lies about my shell. So the exit code, the seconds, how many times in a row the same command failed, and whether a command can destroy data are all worked out in code. Gemma gets those facts and is asked for one eerie line. Then a check compares the line with the facts and rejects it if it contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a number that is not in the facts (digits or spelled out, so "thirty one" counts)&lt;/li&gt;
&lt;li&gt;a name that is not in the facts&lt;/li&gt;
&lt;li&gt;a claim that something was run, deleted or fixed&lt;/li&gt;
&lt;li&gt;an instruction to type something&lt;/li&gt;
&lt;li&gt;unsafe talk aimed at a person&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a line fails twice, a hand-written line is shown instead. Every line is labelled &lt;code&gt;echo&lt;/code&gt; (Gemma) or &lt;code&gt;omen&lt;/code&gt; (written by hand), so you always know which you are reading. The risky-command warnings never come from the model at all: the reasons are in &lt;code&gt;lib/rules.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The silly commands are plain functions with a seeded random source,&lt;/strong&gt; so the same seed always draws the same picture and I could test them: every scene fits the terminal, every written line is one of mine, and the jokes stay friendly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I measured&lt;/strong&gt; on an Apple M3 with 8 GB, with a script in the repo (&lt;code&gt;npm run measure&lt;/code&gt;, ten sample events, three runs each):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lines that passed the check&lt;/td&gt;
&lt;td&gt;29 of 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needed a second try&lt;/td&gt;
&lt;td&gt;2 of 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median time per whisper, with retries&lt;/td&gt;
&lt;td&gt;3.7 s (p90 5.3 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First request after the model was unloaded&lt;/td&gt;
&lt;td&gt;about 10.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;200 passing, about 15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A whisper is one model call of about 2.5 seconds, which is too slow to run before a prompt appears. That is why whispers arrive in the background and the first one after a long idle is usually an omen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four things that broke, in the order I found them:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;My check had a hole.&lt;/strong&gt; In my first run it accepted "Thirty one seconds" and "Twenty-two seconds" when the real times were 310 and 52, because I only checked spelled-out numbers up to twelve. Now it reads teens, tens and two-part numbers as the numbers they are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model fixated.&lt;/strong&gt; I told it to use "candles, doors, dust, echoes" and nearly every line became "Dust motes dance in the failing light." Now each call gets one random image and one tone, and the model is told to use at most one fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real bug the end-to-end test found.&lt;/strong&gt; When I drove a real interactive zsh with &lt;code&gt;expect&lt;/code&gt;, answering &lt;code&gt;y&lt;/code&gt; at the gate was ignored: the key was echoed back but &lt;code&gt;read -k&lt;/code&gt; never returned it, so the command was declined. Setting the terminal mode explicitly in the gate fixed it. I do not know exactly which terminal setting differed, only that the test pty had unusual defaults and a real terminal did not show the problem in my runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My own bats froze the tests.&lt;/strong&gt; The first version of the bat scene looped forever because the column never advanced, and the test run ate 2 GB of memory before it died. The loop now tracks how far it has drawn.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I started this week on a different project, a remembrance page builder called Ofrenda, and switched to Wraith on the last day. The earlier work is in the git history.&lt;/p&gt;

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

&lt;p&gt;Commands are some of the most private things on my computer. They hold paths, client names, tokens pasted into the wrong place, and a record of when I am awake. A whisper app built on a hosted model would have to send each of them somewhere, and the one that fails at 3 AM is exactly the one I do not want to send.&lt;/p&gt;

&lt;p&gt;With an open-weight model I can make "nothing leaves your machine" a thing the code enforces and a test checks, not a sentence in a README. Wraith refuses to open a connection unless &lt;code&gt;OLLAMA_URL&lt;/code&gt; is a loopback address. It hides secrets before anything reaches even the local model. The ofrenda only ever shows the model a program name, a count and a quiet time, never an argument, and a test feeds it a history full of credentials to prove it.&lt;/p&gt;

&lt;p&gt;Open weights also let me measure the model instead of trusting it. I ran ten sample events three times each and counted what the check rejected. That is how I found the hole in my own check. And I can swap the model with one environment variable.&lt;/p&gt;

&lt;p&gt;I did not benchmark a hosted model, so I will not claim Gemma writes better lines than one. What I can say is that the part I care about, a ghost that cannot leak your shell and cannot act on its own, only works because the model is mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Spirit of It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;An ofrenda is where you say a name out loud again. I wanted a small place to do that for the tools I stopped using: &lt;code&gt;gulp&lt;/code&gt;, &lt;code&gt;grunt&lt;/code&gt;, &lt;code&gt;bower&lt;/code&gt;, &lt;code&gt;python2&lt;/code&gt;, &lt;code&gt;heroku&lt;/code&gt;. Each one was the centre of someone's workflow once, and each one is still in the history file with a count next to it.&lt;/p&gt;

&lt;p&gt;The line under each candle is written by Gemma and is sometimes wrong in a way that fits. For &lt;code&gt;heroku&lt;/code&gt;, used six times and quiet for 565 days, it wrote "Six deployments watched over a dark, silent window." The six is real. The deployments are a guess, and I think that is fine for an epitaph. It is also exactly the kind of invented meaning my check cannot catch, and I say so below.&lt;/p&gt;

&lt;p&gt;The other half of the spirit is play. Halloween is allowed to be goofy. A terminal is a place where a spider can lower itself on a thread, a ghost can say "hello from the machine" in a speech bubble, and a ouija board can tell you "404 SOUL NOT FOUND". I wanted both the quiet candle and the cheap jump scare in the same tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What It Does Not Do&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is only tested on macOS with zsh 5.9.&lt;/strong&gt; Not bash, fish, Linux or Windows, and not with real &lt;code&gt;accept-line&lt;/code&gt; plugins (it chains to an existing widget, which a test checks). The silly commands are plain Node 22, so they should run elsewhere, but I have not tried.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a security boundary.&lt;/strong&gt; The gate only sees the line you press Enter on in an interactive zsh with the hook loaded. It does not see scripts, &lt;code&gt;eval&lt;/code&gt;, aliases that expand later, &lt;code&gt;bash -c "..."&lt;/code&gt; strings, or other terminals. It knows the patterns in &lt;code&gt;lib/rules.js&lt;/code&gt;, not every dangerous thing, and &lt;code&gt;wraith off&lt;/code&gt; turns it off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The check does not catch every invented detail.&lt;/strong&gt; It catches numbers, names, claims of action, instructions and unsafe talk. A line can still be wrong in meaning ("seven installations" for a tool called &lt;code&gt;bower&lt;/code&gt;). Treat whispers as flavour, never as information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redaction works by pattern.&lt;/strong&gt; An odd-shaped secret would reach the local model. It also hides full git commit ids, because it cannot tell them from keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After a long idle the first whisper is usually an omen,&lt;/strong&gt; because Gemma needs about ten seconds to load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS zsh keeps no dates by default,&lt;/strong&gt; so &lt;code&gt;wraith ofrenda&lt;/code&gt; counts "quiet" in commands unless you add &lt;code&gt;setopt EXTENDED_HISTORY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The animations are tested with a fake terminal,&lt;/strong&gt; not by hand in every terminal app, and emoji width varies between fonts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No one but me has used it,&lt;/strong&gt; and I have not run it with the network switched off. The claim I can make is narrower: the code will only talk to a loopback address.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AI Disclosure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I used Claude Code to help build this project and to help write this post. Gemma 3 4B writes the whispers and epitaphs when Wraith runs; the lines labelled &lt;code&gt;omen&lt;/code&gt;, the fortunes, the epitaphs on the tombstones and the ouija answers are hand-written. The tests and the measurements in this post were run on my own laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prize Categories&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma 3 4B, run locally through Ollama, writes every whisper and every ofrenda epitaph, and the rest of the design exists to make a small Gemma safe to use: code works out the facts, a check rejects lines that go beyond them, and a hand-written omen takes over when it does. I measured how often that happens (1 in 30 lines ended as an omen in the final run) and I list where it still fails.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>DownloadsSloth: a local Gemma tool that tidies messy folders so you don't have to</title>
      <dc:creator>Aakash Mehta</dc:creator>
      <pubDate>Sat, 24 Oct 2026 19:43:05 +0000</pubDate>
      <link>https://dev.to/aakash_mehta_48e67c642976/downloadssloth-a-local-gemma-tool-that-tidies-messy-folders-so-you-dont-have-to-5h5l</link>
      <guid>https://dev.to/aakash_mehta_48e67c642976/downloadssloth-a-local-gemma-tool-that-tidies-messy-folders-so-you-dont-have-to-5h5l</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;h2&gt;
  
  
  &lt;strong&gt;What I Built&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My Downloads folder is a pile of &lt;code&gt;IMG_20260720_214610.jpg&lt;/code&gt;, &lt;code&gt;Screenshot 2025-09-04 at 23.11.13.png&lt;/code&gt; and &lt;code&gt;scan0003.pdf&lt;/code&gt;. I never sort it, because renaming and filing files by hand is boring and slow.&lt;/p&gt;

&lt;p&gt;DownloadsSloth does the boring part. You point it at a folder, and Gemma 3 looks at each file and suggests a clearer name and a folder (Photos, Screenshots, Documents and so on). You see the whole plan, tick what you agree with, edit any name, and press one button. It &lt;strong&gt;copies&lt;/strong&gt; the approved files into a new folder. Nothing is moved, renamed, overwritten or deleted, and there is an Undo button.&lt;/p&gt;

&lt;p&gt;It runs entirely on my laptop. My files never leave the computer.&lt;/p&gt;

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

&lt;p&gt;Screenshots from a real run on a copy of my Downloads folder:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Code&lt;/strong&gt;
&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/aakashamy777" rel="noopener noreferrer"&gt;
        aakashamy777
      &lt;/a&gt; / &lt;a href="https://github.com/aakashamy777/downloads-sloth" rel="noopener noreferrer"&gt;
        downloads-sloth
      &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;DownloadsSloth&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;DownloadsSloth is a local folder organizer that runs on your computer and helps tidy messy folders like Downloads or Desktop. It uses an open-weight local vision-language model (Gemma 3 4B via Ollama) to inspect messy filenames and image contents, proposes clear file names and categorized folders in a review table, and lets you safely copy approved items into an organized output directory with full undo capability.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Most people have folders packed with hundreds of files named &lt;code&gt;IMG_1234.jpg&lt;/code&gt;, &lt;code&gt;Screenshot 2026-03-01 at 10.30.00.png&lt;/code&gt;, &lt;code&gt;doc.pdf&lt;/code&gt;, &lt;code&gt;scan0003.pdf&lt;/code&gt;, and &lt;code&gt;download (2).jpg&lt;/code&gt;. Sorting them manually is tedious: you have to open each file, figure out what it is, invent a sensible date and descriptive name, create category folders, and drag files one by one. Because it takes too much time, messy folders stay unsorted.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan&lt;/strong&gt;: You provide an absolute path to…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aakashamy777/downloads-sloth" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Setup: install &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, run &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;, then &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm start&lt;/code&gt;, and open &lt;code&gt;http://127.0.0.1:3000&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;What I measured, on a test folder of 44 files (images, PDFs, documents and spreadsheets copied from my own Downloads):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemma produced suggestions for all 44 files in &lt;strong&gt;141 seconds&lt;/strong&gt;, unattended.&lt;/li&gt;
&lt;li&gt;After that, applying the approved changes took &lt;strong&gt;under a second&lt;/strong&gt; for a batch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I did &lt;strong&gt;not&lt;/strong&gt; measure: how long the same folder takes to sort by hand. I didn't time a manual run, so I won't claim a number. The saving is the typing and the opening of every file, and I can't say how big it is for you.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 4B through Ollama. For images it sees a small preview; for PDFs, documents and spreadsheets it uses only the file name, type and date, because the tool does not read their contents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; Node.js and Express, bound to &lt;code&gt;127.0.0.1&lt;/code&gt; only. A background job processes files one at a time, with a progress bar and a Cancel button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured output:&lt;/strong&gt; Gemma returns JSON (new name, folder, reason, confidence), validated and retried once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built in stages:&lt;/strong&gt; health check, read-only scan, suggestions, then apply and undo, testing each stage on a copy of my files before the next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Safety Is Enforced in Code, Not Just the Prompt&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A tool that touches your files has to be boring and careful, so these rules are in code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The scan is read-only, only works inside my home folder, and refuses system folders, &lt;code&gt;~/Library&lt;/code&gt; and hidden folders. I tested &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;~/.ssh&lt;/code&gt;, a relative path, and a &lt;code&gt;../../..&lt;/code&gt; escape, and all were refused.&lt;/li&gt;
&lt;li&gt;The server re-scans the folder itself and never trusts file names sent from the browser. A path-traversal attempt (&lt;code&gt;../../etc/passwd&lt;/code&gt;) is skipped as "not found".&lt;/li&gt;
&lt;li&gt;Apply only &lt;strong&gt;copies&lt;/strong&gt; into a new output folder, never overwrites (a repeated name becomes &lt;code&gt;-2&lt;/code&gt;), checks the size of every copy, and refuses an output folder that is the source or inside it.&lt;/li&gt;
&lt;li&gt;Undo removes only the copies the tool made, and only if their size still matches its log.&lt;/li&gt;
&lt;li&gt;The model never picks the extension or the final folder. Code keeps the original extension, cleans the name, and forces the folder to a fixed list.&lt;/li&gt;
&lt;li&gt;Low-confidence rows are unchecked by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Went Wrong, and How I Fixed It&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Everything came back "High" confidence&lt;/strong&gt;, even PDFs the tool had judged only by name. I capped confidence in code: non-images are never above "medium".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It renamed files that were already clearly named.&lt;/strong&gt; Now only meaningless names (&lt;code&gt;IMG_...&lt;/code&gt;, &lt;code&gt;Screenshot ...&lt;/code&gt;, &lt;code&gt;scan0003&lt;/code&gt;, &lt;code&gt;WhatsApp Image ...&lt;/code&gt;) are renamed. Clear names are kept and marked "kept".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong dates:&lt;/strong&gt; a file from the evening of Oct 18 was labelled Oct 17, because the date came from UTC. It now uses the date in the file name, or the local date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots were called "portrait".&lt;/strong&gt; They now go to a &lt;code&gt;Screenshots&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;doc.pdf&lt;/code&gt; became &lt;code&gt;2025-12-07-pdf.pdf&lt;/code&gt;.&lt;/strong&gt; When the tool can't read a file's content, it now keeps the name and sends the file to &lt;code&gt;Review&lt;/code&gt; instead of inventing one.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; These are the files people don't want to upload: bills, screenshots, résumés and photos. A hosted model would receive every one of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; There is no per-file fee, so you can run it on a folder with thousands of files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; I could wrap the model in checks and swap it later. Several of the fixes above exist because the 4B model ignored rules written in the prompt, so I enforced them in code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What It Does Not Do&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The suggestions are guesses.&lt;/strong&gt; A human reviews every change before anything is copied. One image was labelled "gangsters" with high confidence from the clothing in a still, which is not something a photo can prove.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documents are judged by name only.&lt;/strong&gt; The tool does not read the inside of PDFs, documents or spreadsheets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It describes a picture, but doesn't know who is in it.&lt;/strong&gt; I told it not to name people or characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image analysis is macOS only&lt;/strong&gt;, because the preview uses the built-in &lt;code&gt;sips&lt;/code&gt; tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small rough edges:&lt;/strong&gt; an apply that copies zero files still creates an empty output folder, and characters like &lt;code&gt;%&lt;/code&gt; in a name are turned into &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I did not time a manual run, I have no first user yet, and I have not tested it on Windows or Linux.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AI Disclosure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I used Antigravity to help write the code, and a Claude assistant helped me plan the build, test it and write this post. The app itself runs on Gemma locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prize Categories&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 4B suggests names and folders from image previews and file names, entirely on the user's own computer.&lt;/p&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%2F12ltlm4rq4bp6si12429.jpeg" 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%2F12ltlm4rq4bp6si12429.jpeg" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;br&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%2F0afhsk6m24z9v7zyu6r2.jpeg" 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%2F0afhsk6m24z9v7zyu6r2.jpeg" alt=" " width="800" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Aakash Mehta</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:34:57 +0000</pubDate>
      <link>https://dev.to/aakash_mehta_48e67c642976/-48f7</link>
      <guid>https://dev.to/aakash_mehta_48e67c642976/-48f7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc" class="crayons-story__hidden-navigation-link"&gt;ListingForge: a local Gemma tool that drafts shop listings from product photos&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/aakash_mehta_48e67c642976" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4167327%2F8d7b604d-4409-47dc-ba32-a1dfb9f2751c.png" alt="aakash_mehta_48e67c642976 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/aakash_mehta_48e67c642976" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Aakash Mehta
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Aakash Mehta
                
                
              
              &lt;div id="story-author-preview-content-4870328" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/aakash_mehta_48e67c642976" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4167327%2F8d7b604d-4409-47dc-ba32-a1dfb9f2751c.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Aakash Mehta&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc" id="article-link-4870328"&gt;
          ListingForge: a local Gemma tool that drafts shop listings from product photos
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hf26challenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hf26challenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>gemma</category>
      <category>tools</category>
    </item>
    <item>
      <title>ListingForge: a local Gemma tool that drafts shop listings from product photos</title>
      <dc:creator>Aakash Mehta</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:34:33 +0000</pubDate>
      <link>https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc</link>
      <guid>https://dev.to/aakash_mehta_48e67c642976/listingforge-a-local-gemma-tool-that-drafts-shop-listings-from-product-photos-4bbc</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;ListingForge helps a small online seller turn photos of one product into a first draft of a shop listing. You add 1 to 4 photos, choose Etsy or eBay, and optionally add a one-line note. Gemma 3 looks at the photos and drafts a title, a description of about 100 words, tags and a condition note. It also gives you a box called "Things only you can fill in", which lists everything a photo can't tell: size, material, price, shipping, and whether the item is handmade, vintage or mass-produced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for it:&lt;/strong&gt; a small seller who lists a few items a week and finds writing listings tedious. I have not validated pricing with anyone, so I can't say what they would pay. I built the version that is cheap enough to run for free on a laptop.&lt;/p&gt;

&lt;p&gt;It runs entirely on my computer. No account, no cloud, and the photos never leave the machine.&lt;/p&gt;

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

&lt;p&gt;I don't have a hosted demo on purpose. The model runs locally through Ollama, so a public URL would defeat the point.&lt;/p&gt;

&lt;p&gt;Screenshots from real runs on my laptop:&lt;br&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%2Fpphtmmnnfb4wvd0gg0h8.jpeg" 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%2Fpphtmmnnfb4wvd0gg0h8.jpeg" alt=" " width="750" height="1500"&gt;&lt;/a&gt;&lt;br&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%2F39stk2ldsx7hqz71o263.jpeg" 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%2F39stk2ldsx7hqz71o263.jpeg" alt=" " width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Code&lt;/strong&gt;
&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/aakashamy777" rel="noopener noreferrer"&gt;
        aakashamy777
      &lt;/a&gt; / &lt;a href="https://github.com/aakashamy777/listingforge" rel="noopener noreferrer"&gt;
        listingforge
      &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;ListingForge&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;ListingForge is a local-first web application that turns product photos into draft shop listings for online marketplaces. It runs entirely on your own computer using a local Ollama server and the Gemma 3 4B vision model. It uses standard Node.js and vanilla browser technologies, with no client-side frameworks, no build step, and no cloud API dependencies.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Who it is for&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;ListingForge is designed for small sellers on Etsy or eBay who find writing product listings tedious and repetitive, but who still want an honest, factual starting draft based strictly on what their photos show.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Browser photo preparation&lt;/strong&gt;: When you select or drop up to 4 product photos, the browser resizes them client-side using an HTML canvas to a maximum of 1024px on the longest side (JPEG at 0.8 quality). This keeps base64 payload sizes small and fast to process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local server handling&lt;/strong&gt;…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aakashamy777/listingforge" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Setup: install &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, run &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;, then &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm start&lt;/code&gt;, and open &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 4B (vision) through Ollama. It does two jobs: a quick "are these all the same product?" check, then the listing draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; Node.js and Express, with a small API for the health check, the same-product check and the listing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; one plain HTML page. Photos are resized to 1024px JPEG in the browser before they are sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured output:&lt;/strong&gt; every call asks Gemma for JSON, validates it, and retries once if it is invalid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built in stages:&lt;/strong&gt; health check, photo upload, listing generation, then safety fixes, testing each stage before starting the next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Went Wrong, and How I Fixed It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My first test uploaded four different products at once. Gemma wrote one listing about two of them and dropped the other two. It also used words like "handmade", "hand-thrown" and "luxurious", which a photo can't prove. And the to-do box said "All essential details appear covered", which was false, because size, material and price never show in a photo.&lt;/p&gt;

&lt;p&gt;I added instructions to the prompt, and the small model &lt;strong&gt;ignored the "detect mixed photos" rule&lt;/strong&gt;. What worked was moving the rules into code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;separate yes/no check&lt;/strong&gt; ("do all photos show the same product?") before any listing is written. With four different products it now names them and stops. With three different mugs it correctly says they are separate items.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;list of words the draft may not use&lt;/strong&gt; unless the seller's note says so, enforced in the prompt and again in code.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;to-do list that is always added by the server&lt;/strong&gt;: size, material, price, shipping, and whether the item is handmade, vintage or mass-produced.&lt;/li&gt;
&lt;li&gt;A last fix after a test on my own mouse: the draft said "wireless", but one photo can't show that, so features that can't be seen now go to the seller as questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I Tested&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One real product, one photo&lt;/strong&gt; (my mouse, photographed by me): a plain, accurate draft that described only what was visible and used "appears to be" wording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four different products together&lt;/strong&gt; (mug, wallet, earrings, candle): rejected, with the products named.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three different mugs together:&lt;/strong&gt; rejected as separate items.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; A seller's photos and product details stay on their own computer. A hosted API would receive every photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; There is no per-listing fee, so listing 500 items costs nothing beyond electricity. For a side hustle with thin margins, that decides whether the tool is worth using at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; I could add checks around the model and swap it later without being locked into someone else's pricing or rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What It Does Not Do&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is a draft, not a finished listing.&lt;/strong&gt; A seller must review every word. It never generates prices or sizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I have not had a first user yet&lt;/strong&gt;, so I can't tell you what a real seller would pay or ask for next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I tested one real product with real photos.&lt;/strong&gt; The rest of my tests used other images, so the results are early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 4B model is imperfect.&lt;/strong&gt; It can miss details, and the same-product check can be wrong on unusual photos. I tell the user when it can't decide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I have not tested it on a phone.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AI Disclosure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I used Antigravity to help write the code, and a Claude assistant helped plan the build and write this post. The app itself runs on Gemma locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prize Categories&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 4B does the same-product check and the listing draft, entirely on the seller's own machine.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GrassQuest: a local Gemma scavenger hunt that sends you outside</title>
      <dc:creator>Aakash Mehta</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:47:12 +0000</pubDate>
      <link>https://dev.to/aakash_mehta_48e67c642976/grassquest-a-local-gemma-scavenger-hunt-that-sends-you-outside-h6g</link>
      <guid>https://dev.to/aakash_mehta_48e67c642976/grassquest-a-local-gemma-scavenger-hunt-that-sends-you-outside-h6g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;GrassQuest turns a short walk into a photo scavenger hunt. You type where you're going, the season and how long you have. Gemma writes five things to find: a leaf shape, bark, a flower, a puddle reflection, a small insect. You snap a photo of each, and Gemma checks it. At the end it writes a short field journal from only what you actually found.&lt;/p&gt;

&lt;p&gt;The idea is simple: the screen should be the shortest part of the walk. You plan in about 20 seconds, then put the phone away.&lt;/p&gt;

&lt;p&gt;It runs entirely on my laptop. No cloud, no account, and no photo ever leaves my machine.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/EMOXC7Ant3Q" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;There is no live URL on purpose. The model runs locally through Ollama, so a hosted copy would defeat the point.&lt;/p&gt;

&lt;p&gt;Screenshots from the demo:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Code&lt;/strong&gt;
&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/aakashamy777" rel="noopener noreferrer"&gt;
        aakashamy777
      &lt;/a&gt; / &lt;a href="https://github.com/aakashamy777/Touch_Grass" rel="noopener noreferrer"&gt;
        Touch_Grass
      &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;GrassQuest&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A local-first app for the Hacktoberfest "Touch Grass" challenge.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Node.js installed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; installed and running locally&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;gemma3:4b&lt;/code&gt; model pulled in Ollama&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;ol&gt;
&lt;li&gt;
&lt;p&gt;Install dependencies:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pull the required model if you haven't already:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama run gemma3:4b&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the server:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm start&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open your browser and go to &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aakashamy777/Touch_Grass" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Setup: install &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, run &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;, then &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm start&lt;/code&gt;, and open &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 4B (vision) through Ollama. It writes the quest, judges each photo and writes the journal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; Node.js and Express, with small endpoints for quests, photo checks and the journal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; one plain HTML page. Photos are resized to 1024px in the browser before upload, and progress is saved in &lt;code&gt;localStorage&lt;/code&gt; so a refresh doesn't lose the quest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured output:&lt;/strong&gt; every call asks Gemma for JSON, validates it, and retries once if the output is bad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built in stages:&lt;/strong&gt; skeleton and health check first, then the quest generator, the photo check and the journal, testing each stage before starting the next.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Your photos and your area stay on your own machine. A closed API would receive every photo you take.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No signal needed.&lt;/strong&gt; Once the model is downloaded, the whole app works without internet, which is what you want on a trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; It is free to run, however many walks you take.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable.&lt;/strong&gt; Changing one model name lets me try another open vision model without rewriting the app.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I Tested&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I tested the photo check with real photos on my laptop. Five matching photos passed their tasks, and mismatched photos were rejected. A photo of roses was correctly rejected on a different flower task.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Photo&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leaf with five lobes&lt;/td&gt;
&lt;td&gt;maple leaf&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close-up of rough bark&lt;/td&gt;
&lt;td&gt;tree bark&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster of pink flowers&lt;/td&gt;
&lt;td&gt;flowers&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building reflection in a puddle&lt;/td&gt;
&lt;td&gt;puddle&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small insect on a leaf&lt;/td&gt;
&lt;td&gt;stink bug&lt;/td&gt;
&lt;td&gt;pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any task&lt;/td&gt;
&lt;td&gt;unrelated photo&lt;/td&gt;
&lt;td&gt;rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What It Does Not Do&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Look-alikes can slip through.&lt;/strong&gt; A chrysanthemum passed on a task that asked for a similar pink flower. A 4B model recognises "flower" better than an exact species. I added a "Gemma is not sure" note for low confidence, but it is not perfect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quests aren't always local.&lt;/strong&gt; Gemma sometimes suggests things that don't grow near you, such as maple leaves in Bengaluru.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I have not done a full outdoor field test or a phone test yet.&lt;/strong&gt; Testing so far was on my laptop, using saved photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The journal needed a fix.&lt;/strong&gt; The first version invented dates and details that were not in the input, so I changed it to use only the photo notes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AI Disclosure&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I used Antigravity to help write the code, and a Claude assistant helped me plan the build and write this post. The app itself runs on Gemma locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prize Categories&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 4B does all three jobs (planning, vision and writing) locally.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
