<?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: x t</title>
    <description>The latest articles on DEV Community by x t (@t364170654).</description>
    <link>https://dev.to/t364170654</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%2F4188119%2Ff5fec33e-8f09-4ae2-8d2b-2ca67323303d.png</url>
      <title>DEV Community: x t</title>
      <link>https://dev.to/t364170654</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/t364170654"/>
    <language>en</language>
    <item>
      <title>séance: I let a Gemma ghost haunt my shell (and built it an ofrenda for my deleted files)</title>
      <dc:creator>x t</dc:creator>
      <pubDate>Wed, 28 Oct 2026 03:06:11 +0000</pubDate>
      <link>https://dev.to/t364170654/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted-files-2dii</link>
      <guid>https://dev.to/t364170654/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted-files-2dii</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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👻 &lt;strong&gt;ezequiel ›&lt;/strong&gt; make test again, you foolish mortal? Exit code two; a familiar echo of my own demise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That line was written by a 4-billion-parameter model running on the CPU of a small Linux VM, about three&lt;br&gt;
and a half seconds after my test suite failed for the second time in a row. No network request left&lt;br&gt;
the machine to produce it. That's the whole project, really: a ghost that lives &lt;em&gt;inside&lt;/em&gt; the machine,&lt;br&gt;
in the most literal way I could manage.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;séance&lt;/strong&gt; is a haunted terminal. You add one line to your &lt;code&gt;.bashrc&lt;/code&gt; (or &lt;code&gt;.zshrc&lt;/code&gt;), and from then on&lt;br&gt;
your shell is shared with &lt;strong&gt;Ezequiel&lt;/strong&gt;, a sysadmin who died in 1999 halfway through an &lt;code&gt;fsck&lt;/code&gt; and&lt;br&gt;
never left the server room. After each command he whispers, in one or two sentences, what he thinks&lt;br&gt;
of what you just did.&lt;/p&gt;

&lt;p&gt;Yes, the "haunted terminal" is the first idea on the challenge's own list. I couldn't resist it, so I&lt;br&gt;
tried to make it &lt;em&gt;mean&lt;/em&gt; something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;He remembers.&lt;/strong&gt; Every session keeps a short memory (redacted, on disk, local). He knows this is
your second &lt;code&gt;make test&lt;/code&gt;, and he'll bring up the exit code 2 from three commands ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;He reads omens.&lt;/strong&gt; Before the model is asked anything, plain Python works out what's actually going
on: exit 127 means "command not found", 137 means SIGKILL, this is the 4th failure in a row, this
command deletes things, this one is &lt;code&gt;sudo&lt;/code&gt;. Gemma gets those facts; it only has to be eerie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apparitions.&lt;/strong&gt; Delete files and a tombstone rises. Kill a process and a calavera (sugar skull)
grins at you. &lt;code&gt;git push --force&lt;/code&gt; gets a ghost. &lt;code&gt;sudo&lt;/code&gt; gets a watching eye.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Possession.&lt;/strong&gt; The more ominous things get (failure streaks, destructive commands), the more likely
the terminal is to glitch, with Zalgo text and block noise, while the ghost "stirs".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;seance ofrenda&lt;/code&gt;&lt;/strong&gt;, for Día de los Muertos. An altar of candles and marigolds for everything that
died on your machine: files you &lt;code&gt;rm&lt;/code&gt;'d, processes you &lt;code&gt;kill&lt;/code&gt;ed or that got SIGKILLed, git branches you
deleted, and the &lt;em&gt;almas en pena&lt;/em&gt;, branches left untouched for months. Gemma writes a short
remembrance for each one. It reads your shell history, the séance graveyard, the freedesktop Trash and
the repo's reflog, and it never modifies any of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a pipx-installable Python package with zero dependencies, it works fully offline, it has no API&lt;br&gt;
keys, and it's MIT-licensed.&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/https%3A%2F%2Fraw.githubusercontent.com%2FTx13758627780%2Fseance-sh%2Fmain%2Fmedia%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FTx13758627780%2Fseance-sh%2Fmain%2Fmedia%2Fdemo.gif" alt="séance haunting a bash session" width="559" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The GIF above is a real recording: real bash, the real hook, and gemma3:4b answering live. The keystrokes&lt;br&gt;
are typed by a small script (&lt;code&gt;scripts/demo.py&lt;/code&gt;), so anyone can reproduce the recording. The only&lt;br&gt;
editing is that pauses longer than 1.5 s are shortened. The latency under each whisper is what séance&lt;br&gt;
measured at the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possession:&lt;/strong&gt; the second failing &lt;code&gt;make test&lt;/code&gt; makes the terminal glitch while the ghost stirs.&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%2Fdpbhjvqj81hshhaqq2sb.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%2Fdpbhjvqj81hshhaqq2sb.png" alt="possession" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Omens and apparitions:&lt;/strong&gt; &lt;code&gt;rm -rf&lt;/code&gt; raises a tombstone, and a SIGKILLed training script gets a calavera.&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%2Fh1rwvfb8mfcg52e6ttq1.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%2Fh1rwvfb8mfcg52e6ttq1.png" alt="calavera" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ofrenda:&lt;/strong&gt; remembrances for a deleted hotfix branch, a SIGKILLed process, two deleted files,&lt;br&gt;
and an abandoned &lt;code&gt;experiment/rewrite-in-rust&lt;/code&gt; branch, 117 days old.&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%2Ffrsqhcx895l41ncrkoh0.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%2Ffrsqhcx895l41ncrkoh0.png" alt="ofrenda" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few lines from that recorded session, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👻 &lt;strong&gt;ezequiel ›&lt;/strong&gt; gti status. The command is a phantom, like my existence – it simply doesn't exist.&lt;/p&gt;

&lt;p&gt;🕯️ &lt;strong&gt;experiment/rewrite-in-rust&lt;/strong&gt; &lt;em&gt;[alma en pena · abandoned branch · 117 days ago]&lt;/em&gt;&lt;br&gt;
Poor experiment/rewrite-in-rust. It wrestled with grand ambitions, a tangled promise of rust, then simply vanished into the depths of the repository.&lt;/p&gt;

&lt;p&gt;🕯️ &lt;strong&gt;~/crypt/build&lt;/strong&gt; &lt;em&gt;[archivo · file]&lt;/em&gt;&lt;br&gt;
The build folder, a tireless worker, finally rests. May marigold petals guide its code back to Mictlan, a gentle farewell to its frantic efforts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The typo one got me. I typed &lt;code&gt;gti&lt;/code&gt; on purpose for the demo, and I still laughed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Tx13758627780/seance-sh" rel="noopener noreferrer"&gt;https://github.com/Tx13758627780/seance-sh&lt;/a&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
pipx &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Tx13758627780/seance-sh
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(seance init bash)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  The shape of it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash/zsh hook ──(command, exit code, start time, cwd)──▶ seance whisper
   omens.py   deterministic facts: exit meanings, streaks, repeats, deaths, which apparition
   memory.py  last N commands this session (JSONL, secrets redacted) + a graveyard of deaths
   ghost.py   persona + memory + omens → POST /api/chat (streaming) → Ollama → Gemma 3
   effects.py "the ghost stirs" glitch while waiting → whisper streams in, word-wrapped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bash hook uses a &lt;code&gt;DEBUG&lt;/code&gt; trap to catch the command and its start time, plus &lt;code&gt;PROMPT_COMMAND&lt;/code&gt; to&lt;br&gt;
call &lt;code&gt;seance whisper&lt;/code&gt; with the exit code once the command finishes. zsh uses &lt;code&gt;preexec&lt;/code&gt;/&lt;code&gt;precmd&lt;/code&gt;. The hook&lt;br&gt;
keeps your &lt;code&gt;$?&lt;/code&gt; intact, skips tab completion, and doesn't re-whisper the previous command when you just&lt;br&gt;
press Enter. That last bug took me longer than any of the AI parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Gemma, and which Gemma
&lt;/h3&gt;

&lt;p&gt;I benchmarked both small Gemma 3 models through Ollama with séance's &lt;em&gt;real&lt;/em&gt; prompt (persona, memory,&lt;br&gt;
omens), 14 runs each, on the only hardware I had for this: &lt;strong&gt;a Linux VM with 8 vCPUs (Intel Xeon),&lt;br&gt;
16 GB of RAM and no GPU.&lt;/strong&gt; Ollama confirms it's running on CPU.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;model&lt;/th&gt;
&lt;th&gt;cold start (load + first reply)&lt;/th&gt;
&lt;th&gt;first word, warm (median)&lt;/th&gt;
&lt;th&gt;full whisper, warm (median)&lt;/th&gt;
&lt;th&gt;RAM (&lt;code&gt;ollama ps&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:1b&lt;/td&gt;
&lt;td&gt;3.08 s&lt;/td&gt;
&lt;td&gt;0.90 s (0.70–1.13)&lt;/td&gt;
&lt;td&gt;1.78 s (1.41–2.34)&lt;/td&gt;
&lt;td&gt;1.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:4b&lt;/td&gt;
&lt;td&gt;6.79 s&lt;/td&gt;
&lt;td&gt;1.90 s (1.38–2.75)&lt;/td&gt;
&lt;td&gt;3.58 s (2.54–5.90)&lt;/td&gt;
&lt;td&gt;2.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1B model is twice as fast, but in my testing its whispers were generic. They were spooky in the&lt;br&gt;
abstract, but they often ignored the actual command. The 4B model reliably names the&lt;br&gt;
file, the branch or the exit code, and that's what makes it feel like someone is &lt;em&gt;watching&lt;/em&gt;. So&lt;br&gt;
&lt;strong&gt;gemma3:4b is the default&lt;/strong&gt;, and &lt;code&gt;SEANCE_MODEL=gemma3:1b&lt;/code&gt; is one environment variable away if your&lt;br&gt;
machine is slower or you want a ghost that talks faster. In the recorded demo session, the 7 whispers&lt;br&gt;
had a median of &lt;strong&gt;1.80 s to the first word&lt;/strong&gt; and &lt;strong&gt;3.15 s for the full whisper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The benchmark script is in the repo (&lt;code&gt;scripts/bench.py&lt;/code&gt;), so you can measure your own machine instead&lt;br&gt;
of trusting mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding latency: make the waiting part of the haunting
&lt;/h3&gt;

&lt;p&gt;Three seconds of nothing after every command would feel broken. So séance streams the response and, until the&lt;br&gt;
first token arrives, plays a glitching &lt;code&gt;░▒▓ the ghost stirs ▓▒░&lt;/code&gt; line that dissolves into the whisper.&lt;br&gt;
The whisper is then typed out as it streams, word-wrapped with a hanging indent. The roughly&lt;br&gt;
1.9 s wait for the first word stops feeling like lag and starts feeling like the ghost gathering itself.&lt;/p&gt;

&lt;p&gt;For when you actually need to work, &lt;code&gt;SEANCE_RATE=0.3&lt;/code&gt; keeps him quiet on calm, successful commands&lt;br&gt;
(failures and destructive commands always get a whisper), and &lt;code&gt;SEANCE_HUSH=1&lt;/code&gt; silences him entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompting a small model
&lt;/h3&gt;

&lt;p&gt;Some things I learned, in roughly the order they bit me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma has no system role.&lt;/strong&gt; Gemma's chat format only has user and model turns, so I fold the persona
into the first user turn myself instead of relying on how a server template treats a system message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small models need facts, not puzzles.&lt;/strong&gt; Asking "what does exit code 137 mean?" invites hallucination.
Telling the model "Omens you sense: killed by SIGKILL (137); this is the 2nd failure in a row" gives it
something true to be dramatic about. This is the single biggest quality improvement I made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few-shot beats adjectives.&lt;/strong&gt; Four example whispers in the persona did more than any amount of
"be eerie, be specific, be concise".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean the output defensively.&lt;/strong&gt; The model sometimes echoes the prompt format back ("make test
(exit 2) – …"), adds markdown, or rambles. A small &lt;code&gt;clean()&lt;/code&gt; step strips that and caps the whisper at two
sentences. An earlier version of that step also stripped the underscores out of
&lt;code&gt;notes_from_last_halloween.txt&lt;/code&gt;, which was its own small horror.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variety for the ofrenda.&lt;/strong&gt; Each remembrance gets a different "angle" (an offering left on the
altar, a tiny eulogy from its old friends in the codebase, marigold petals guiding it home…). Otherwise five epitaphs in a row all start
with "Rest now".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It isn't perfect. Ezequiel brings up his own death &lt;em&gt;a lot&lt;/em&gt;, and sometimes he invents history ("the&lt;br&gt;
last time you checked for disks"). For a ghost, I've decided that's in character.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy, carefully
&lt;/h3&gt;

&lt;p&gt;Shell history is about as private as data gets: hostnames, paths, typo'd passwords. Before anything is&lt;br&gt;
stored, séance redacts &lt;code&gt;*TOKEN*/*SECRET*/*PASSWORD*&lt;/code&gt; variables, &lt;code&gt;--password&lt;/code&gt; flags, &lt;code&gt;Authorization:&lt;/code&gt;&lt;br&gt;
headers, &lt;code&gt;user:pass@&lt;/code&gt; URLs, and GitHub, OpenAI, Hugging Face and AWS-style keys. There are tests for&lt;br&gt;
this. And then it only ever sends that redacted text to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because the ghost reads your shell history.&lt;/strong&gt; I would never pipe every command I type, with its&lt;br&gt;
paths, hostnames and the odd password pasted in the wrong window, to someone else's API to get a joke&lt;br&gt;
back. With an open-weight model it never has to leave the box. séance talks only to&lt;br&gt;
&lt;code&gt;http://127.0.0.1:11434&lt;/code&gt;. You can unplug the network cable and the haunting carries on. I measured&lt;br&gt;
every number in this post on a machine with no GPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because it costs nothing to be haunted.&lt;/strong&gt; An always-on shell companion makes a model call after&lt;br&gt;
&lt;em&gt;every&lt;/em&gt; command. With a hosted API, that's a meter running all day. Locally it's 2.9 GB of RAM and a few&lt;br&gt;
seconds of CPU per whisper (about 3.6 s median for gemma3:4b, 1.8 s for gemma3:1b on my 8-vCPU VM).&lt;br&gt;
There's no key to leak and no bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because the model is swappable.&lt;/strong&gt; The 1B-vs-4B trade-off above was a decision &lt;em&gt;I&lt;/em&gt; got to make,&lt;br&gt;
with my own benchmarks, on my own hardware. Next year it'll be a different model, and switching is one&lt;br&gt;
environment variable: &lt;code&gt;SEANCE_MODEL=&amp;lt;anything Ollama can run&amp;gt;&lt;/code&gt;. No vendor decides that a fun but&lt;br&gt;
pointless app is no longer worth serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because things you depend on should outlive the company that made them.&lt;/strong&gt; That one fits the theme a&lt;br&gt;
little too well. An ofrenda is about remembering what's gone. I didn't want to build one that would join&lt;br&gt;
it the day an API was deprecated. Gemma's weights are on my disk. As long as Ollama, or llama.cpp, or&lt;br&gt;
whatever comes after them can load a GGUF, Ezequiel stays.&lt;/p&gt;

&lt;p&gt;Where it was &lt;em&gt;worse&lt;/em&gt; than a closed frontier model: latency and polish. A big hosted model would&lt;br&gt;
whisper faster and repeat itself less. But a faster ghost that phones home with your shell history&lt;br&gt;
isn't the ghost I wanted.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 (gemma3:4b by default, gemma3:1b optional) run locally through Ollama&lt;br&gt;
is the ghost. It writes every whisper and every ofrenda remembrance, grounded by deterministic omens&lt;br&gt;
and benchmarked on CPU-only hardware.&lt;/p&gt;




&lt;p&gt;Thanks for reading. If you install it, run &lt;code&gt;seance ofrenda&lt;/code&gt; on Nov 1 or 2. The altar notices the date. 🕯️🌼&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— xt&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>séance: I let a Gemma ghost haunt my shell (and built it an ofrenda for my deleted files)</title>
      <dc:creator>x t</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:59:33 +0000</pubDate>
      <link>https://dev.to/t364170654/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted-files-3590</link>
      <guid>https://dev.to/t364170654/seance-i-let-a-gemma-ghost-haunt-my-shell-and-built-it-an-ofrenda-for-my-deleted-files-3590</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&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👻 &lt;strong&gt;ezequiel ›&lt;/strong&gt; make test again, you foolish mortal? Exit code two; a familiar echo of my own demise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That line was written by a 4-billion-parameter model running on the CPU of a small Linux VM, about three&lt;br&gt;
and a half seconds after my test suite failed for the second time in a row. No network request left&lt;br&gt;
the machine to produce it. That's the whole project, really: a ghost that lives &lt;em&gt;inside&lt;/em&gt; the machine,&lt;br&gt;
in the most literal way I could manage.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;séance&lt;/strong&gt; is a haunted terminal. You add one line to your &lt;code&gt;.bashrc&lt;/code&gt; (or &lt;code&gt;.zshrc&lt;/code&gt;), and from then on&lt;br&gt;
your shell is shared with &lt;strong&gt;Ezequiel&lt;/strong&gt;, a sysadmin who died in 1999 halfway through an &lt;code&gt;fsck&lt;/code&gt; and&lt;br&gt;
never left the server room. After each command he whispers, in one or two sentences, what he thinks&lt;br&gt;
of what you just did.&lt;/p&gt;

&lt;p&gt;Yes, the "haunted terminal" is the first idea on the challenge's own list. I couldn't resist it, so I&lt;br&gt;
tried to make it &lt;em&gt;mean&lt;/em&gt; something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;He remembers.&lt;/strong&gt; Every session keeps a short memory (redacted, on disk, local). He knows this is
your second &lt;code&gt;make test&lt;/code&gt;, and he'll bring up the exit code 2 from three commands ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;He reads omens.&lt;/strong&gt; Before the model is asked anything, plain Python works out what's actually going
on: exit 127 means "command not found", 137 means SIGKILL, this is the 4th failure in a row, this
command deletes things, this one is &lt;code&gt;sudo&lt;/code&gt;. Gemma gets those facts; it only has to be eerie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apparitions.&lt;/strong&gt; Delete files and a tombstone rises. Kill a process and a calavera (sugar skull)
grins at you. &lt;code&gt;git push --force&lt;/code&gt; gets a ghost. &lt;code&gt;sudo&lt;/code&gt; gets a watching eye.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Possession.&lt;/strong&gt; The more ominous things get (failure streaks, destructive commands), the more likely
the terminal is to glitch, with Zalgo text and block noise, while the ghost "stirs".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;seance ofrenda&lt;/code&gt;&lt;/strong&gt;, for Día de los Muertos. An altar of candles and marigolds for everything that
died on your machine: files you &lt;code&gt;rm&lt;/code&gt;'d, processes you &lt;code&gt;kill&lt;/code&gt;ed or that got SIGKILLed, git branches you
deleted, and the &lt;em&gt;almas en pena&lt;/em&gt;, branches left untouched for months. Gemma writes a short
remembrance for each one. It reads your shell history, the séance graveyard, the freedesktop Trash and
the repo's reflog, and it never modifies any of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a pipx-installable Python package with zero dependencies, it works fully offline, it has no API&lt;br&gt;
keys, and it's MIT-licensed.&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/https%3A%2F%2Fraw.githubusercontent.com%2FTx13758627780%2Fseance-sh%2Fmain%2Fmedia%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FTx13758627780%2Fseance-sh%2Fmain%2Fmedia%2Fdemo.gif" alt="séance haunting a bash session" width="559" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The GIF above is a real recording: real bash, the real hook, and gemma3:4b answering live. The keystrokes&lt;br&gt;
are typed by a small script (&lt;code&gt;scripts/demo.py&lt;/code&gt;), so anyone can reproduce the recording. The only&lt;br&gt;
editing is that pauses longer than 1.5 s are shortened. The latency under each whisper is what séance&lt;br&gt;
measured at the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possession:&lt;/strong&gt; the second failing &lt;code&gt;make test&lt;/code&gt; makes the terminal glitch while the ghost stirs.&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%2Fdpbhjvqj81hshhaqq2sb.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%2Fdpbhjvqj81hshhaqq2sb.png" alt="possession" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Omens and apparitions:&lt;/strong&gt; &lt;code&gt;rm -rf&lt;/code&gt; raises a tombstone, and a SIGKILLed training script gets a calavera.&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%2Fh1rwvfb8mfcg52e6ttq1.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%2Fh1rwvfb8mfcg52e6ttq1.png" alt="calavera" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ofrenda:&lt;/strong&gt; remembrances for a deleted hotfix branch, a SIGKILLed process, two deleted files,&lt;br&gt;
and an abandoned &lt;code&gt;experiment/rewrite-in-rust&lt;/code&gt; branch, 117 days old.&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%2Ffrsqhcx895l41ncrkoh0.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%2Ffrsqhcx895l41ncrkoh0.png" alt="ofrenda" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few lines from that recorded session, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👻 &lt;strong&gt;ezequiel ›&lt;/strong&gt; gti status. The command is a phantom, like my existence – it simply doesn't exist.&lt;/p&gt;

&lt;p&gt;🕯️ &lt;strong&gt;experiment/rewrite-in-rust&lt;/strong&gt; &lt;em&gt;[alma en pena · abandoned branch · 117 days ago]&lt;/em&gt;&lt;br&gt;
Poor experiment/rewrite-in-rust. It wrestled with grand ambitions, a tangled promise of rust, then simply vanished into the depths of the repository.&lt;/p&gt;

&lt;p&gt;🕯️ &lt;strong&gt;~/crypt/build&lt;/strong&gt; &lt;em&gt;[archivo · file]&lt;/em&gt;&lt;br&gt;
The build folder, a tireless worker, finally rests. May marigold petals guide its code back to Mictlan, a gentle farewell to its frantic efforts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The typo one got me. I typed &lt;code&gt;gti&lt;/code&gt; on purpose for the demo, and I still laughed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Tx13758627780/seance-sh" rel="noopener noreferrer"&gt;https://github.com/Tx13758627780/seance-sh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home page:&lt;/strong&gt; &lt;a href="https://seance.tianx.tech" rel="noopener noreferrer"&gt;seance.tianx.tech&lt;/a&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
pipx &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Tx13758627780/seance-sh
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(seance init bash)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  The shape of it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash/zsh hook ──(command, exit code, start time, cwd)──▶ seance whisper
   omens.py   deterministic facts: exit meanings, streaks, repeats, deaths, which apparition
   memory.py  last N commands this session (JSONL, secrets redacted) + a graveyard of deaths
   ghost.py   persona + memory + omens → POST /api/chat (streaming) → Ollama → Gemma 3
   effects.py "the ghost stirs" glitch while waiting → whisper streams in, word-wrapped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bash hook uses a &lt;code&gt;DEBUG&lt;/code&gt; trap to catch the command and its start time, plus &lt;code&gt;PROMPT_COMMAND&lt;/code&gt; to&lt;br&gt;
call &lt;code&gt;seance whisper&lt;/code&gt; with the exit code once the command finishes. zsh uses &lt;code&gt;preexec&lt;/code&gt;/&lt;code&gt;precmd&lt;/code&gt;. The hook&lt;br&gt;
keeps your &lt;code&gt;$?&lt;/code&gt; intact, skips tab completion, and doesn't re-whisper the previous command when you just&lt;br&gt;
press Enter. That last bug took me longer than any of the AI parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Gemma, and which Gemma
&lt;/h3&gt;

&lt;p&gt;I benchmarked both small Gemma 3 models through Ollama with séance's &lt;em&gt;real&lt;/em&gt; prompt (persona, memory,&lt;br&gt;
omens), 14 runs each, on the only hardware I had for this: &lt;strong&gt;a Linux VM with 8 vCPUs (Intel Xeon),&lt;br&gt;
16 GB of RAM and no GPU.&lt;/strong&gt; Ollama confirms it's running on CPU.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;model&lt;/th&gt;
&lt;th&gt;cold start (load + first reply)&lt;/th&gt;
&lt;th&gt;first word, warm (median)&lt;/th&gt;
&lt;th&gt;full whisper, warm (median)&lt;/th&gt;
&lt;th&gt;RAM (&lt;code&gt;ollama ps&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:1b&lt;/td&gt;
&lt;td&gt;3.08 s&lt;/td&gt;
&lt;td&gt;0.90 s (0.70–1.13)&lt;/td&gt;
&lt;td&gt;1.78 s (1.41–2.34)&lt;/td&gt;
&lt;td&gt;1.2 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma3:4b&lt;/td&gt;
&lt;td&gt;6.79 s&lt;/td&gt;
&lt;td&gt;1.90 s (1.38–2.75)&lt;/td&gt;
&lt;td&gt;3.58 s (2.54–5.90)&lt;/td&gt;
&lt;td&gt;2.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1B model is twice as fast, but in my testing its whispers were generic. They were spooky in the&lt;br&gt;
abstract, but they often ignored the actual command. The 4B model reliably names the&lt;br&gt;
file, the branch or the exit code, and that's what makes it feel like someone is &lt;em&gt;watching&lt;/em&gt;. So&lt;br&gt;
&lt;strong&gt;gemma3:4b is the default&lt;/strong&gt;, and &lt;code&gt;SEANCE_MODEL=gemma3:1b&lt;/code&gt; is one environment variable away if your&lt;br&gt;
machine is slower or you want a ghost that talks faster. In the recorded demo session, the 7 whispers&lt;br&gt;
had a median of &lt;strong&gt;1.80 s to the first word&lt;/strong&gt; and &lt;strong&gt;3.15 s for the full whisper&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The benchmark script is in the repo (&lt;code&gt;scripts/bench.py&lt;/code&gt;), so you can measure your own machine instead&lt;br&gt;
of trusting mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding latency: make the waiting part of the haunting
&lt;/h3&gt;

&lt;p&gt;Three seconds of nothing after every command would feel broken. So séance streams the response and, until the&lt;br&gt;
first token arrives, plays a glitching &lt;code&gt;░▒▓ the ghost stirs ▓▒░&lt;/code&gt; line that dissolves into the whisper.&lt;br&gt;
The whisper is then typed out as it streams, word-wrapped with a hanging indent. The roughly&lt;br&gt;
1.9 s wait for the first word stops feeling like lag and starts feeling like the ghost gathering itself.&lt;/p&gt;

&lt;p&gt;For when you actually need to work, &lt;code&gt;SEANCE_RATE=0.3&lt;/code&gt; keeps him quiet on calm, successful commands&lt;br&gt;
(failures and destructive commands always get a whisper), and &lt;code&gt;SEANCE_HUSH=1&lt;/code&gt; silences him entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompting a small model
&lt;/h3&gt;

&lt;p&gt;Some things I learned, in roughly the order they bit me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma has no system role.&lt;/strong&gt; Gemma's chat format only has user and model turns, so I fold the persona
into the first user turn myself instead of relying on how a server template treats a system message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small models need facts, not puzzles.&lt;/strong&gt; Asking "what does exit code 137 mean?" invites hallucination.
Telling the model "Omens you sense: killed by SIGKILL (137); this is the 2nd failure in a row" gives it
something true to be dramatic about. This is the single biggest quality improvement I made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few-shot beats adjectives.&lt;/strong&gt; Four example whispers in the persona did more than any amount of
"be eerie, be specific, be concise".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean the output defensively.&lt;/strong&gt; The model sometimes echoes the prompt format back ("make test
(exit 2) – …"), adds markdown, or rambles. A small &lt;code&gt;clean()&lt;/code&gt; step strips that and caps the whisper at two
sentences. An earlier version of that step also stripped the underscores out of
&lt;code&gt;notes_from_last_halloween.txt&lt;/code&gt;, which was its own small horror.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variety for the ofrenda.&lt;/strong&gt; Each remembrance gets a different "angle" (an offering left on the
altar, a tiny eulogy from its old friends in the codebase, marigold petals guiding it home…). Otherwise five epitaphs in a row all start
with "Rest now".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It isn't perfect. Ezequiel brings up his own death &lt;em&gt;a lot&lt;/em&gt;, and sometimes he invents history ("the&lt;br&gt;
last time you checked for disks"). For a ghost, I've decided that's in character.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy, carefully
&lt;/h3&gt;

&lt;p&gt;Shell history is about as private as data gets: hostnames, paths, typo'd passwords. Before anything is&lt;br&gt;
stored, séance redacts &lt;code&gt;*TOKEN*/*SECRET*/*PASSWORD*&lt;/code&gt; variables, &lt;code&gt;--password&lt;/code&gt; flags, &lt;code&gt;Authorization:&lt;/code&gt;&lt;br&gt;
headers, &lt;code&gt;user:pass@&lt;/code&gt; URLs, and GitHub, OpenAI, Hugging Face and AWS-style keys. There are tests for&lt;br&gt;
this. And then it only ever sends that redacted text to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Because the ghost reads your shell history.&lt;/strong&gt; I would never pipe every command I type, with its&lt;br&gt;
paths, hostnames and the odd password pasted in the wrong window, to someone else's API to get a joke&lt;br&gt;
back. With an open-weight model it never has to leave the box. séance talks only to&lt;br&gt;
&lt;code&gt;http://127.0.0.1:11434&lt;/code&gt;. You can unplug the network cable and the haunting carries on. I measured&lt;br&gt;
every number in this post on a machine with no GPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because it costs nothing to be haunted.&lt;/strong&gt; An always-on shell companion makes a model call after&lt;br&gt;
&lt;em&gt;every&lt;/em&gt; command. With a hosted API, that's a meter running all day. Locally it's 2.9 GB of RAM and a few&lt;br&gt;
seconds of CPU per whisper (about 3.6 s median for gemma3:4b, 1.8 s for gemma3:1b on my 8-vCPU VM).&lt;br&gt;
There's no key to leak and no bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because the model is swappable.&lt;/strong&gt; The 1B-vs-4B trade-off above was a decision &lt;em&gt;I&lt;/em&gt; got to make,&lt;br&gt;
with my own benchmarks, on my own hardware. Next year it'll be a different model, and switching is one&lt;br&gt;
environment variable: &lt;code&gt;SEANCE_MODEL=&amp;lt;anything Ollama can run&amp;gt;&lt;/code&gt;. No vendor decides that a fun but&lt;br&gt;
pointless app is no longer worth serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because things you depend on should outlive the company that made them.&lt;/strong&gt; That one fits the theme a&lt;br&gt;
little too well. An ofrenda is about remembering what's gone. I didn't want to build one that would join&lt;br&gt;
it the day an API was deprecated. Gemma's weights are on my disk. As long as Ollama, or llama.cpp, or&lt;br&gt;
whatever comes after them can load a GGUF, Ezequiel stays.&lt;/p&gt;

&lt;p&gt;Where it was &lt;em&gt;worse&lt;/em&gt; than a closed frontier model: latency and polish. A big hosted model would&lt;br&gt;
whisper faster and repeat itself less. But a faster ghost that phones home with your shell history&lt;br&gt;
isn't the ghost I wanted.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 (gemma3:4b by default, gemma3:1b optional) run locally through Ollama&lt;br&gt;
is the ghost. It writes every whisper and every ofrenda remembrance, grounded by deterministic omens&lt;br&gt;
and benchmarked on CPU-only hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of a .tech Domain:&lt;/strong&gt; séance's home page lives at &lt;a href="https://seance.tianx.tech" rel="noopener noreferrer"&gt;seance.tianx.tech&lt;/a&gt;, on a .tech domain I claimed through Hacktoberfest. It has the live demo, install steps and benchmarks.&lt;/p&gt;




&lt;p&gt;Thanks for reading. If you install it, run &lt;code&gt;seance ofrenda&lt;/code&gt; on Nov 1 or 2. The altar notices the date. 🕯️🌼&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— xt&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>SlothCommit 🦥: An Open-Weight AI That Writes Your Commit Messages So You Don't Have To</title>
      <dc:creator>x t</dc:creator>
      <pubDate>Tue, 20 Oct 2026 03:29:17 +0000</pubDate>
      <link>https://dev.to/t364170654/slothcommit-an-open-weight-ai-that-writes-your-commit-messages-so-you-dont-have-to-4db</link>
      <guid>https://dev.to/t364170654/slothcommit-an-open-weight-ai-that-writes-your-commit-messages-so-you-dont-have-to-4db</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;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every developer knows the moment. The code is done. The tests pass. And then you sit there, staring at an empty commit box, trying to summarize three hours of work in one line.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fix stuff&lt;/code&gt; you type. You delete it. &lt;code&gt;update code&lt;/code&gt;. Delete. &lt;code&gt;changes&lt;/code&gt;. Delete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SlothCommit&lt;/strong&gt; 🦥 removes that moment entirely. Paste your &lt;code&gt;git diff&lt;/code&gt;, and it hands you a proper commit message — Conventional Commits, a plain summary, or an emoji-flavored one — plus an optional PR description with &lt;em&gt;What changed / Why / Testing&lt;/em&gt; sections.&lt;/p&gt;

&lt;p&gt;The laziest part of your workflow, automated. Who gets to be lazier? Anyone who has ever typed &lt;code&gt;wip&lt;/code&gt; and pushed.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://commit.tianx.tech" rel="noopener noreferrer"&gt;https://commit.tianx.tech&lt;/a&gt; — the web UI is live on the &lt;code&gt;.tech&lt;/code&gt; domain. Click &lt;strong&gt;Try sample diff&lt;/strong&gt; to see the output format instantly. Full AI generation runs locally (see &lt;em&gt;Run it yourself&lt;/em&gt; below) — paste the output of &lt;code&gt;git diff --staged&lt;/code&gt;, pick a style, hit Generate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video demo:&lt;/strong&gt;&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/joemh79" rel="noopener noreferrer"&gt;
        joemh79
      &lt;/a&gt; / &lt;a href="https://github.com/joemh79/sloth-commit" rel="noopener noreferrer"&gt;
        sloth-commit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🦥 Paste your git diff, get a clean commit message + PR description — powered by open-weight AI
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/8509478/679234592-81c4bab9-8b10-440f-a6a7-9f18e6d34eed.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3OTI2NTQ2MjAsIm5iZiI6MTc5MjY1NDMyMCwicGF0aCI6Ii84NTA5NDc4LzY3OTIzNDU5Mi04MWM0YmFiOS04YjEwLTQ0MGYtYTZhNy05ZjE4ZTZkMzRlZWQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MTAyMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjEwMjJUMDczMjAwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDlkZWE4MWNkMjRmYTYxNTQwN2QzZTIwYzA2MDI0OGMwODIwOGYxM2Y0OWY4MDVlODVlZTY1ZDYwM2ExNWEwZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmcmVzcG9uc2UtY29udGVudC10eXBlPWltYWdlJTJGcG5nIn0.zGp-_4_ElFOQRpGKJXEp0nx5cp6d-ltgZWQzHzoDHHI"&gt;&lt;img width="1672" height="941" alt="1000002172_2_uh63" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F8509478%2F679234592-81c4bab9-8b10-440f-a6a7-9f18e6d34eed.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3OTI2NTQ2MjAsIm5iZiI6MTc5MjY1NDMyMCwicGF0aCI6Ii84NTA5NDc4LzY3OTIzNDU5Mi04MWM0YmFiOS04YjEwLTQ0MGYtYTZhNy05ZjE4ZTZkMzRlZWQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MTAyMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjEwMjJUMDczMjAwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDlkZWE4MWNkMjRmYTYxNTQwN2QzZTIwYzA2MDI0OGMwODIwOGYxM2Y0OWY4MDVlODVlZTY1ZDYwM2ExNWEwZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmcmVzcG9uc2UtY29udGVudC10eXBlPWltYWdlJTJGcG5nIn0.zGp-_4_ElFOQRpGKJXEp0nx5cp6d-ltgZWQzHzoDHHI" class="js-gh-image-fallback"&gt;&lt;/a&gt;
[中文](./README.zh-CN.md) | English
&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SlothCommit 🦥&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Never stare at a commit box again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Paste your &lt;code&gt;git diff&lt;/code&gt;, get a clean Conventional Commit message + PR description — generated by open-weight AI.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://commit.tianx.tech" rel="nofollow noopener noreferrer"&gt;Live Demo&lt;/a&gt; · &lt;a href="https://github.com/joemh79/sloth-commit#" rel="noopener noreferrer"&gt;DEV Post&lt;/a&gt; · &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19" rel="nofollow"&gt;Hacktoberfest Week 3: Slow and Steady&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

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

  
    
    &lt;span class="m-1"&gt;33baf99319051ec48d065b319c9fdf321e22e6ddd20616211c5bf900a3748e78.mp4&lt;/span&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;Writing commit messages is the most procrastinated 30 seconds in a developer's day. SlothCommit removes it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;git diff --staged&lt;/code&gt; in your terminal&lt;/li&gt;
&lt;li&gt;Paste the diff into SlothCommit&lt;/li&gt;
&lt;li&gt;Get back a proper commit message (Conventional Commits / Simple / Emoji style) + an optional PR description&lt;/li&gt;
&lt;li&gt;Copy, paste, done. Back to napping.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it's built&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Single zero-build &lt;code&gt;index.html&lt;/code&gt; — no framework, no bundler&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Cloudflare Pages Function (&lt;code&gt;functions/api/generate.js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: &lt;a href="https://ai.meta.com/llama/" rel="nofollow noopener noreferrer"&gt;Llama 3.1 8B Instruct&lt;/a&gt; (open weights) via &lt;a href="https://developers.cloudflare.com/workers-ai/" rel="nofollow noopener noreferrer"&gt;Cloudflare Workers AI&lt;/a&gt; — no API keys, no per-token billing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt;: Deployed on &lt;code&gt;commit.tianx.tech&lt;/code&gt; (a &lt;code&gt;.tech&lt;/code&gt; domain)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why open&lt;/h2&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/joemh79/sloth-commit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Zero-build frontend (one &lt;code&gt;index.html&lt;/code&gt;), one Cloudflare Pages Function for the API, and an open-weight model doing the thinking. The whole thing is small enough to read over coffee.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: a single zero-build HTML file — no framework, no bundler, no &lt;code&gt;node_modules&lt;/code&gt; folder the size of a small moon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend (local)&lt;/strong&gt;: a tiny Python server (&lt;code&gt;local_server.py&lt;/code&gt;, ~100 lines) that forwards prompts to Ollama. No Cloudflare lock-in for the AI path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: &lt;strong&gt;Gemma 3 (4B)&lt;/strong&gt; — open weights from Google — running locally via &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. No API key to manage, no per-token bill to dread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt;: the web UI is deployed on &lt;code&gt;commit.tianx.tech&lt;/code&gt; (Cloudflare Pages, free tier), because a tool this lazy deserves a proper home.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting engineering problem wasn't the generation — it's the &lt;em&gt;guardrails&lt;/em&gt;. Left alone, a small model will happily invent file names and add cheerful commentary. The prompt enforces: subject line only per the format, body bullets explain &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; (never &lt;em&gt;how&lt;/em&gt;), and output nothing but the message itself.&lt;/p&gt;

&lt;p&gt;A note on the architecture: the deployed site serves the UI and a one-click sample. Full AI generation runs against local open-weight models — the same code path, zero data leaves your machine. The deployed Cloudflare Pages Function version is in the repo for anyone who wants to wire it to Workers AI on their own account.&lt;/p&gt;

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

&lt;p&gt;Your diff is your most honest code. It's the half-finished refactor, the internal API you haven't announced, the proprietary logic your company hasn't shipped yet.&lt;/p&gt;

&lt;p&gt;Sending that to a closed, per-token API means trusting a third party with your unfinished work — and paying them for the privilege, every single commit, forever.&lt;/p&gt;

&lt;p&gt;Open weights change the math:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by architecture&lt;/strong&gt;: the model runs on infrastructure you control (or your own machine, if you self-host this). Your uncommitted code isn't training data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up once, pay never&lt;/strong&gt;: no subscription, no token anxiety. A commit message costs fractions of a cent on Workers AI's free tier — or nothing on a local Ollama install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline&lt;/strong&gt;: point the same frontend at a local open-weight model and SlothCommit works on a plane, in a tunnel, during an outage. A closed API can't say that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sloth doesn't pay rent on its tree. Your commit messages shouldn't require a subscription either.&lt;/p&gt;

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

&lt;p&gt;Measured honestly, from my own week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before&lt;/strong&gt;: ~40 seconds of staring + typing + rewriting per commit message, ~15 commits a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After&lt;/strong&gt;: paste, glance, copy — under 10 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saved&lt;/strong&gt;: roughly &lt;strong&gt;7 minutes a day&lt;/strong&gt;, ~35 minutes a week. Small numbers — which is exactly the point. Sloth-sized savings, every day, forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PR description generator is the bigger win: a structured &lt;em&gt;What/Why/Testing&lt;/em&gt; write-up used to take me 3–5 minutes of grudging typing. Now it's one click and a quick review.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of a .tech Domain&lt;/strong&gt; — SlothCommit lives at &lt;a href="https://commit.tianx.tech" rel="noopener noreferrer"&gt;commit.tianx.tech&lt;/a&gt;, deployed on the &lt;code&gt;.tech&lt;/code&gt; domain I picked up during Hacktoberfest.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Blog-OS: An Open-Source Blog System with a Local AI Writing Assistant</title>
      <dc:creator>x t</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:05:27 +0000</pubDate>
      <link>https://dev.to/t364170654/blog-os-an-open-source-blog-system-with-a-local-ai-writing-assistant-4cde</link>
      <guid>https://dev.to/t364170654/blog-os-an-open-source-blog-system-with-a-local-ai-writing-assistant-4cde</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Blog-OS&lt;/strong&gt; is a complete personal blog system with a built-in AI writing assistant. Article management, Markdown editor, site settings, comment moderation — plus an AI helper that suggests excerpts, tags, and titles.&lt;/p&gt;

&lt;p&gt;The twist: the AI runs entirely on your own machine via Ollama and Gemma 3 4B (open-weight model from Google). No API keys. No per-token billing. No drafts leaving your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Side Hustle
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Who would pay:&lt;/strong&gt; Independent bloggers, newsletter writers, and small content creators who publish regularly in Chinese.&lt;/p&gt;

&lt;p&gt;The math is simple. A managed blog with AI features costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ghost Pro + AI add-ons: $15+/month&lt;/li&gt;
&lt;li&gt;WordPress hosting + AI plugins: $10+/month + API costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Blog-OS costs $0/month to run (Cloudflare free tier + local model). The AI assistant saves ~10 minutes per post on the tedious parts — writing excerpts, picking tags, brainstorming titles. For someone publishing 3x/week, that's 2 hours/month saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business:&lt;/strong&gt; Host it for non-technical bloggers at $5/month. They get a professional blog with AI assistance. I get margin because the AI compute is on &lt;em&gt;their&lt;/em&gt; machine, not mine. No GPU bills. No API costs to pass through. The open-weight model is what makes the unit economics work — a closed API would eat the entire $5 in token fees.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/joemh79/blog-os" rel="noopener noreferrer"&gt;https://github.com/joemh79/blog-os&lt;/a&gt; (MIT licensed)&lt;/li&gt;
&lt;li&gt;Deploy &lt;code&gt;frontend/&lt;/code&gt; to Cloudflare Pages — zero build step, 15 minutes&lt;/li&gt;
&lt;li&gt;Admin panel at &lt;code&gt;/admin/&lt;/code&gt; — write in Markdown, click the AI assistant buttons&lt;/li&gt;
&lt;li&gt;AI needs Ollama locally: &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;The open core:&lt;/strong&gt; Three API endpoints proxy to Ollama's local API (&lt;code&gt;http://localhost:11434&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/ai/excerpt&lt;/code&gt; — Chinese summary from Markdown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/ai/tags&lt;/code&gt; — 3-5 relevant tags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/ai/title&lt;/code&gt; — title suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompts tuned for Chinese blog content. If Ollama isn't running, the UI explains how to set it up instead of crashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: Pure HTML/CSS/JS, no build. Single &lt;code&gt;config.js&lt;/code&gt; for all personal info.&lt;/li&gt;
&lt;li&gt;Backend: Single-file Cloudflare Worker + D1. Auth, CRUD, settings, AI proxy — ~600 lines, zero npm dependencies.&lt;/li&gt;
&lt;li&gt;63 backend tests passing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters for This Build
&lt;/h2&gt;

&lt;p&gt;Three places where "open" isn't a philosophy — it's the reason this works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The model makes the business possible.&lt;/strong&gt; If the AI required a paid API, every excerpt/tag/title would cost tokens. At $5/month pricing, API fees would exceed revenue. Gemma's open weights mean the AI runs free, forever, on the user's hardware. Closed AI kills this business model; open AI enables it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy is the feature.&lt;/strong&gt; Blog drafts are unfinished thoughts — half-formed opinions, personal stories, unannounced projects. Sending those to a cloud API for a tag suggestion is a privacy leak. Local inference means the AI sees your draft and nothing else does. For writers, that's not a nice-to-have; it's the reason to choose this over a cloud alternative.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No vendor lock-in on the stack.&lt;/strong&gt; Every line is readable. No minified blobs, no telemetry, no proprietary APIs. If Cloudflare changes pricing, the Worker runs on any compatible runtime. If Gemma gets surpassed, swap in any Ollama-compatible model. The user owns their blog completely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Designed and built with Muse (AI assistant) through agent sessions. The 63 backend tests, the AI prompt tuning, and the admin UI were all developed iteratively with AI assistance.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hacktoberfest Week 2: Side Hustle&lt;/strong&gt; (primary)&lt;/li&gt;
&lt;li&gt;Best Use of Gemma (Gemma 3 4B via Ollama, local inference)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>sidehustle</category>
    </item>
    <item>
      <title>HI</title>
      <dc:creator>x t</dc:creator>
      <pubDate>Fri, 16 Oct 2026 07:24:25 +0000</pubDate>
      <link>https://dev.to/t364170654/hi-1836</link>
      <guid>https://dev.to/t364170654/hi-1836</guid>
      <description></description>
      <category>hello</category>
    </item>
  </channel>
</rss>
