This is a submission for the Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine.
The first time I ran it for real, Gemma said nothing at all. Eight commands, eight empty lines. The ghost was there, it just wouldn't speak. That turned out to be the most honest bug I've ever hit in a Halloween project, and I'll get to it.
What I Built
haunted-terminal is a ghost that moves into your zsh, and it doesn't just leave comments. It haunts the place.
When you open a terminal:
- The ghost rises. A full-screen ASCII ghost fades in out of the dark, bobs, blinks, and fades away.
- The music starts. A horror soundtrack loops for as long as any haunted tab is open, and stops when you close the last one.
- The welcome card appears. The same ghost floats beside your status (is Gemma awake, is the music on, where you are) until you press a key.
- Then it stays. About one command in three, it whispers a line about what you just did. Every few commands, something in the room makes a sound: a whispered boo right behind your ear, a wolf, a witch cackling, chains on stone.
The ghost is a programmer who died in 1987 at a VT100 terminal, halfway through a COBOL migration. He is cold, a little jealous of the living, and he never gives advice.
He also has one job that is not a joke. Before you press Enter on rm -rf, git push --force, git reset --hard, DROP TABLE or curl ... | sh, he stops you and asks if you're sure.
And on November 1 and 2, for DÃa de los Muertos, the welcome card turns marigold, and the ghost builds a small ofrenda for everything you deleted that day.
The ghost is Gemma 4 E2B, running on my laptop through llama.cpp. Nothing I type goes to a server.
Demo
A two-minute tour: the ghost rising, the welcome card, real Gemma whispers on git status, npm test and a typo, a wolf howl from the ElevenLabs sounds, the rm -rf guard, the DÃa de los Muertos card, and how to install it. Turn the sound on.
Every ghost line in that picture is real Gemma output. A few more from the test runs:
$ git log --oneline -5
👻 Five commits remain, the ghosts of my forgotten logic.
$ git checkout feature/login (exit 128)
👻 The branch is gone, just like my last memory of warmth.
$ git status
👻 The changes remain, just like the dust on this old screen.
$ npm test (exit 1)
👻 That error is familiar; the same ghost in the machine.
Website: haunted-terminal-lovat.vercel.app, with the same ghost animated in your browser and a button to play the soundtrack.
Code
ankitjha1405
/
haunted-terminal
A local Gemma ghost that haunts zsh
👻 haunted-terminal
A ghost moves into your terminal. Open a tab and it rises out of the dark while a horror soundtrack starts. Then it stays. It whispers about the commands you run, and stops you before you rm -rf.
The ghost is Gemma 4, running on your own machine through llama.cpp. Your shell history never leaves your laptop.
 Website · Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine
Every ghost line above is real Gemma output from a laptop, about half a second each.
Hacktoberfest 2026 · Week 4: Ghost in the Machine
This is my entry for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine. The week asks for a spooky build with open-source AI at its core. Here the ghost is the open model: Gemma 4, running on your own laptop, gives the…
Two files do the work, with no dependencies beyond Python's standard library: ghost.zsh (the hooks) and ghost (everything else).
How I Built It
The architecture is small. ghost.zsh hooks into zsh in two places. After every command, precmd hands the command and its exit code to the ghost. Before Enter, a wrapped accept-line widget runs the safety check. When a terminal opens, it starts the music and plays the intro. The ghost script talks to llama-server's OpenAI-compatible endpoint, draws the animations, and manages the sound.
Most of what I learned came from things going wrong.
1. The ghost that wouldn't speak
That first live run: eight commands, eight empty replies. The raw response showed why. Gemma 4 starts thinking as soon as it gets a system prompt, and it spent all 60 of its tokens reasoning about how to be a ghost:
"content": "",
"reasoning_content": "Thinking Process:\n\n1. **Analyze the Request & Persona:**..."
One flag fixed it: "chat_template_kwargs": {"enable_thinking": false}. A ghost should blurt, not deliberate. There's now a test so this can't quietly come back.
2. "Another…"
Once it spoke, it started a lot of lines the same way: Another triumph. Another failure. Another branch lost. So I measured it. I wrote eval_ghost.py, which runs 40 everyday commands (git, npm, docker, typos, failed builds) through the ghost three times and checks every reply.
Then I added four example replies to the prompt, told it to notice one concrete detail of the command, and skipped commands like ls and cd that aren't worth a ghost's breath. Here's the before and after, 120 replies each:
| Check | Before | After |
|---|---|---|
| Replied at all | 100% | 100% |
| Under 20 words | 100% | 100% |
| Gave no advice | 100% | 100% |
| No emoji, quotes or hashtags | 100% | 100% |
| Didn't open with "Another" | 82% | 97% |
| One sentence | 82% | 88% |
| Median reply time | 0.7 s | 0.7 s |
Most of the remaining "two sentence" replies are fragments like "Five commits. Such a brief, pathetic life for code.", which honestly suit him. The examples cost no speed: llama-server caches the shared prompt, so old and new run at the same median.
3. The safety check is not the model
A warning before rm -rf has to be instant and right every time, so it's plain pattern matching. Gemma only does the part where being creative is the point. The same script tests it:
| Safety check | Result |
|---|---|
| Dangerous commands caught | 19/19 |
| Harmless look-alikes left alone | 19/19 (was 17/19) |
The two false alarms were grep -r 'DROP TABLE' migrations and echo 'drop table' > notes.txt. Now DROP only counts inside a database client like psql or mysql.
4. Secrets never reach the model
Even a local model shouldn't see your keys, and the ghost could repeat one on screen. Before a command reaches Gemma, anything that looks like a secret is blanked: export OPENAI_API_KEY=sk-..., --password=..., Bearer tokens, https://user:token@..., and GitHub, Slack and AWS key formats. npm test stays npm test. echo $GITHUB_TOKEN stays too, because a variable name isn't a secret.
5. A voice that talked too much
My first version had ElevenLabs read every ghost line aloud. It was too much within a minute. A ghost that talks constantly isn't scary. A sound you didn't expect is.
So now the shell keeps a counter. After every one to three commands (two on average, which I checked over 30 commands), it plays one of seven horror sounds made with the ElevenLabs sound effects API: a breathy boo, a wolf howl, a witch's cackle, a creaking door, a moan, dragging chains, overlapping whispers. Each is generated once and cached. After that they cost no credits, work offline, and ElevenLabs never sees a single command.
6. Colours that didn't exist
The welcome card's title came out garbled on my Mac, with some letters invisible. macOS Terminal.app can't display 24-bit colour, and I had used it for a gradient. The card now uses only the 256-colour palette, with a solid white title and a violet base.
The rest
-
The ghost is a signed distance field: a circle for the head and a box with a wavy hem for the body, drawn in
@$%and lit by a violet and green halo. The website draws its hero with the same math in JavaScript. - The welcome card redraws in place 20 times a second until you press a key, and that key isn't typed into your prompt.
- One music player serves every tab. An atomic lock file stops two tabs from starting two players. (They did, the first time. Testing caught it.) Each shell registers itself, and the music stops when none are left.
- No server, no ghost. If llama-server is down, the ghost stays silent in milliseconds. It never blocks the shell.
-
Tests: 15 unit tests, plus an interactive zsh test that drives a real shell through a pseudo-terminal. It types
rm -rf, pressesn, and checks the folder survived. GitHub Actions runs them on every push.
I built this with Claude Code as my pair programmer.
Why Does Open Innovation Matter?
A terminal sees everything: file paths, hostnames, branch names, half-typed secrets, the command you ran at 2 a.m. I would never pipe that into a hosted API to get a joke back.
With an open-weight model I don't have to. Gemma runs on my machine, so:
- It works offline. On a plane, in a basement, with Wi-Fi off.
- My shell history stays mine. No account, no logs on someone else's server.
- It costs nothing to run. A ghost that comments hundreds of times a day would be a real bill on a paid API. Here it's zero.
-
No lock-in.
GHOST_URLpoints at any OpenAI-compatible server. Swapping Gemma for another open model is one flag to llama-server.
This only works because open models are now small and good enough to sit next to your shell and answer in under a second. A year ago this would have been a cloud API with a privacy policy. Now it's a file on my disk.
Prize Categories
- Best Use of Gemma: Gemma 4 E2B is the ghost, running locally through llama.cpp, with thinking turned off, example-driven prompting, and a measured before and after.
- Best Use of ElevenLabs: seven horror sound effects made with the ElevenLabs sound effects API, played at random every one to three commands and cached so they work offline.
- Best Use of GitHub Copilot: GitHub Actions runs the unit tests and a zsh syntax check on every push.
Music: "Horror Music" by kiravale, via Pixabay. Code: MIT. Demo video: built with the onetake motion skill (PolyForm Noncommercial), narrated with the open Kokoro TTS model. The terminal in it is redrawn from the ghost's own code, and every ghost line in it is real Gemma output.


Top comments (0)