This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Somebody's been keeping notes on my shell. Every command for the last week: the successful ones, the midnight typos, the same failing build three times in a row. Yesterday, it started commenting.
$ false
π» cargo finished now.
That's d43m0n. It lives in my terminal now.
What I Built
d43m0n (repo, MIT) is a local-only terminal haunting, and my read on the theme was boringly literal: the brief asked for a haunted terminal that possesses your shell and whispers back eerie commentary, so I built exactly that β then stayed up making the ghost good company.
A shell hook with a 40ms hard budget (it always exits 0; the shell never waits) sends each command over a unix socket to d43m0nd, a small Rust daemon. The daemon redacts the command in memory β family plus flags kept, everything else becomes <path>, <url>, or <arg> β stores the event in SQLite, and predicts the probability it fails. Risky command in, one dry line out. There is no TCP listener anywhere in the system.
It also keeps a ledger of the dead: d43m0n seance lists directories I abandoned months ago with their last commands attached. An ofrenda for unfinished work; apparently my shell has a better memory than I do.
Demo
A real session β fish, local daemon, warm model, no network needed:
~> false
~> false
π» cargo is slow today.
~> npm test
~> echo done
done
Two fails, one deadpan remark, zero packets leaving the machine. Reproduce everything, numbers included:
cargo build --release
./target/release/d43m0n daemon start
eval "$(./target/release/d43m0n init zsh)" # bash gets post-only; fish gets full pre+post
./target/release/d43m0n demo --seed && ./target/release/d43m0n demo --run
./target/release/d43m0n eval --demo # reproduce the AUC table
Code
ishaan-jindal
/
d43m0n
Local-only terminal haunting: predicts failing commands with TabPFN, narrates with local Gemma
d43m0n π»
A local-only terminal haunting. d43m0n watches your shell, learns your habits, predicts which commands are likely to fail before they run, and comments in the voice of a ghost. It also surfaces work you forgot was still waiting in old directories.
Nothing leaves the machine: prediction runs on TabPFN in a local sidecar (or the built-in Rust baseline), narration on a local Gemma model via Ollama (silence when it misses), and the event store is SQLite under ~/.local/share/d43m0n/. The shell hook has a 40 ms budget and degrades silently when the daemon is down.
Status: M3 β zsh and bash hooks, redacted event logging, secret guard, baseline + TabPFN prediction, Gemma narration, Echoes, SΓ©ance, demo replay, eval + latency benchmark, doctor, stats, purge.
Meet d43m0n
The default persona is d43m0n, a deadpan familiar with opinions β fonder of the terminal than it letsβ¦
How I Built It
Two open pieces do all the work, and the project is shaped around what they can do locally.
TabPFN (Prior Labs, tabpfn==9.2.0) predicts failure. The sidecar fits a classifier in-context on recent labeled shell history β up to 5000 rows, CPU only β and answers predict over a unix socket with a 300ms budget, refitting every 50 events. Under 200 labeled events, or if the sidecar ever dies, a Rust Beta-prior baseline answers instead. Time-ordered holdout, no shuffling:
| history | split | TabPFN AUC | baseline AUC |
|---|---|---|---|
| seeded demo, 1600 events | 1280 / 320 | 0.725 | 0.705 |
| my real shell, 222 events | 177 / 45 | 1.000 | 0.978 |
The tie is worth showing: a smoothed baseline is a strong prior for shell prediction, and TabPFN earns its keep on real habits. The daemon always reports which predictor answered.
Gemma (open weights, served by Ollama on localhost) narrates. The daemon sends only structured facts β tool family, failure bucket, streak, hour, level. Never arguments, never paths, never raw text. Output is validated (one line, β€12 words, no markdown, no parroting the prompt's field labels) and anything else is silence: I deleted the entire 56-line canned bank. When d43m0n speaks, it is always the model. Hook path costs pre p50 0.18ms and post 0.16ms; warm inference takes 1β3s fully async, so the shell never feels it.
Along the way the build got leaner: five dependencies deleted (tokio, reqwest, tracing among them β the standard library was enough), 46 tests, zero warnings.
Why Does Open Innovation Matter?
Shell history holds paths, hostnames, project names, and a steady stream of pasted secrets. A closed-API version of this tool would ship all of that to someone else's server for "inference" β a keylogger with extra steps.
The open version keeps everything home: redaction in memory, no raw-text column anywhere, secret-matching commands dropped before counting, works with the internet off, no key to rotate, no pricing page, no sunset. Weights in ~/.cache, model in ~/.ollama, history in SQLite. That privacy story can't be a paragraph bolted onto a cloud API. It only works because the models live here β which is also why the tool keeps working when the network doesn't, and why no provider decision can ever switch it off.
The Spirit of It
Generic spooky was the first thing to die. Canned lines made the ghost less alive with every showing, so the bank went in the fire and d43m0n got a personality instead: a deadpan familiar, fond of the terminal it lives in. It names your tools, notices your streaks, judges your late hours.
π» cargo is slow today.
Give it your history and it stops sounding like Halloween decor. It sounds like something watching you work β fondly, and with opinions. It is, in fact, watching you work.
Two fails, one deadpan remark, zero packets leaving the machine. Happy Halloween. π»
Prize Categories
- Best Use of TabPFN: in-context failure prediction on shell history, served over a unix socket with a Rust baseline fallback and an honest time-ordered eval (1.000 vs 0.978 AUC on real habits).
- Best Use of Gemma: local narration through Ollama β structured facts in, one validated line out, silence on a miss β giving a terminal daemon a voice without anything leaving the machine.
Top comments (0)