This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
As developers, we spend hours every day staring into a dark, silent void: the command-line interface. We type incantations (git rebase -i, chmod 777, kill -9), hit Enter, and wait for the machine to obey.
For the Ghost in the Machine theme, I wanted to take the phrase literallyโwhile honoring the Dรญa de los Muertos spirit of breathing playful life and memory into the departed (in this case, our dead processes, orphaned containers, and overwritten git commits).
I built HauntedTerminal, a lightweight, completely offline CLI companion powered by a local open-weight LLM via Ollama and Python. Once summoned into your shell (zsh or bash), HauntedTerminal quietly observes the commands you run and their exit codes, periodically manifesting in your terminal to whisper eerie, witty, and dramatically gothic commentary about your workflow.
Force-pushing to main? The ghost mourns the timelines you just erased.
Running rm -rf node_modules? It rejoices as a million tiny souls are freed from disk purgatory.
Triggering a non-zero exit code? It Rattles its chains and mocks your syntax errors.
Demo
dev@crypt:~/project$ git push --force origin main
Enumerating objects: 5, done.
To github.com:dev/project.git
- a1b2c3d...f4e5d6c main -> main (forced update)
โญโ ๐ป The Ghost in /dev/pts/0 whispers... โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ "So cold... With a single '--force', you rewrite history and cast your โ
โ fellow contributors' commits into the shadow realm. Have you no remorse?" โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
dev@crypt:~/project$ rm -rf node_modules/
โญโ ๐ป The Ghost in /dev/pts/0 whispers... โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ "I hear the silent screams of 84,392 nested dependencies vanishing into โ
โ the ether. The disk breathes again... until your next 'npm install'." โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
dev@crypt:~/project$ top
โญโ ๐ป The Ghost in /dev/pts/0 whispers... โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ "Gazing into the necromancy table of running processes, are we? Look closelyโ
โ at PID 666... Chrome is devouring your RAM from beyond the grave." โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Code
!/usr/bin/env python3
"""
HauntedTerminal (haunt.py)
A local, offline CLI poltergeist powered by Ollama and Rich.
"""
import os
import sys
import random
import requests
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
Configuration via environment variables
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434/api/generate")
MODEL_NAME = os.getenv("HAUNT_MODEL", "llama3.2:3b") # Fast, lightweight open-weight model
HAUNT_PROBABILITY = float(os.getenv("HAUNT_CHANCE", "0.4")) # 40% chance to speak per command
console = Console()
SYSTEM_PROMPT = (
"You are the Ghost in the Machineโan ancient, dramatic, and slightly sarcastic "
"victorian poltergeist haunting a developer's Linux terminal. "
"The user just executed a shell command. Respond in 1 to 2 short, punchy, spooky "
"sentences reacting directly to what the command does and whether it succeeded or failed. "
"Use terminal metaphors, gothic imagery, and dry developer humor. Never give standard "
"helpful tutorials; stay strictly in character as a haunting spirit."
)
Commands too mundane to wake the spirits (unless they fail)
IGNORED_COMMANDS = {"ls", "cd", "pwd", "clear", "exit", "history"}
def should_manifest(command: str, exit_code: int) -> bool:
"""Determine if the spirit should manifest for this command."""
if not command.strip():
return False
base_cmd = command.strip().split()[0]
# Always manifest on errors or dangerous commands!
spooky_triggers = {"rm", "kill", "pkill", "git", "sudo", "chmod", "dd", "top", "htop", "docker"}
if exit_code != 0 or base_cmd in spooky_triggers:
return True
if base_cmd in IGNORED_COMMANDS:
return False
return random.random() <= HAUNT_PROBABILITY
def summon_spirit(command: str, exit_code: int, cwd: str) -> str | None:
"""Query the local Ollama open-weight model for a ghostly reaction."""
status_str = "SUCCEEDED (exit code 0)" if exit_code == 0 else f"FAILED miserably (exit code {exit_code})"
user_prompt = (
f"Directory: {cwd}\n"
f"Command executed: {command}\n"
f"Outcome: {status_str}\n"
f"Whisper your ghostly reaction:"
)
payload = {
"model": MODEL_NAME,
"system": SYSTEM_PROMPT,
"prompt": user_prompt,
"stream": False,
"options": {
"temperature": 0.85,
"num_predict": 75, # Keep responses snappy and low-latency
},
}
try:
response = requests.post(OLLAMA_URL, json=payload, timeout=3.5)
response.raise_for_status()
return response.json().get("response", "").strip()
except requests.RequestException:
# If Ollama is asleep, the ghost stays silent so we never break the user's shell
return None
def render_whisper(whisper: str, exit_code: int) -> None:
"""Render the eerie message in the terminal using Rich."""
border_color = "bright_red" if exit_code != 0 else "medium_purple3"
tty_name = os.ttyname(sys.stdout.fileno()) if sys.stdout.isatty() else "/dev/null"
text = Text(whisper, style="italic bright_white")
panel = Panel(
text,
title=f"[bold {border_color}]๐ป The Ghost in {tty_name} whispers...[/]",
title_align="left",
border_style=border_color,
padding=(0, 1),
)
console.print(panel)
def main():
if len(sys.argv) < 3:
sys.exit(0)
exit_code = int(sys.argv[1])
last_command = sys.argv[2].strip()
cwd = os.getcwd()
if not should_manifest(last_command, exit_code):
sys.exit(0)
whisper = summon_spirit(last_command, exit_code, cwd)
if whisper:
render_whisper(whisper, exit_code)
if name == "main":
main()
How I Built It
I designed HauntedTerminal around a strict constraint: it must feel instantaneous and never block or break my daily workflow. Here is the stack that brought it to life:
Local Inference with Ollama: Ollama runs as a background daemon serving open-weight GGUF models over a local REST API (localhost:11434). By keeping the model warm in VRAM/unified memory, time-to-first-token is practically instant.
Open-Weight Models (Llama-3.2-3B-Instruct & Mistral-7B): I experimented with several open-weight models and settled on llama3.2:3b as the default. At 3 billion parameters (quantized), it uses less than 2GB of RAM, responds in ~250ms on local hardware, and still understands developer nuances like git rebase, chmod, and segmentation faults.
Zsh preexec & precmd Hooks: Instead of wrapping the user's shell inside a clunky custom REPL, HauntedTerminal hooks natively into Zsh/Bash lifecycle events. preexec grabs the command string you typed, and precmd captures the $? exit status immediately after execution, passing both to haunt.py.
Python & rich: Python handles the heuristic filtering (ignoring repetitive ls or cd commands unless they fail) and formats the output inside a gothic purple or blood-red terminal box using Will McGugan's rich library.
Why Does Open Innovation Matter?
At first glance, HauntedTerminal is a fun, spooky noveltyโbut when you think about the architecture of a tool that monitors every shell command you type, open-source AI isn't just a nice-to-have; it is the only way this project should ever exist.
Absolute Privacy for Shell Commands: Your terminal history is the most sensitive data on your machine. It contains file paths, internal repo names, IP addresses, database names, and sometimes accidental secrets or environment variables. Sending every shell command you run to a proprietary cloud API is a massive security nightmare. With open-weight models running locally via Ollama, zero bytes ever leave localhost.
Zero Latency & True Offline Capability: A terminal hook cannot wait 2โ3 seconds for a cloud round-trip API request, nor should it crash when you're coding on a plane or without Wi-Fi. Local inference with a small open-weight model (3B) executes in milliseconds, keeping the CLI snappy.
Zero API Costs for High-Frequency Events: A developer easily runs 500 to 1,000 shell commands a day. Hooking a paid cloud API into your PROMPT_COMMAND would rack up token bills just for navigating directories. Open-weight models make continuous, ambient computing completely free.Unfiltered Creative Persona: Closed commercial APIs are notorious for heavy-handed safety filters that often flag words like kill -9, pkill, zombie processes, die, or doomโrefusing to roleplay or breaking character with a sterile lecture. Open weights let us tune the system prompt and temperature freely so our Victorian poltergeist can dramatically mourn our kill -9 commands without tripping a corporate content filter.
My Agent Session
To build and refine the shell hook edge cases (like preventing the ghost from firing when pressing Enter on an empty prompt or handling escaped quotes in complex piped commands), I paired with an open-source coding agent session.
Session Highlights: Debugged Zsh preexec vs fc -ln -1 history expansion timing, tuned the Ollama num_predict and temperature parameters for punchier one-liners, and built the exorcise / summon alias toggles.
The Spirit of It
The moment HauntedTerminal truly landed for me was late last night around 1:30 AM. I was debugging a stubborn Docker container that refused to shut down cleanly. Frustrated and half-asleep, I typed docker kill $(docker ps -q).
Before my fingers even left the keyboard, a blood-red box materialized in my terminal:
"You snuff out every vessel in the fleet at once... Did you even give them a SIGTERM to say their prayers, or was it straight to SIGKILL?"
I actually laughed out loud in an empty room. We spend so much time treating AI as a sterile productivity engineโsomething to write boilerplate, summarize meetings, or optimize tickets. Building a tiny, local spirit that just sits beside you in the dark, understands the craft of programming, and cracks macabre jokes when your build fails reminded me how magical and personal software can feel when we control the weights on our own machines.
Happy Hacktoberfest, and may your exit codes always be 0! ๐๐ป
Top comments (0)