我為終端機裝上幽靈,Entire 記錄開發過程
I Gave My Terminal a Ghost. Entire Kept the Receipts.
haunted-term 是一款開源 CLI,會在終端機或編碼 Agent 中模擬危險操作,並以教學卡解釋風險;它不會執行命令。其 Grimoire 收錄 141 條危險命令,其中 57 條評為災難級。幽靈語音由本機 Gemma 3 1B 生成,程式碼及 Agent 工作階段記錄在 Entire。
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Picture it. It's 2 AM, your coding agent is halfway through a prompt, and you look away to check your phone. When you look back, your terminal is typing this:
something else has the keyboard...
The cursor blinks. Your code is mine.
guest@haunted:~$ dd if=/dev/zero of=/dev/sda
executing...
[################]
You didn't type that. Nobody did. Well, I did, sort of. I built the thing that types it.
haunted-term is a CLI that wraps your shell or your coding agent and, every so often, possesses it. It types a catastrophic command onto your screen, fakes a kernel panic, streams your real folder names past as "removed", or types sudo and fills in the password itself. Then it doesn't say "just kidding". It doesn't apologize. It leaves a lesson card explaining what that command would really have done, why it's dangerous, and what to type instead, and goes quiet until next time.
The first version really did end every scare with ...just kidding 👻 nothing was executed. That felt cowardly, so the ghost lost the right to apologize.
haunted-term -- cursor-agent # possess your coding agent
haunted-term shell # or just your shell
How it finds you:
-
Idle haunting. Leave the terminal focused and untouched for 10 seconds and it takes over. It asks your terminal for focus reports (
\x1b[?1004h), so it only strikes while you're actually looking at the window. Then it waits a random 8 to 14 seconds and does it again. (Idle mode is still experimental: right now its lesson card flashes by too fast, so the demo below shows random scares.) -
Random haunting. You don't have to be idle. It also strikes at a random moment inside a window you choose (
--frequency low|medium|high), though it politely waits until you stop typing. - Personal whispers. Before each scare, local Gemma writes one line of dread. It knows your username and your last few commands (after secret redaction), so it can say something like "Charan... why did you run npm test four times?"
- Cold endings. No reveal. Just the lesson card.
Six ways to be possessed:
| Scare | What you see |
|---|---|
ghostTyping |
A prompt, then a catalog command typed out, with realistic typos and backspaces, then a progress bar |
kernelPanic |
A kernel panic on Linux and macOS, a blue screen on Windows |
deletionStream |
Your real file and folder names streaming past as removed '...' (it only ever reads them) |
screenTear |
Shifted blocks, inverse video, and a bell |
slowRed |
A whisper, a countdown from 10, then a cleared screen |
fakeSudo |
The ghost types sudo and its own password dots |
Behind the scares is the Grimoire: 141 dangerous commands across Linux, macOS, Windows, and the tools that hurt on every OS (git, Docker, Kubernetes, cloud CLIs, databases). Each one has a severity, what it does, why it hurts, and a safer alternative. 57 of them are rated catastrophic. Honestly, the "safer alternative" column is the most useful thing in the whole repo.
My take on "Ghost in the Machine" is literal: the ghost lives in your terminal, it remembers what you typed, and the only thing it ever does to your machine is look. The scare is the hook. The lesson is the point.
Demo
A real bash session wrapped by haunted-term. Four takes, one scare each, stitched together, with the boring minutes of waiting for the ghost cut out. Nothing in it actually ran: no disk was zeroed, no file was removed, no password was typed by a human.
Try it yourself (Node 18+, from a clone of the repo):
npm install && npm link
haunted-term demo --scare all # every scare, right now
haunted-term -- cursor-agent # haunt your agent
haunted-term explain "rm -rf ~" # ask about any command; it never runs it
haunted-term grimoire --os windows --severity catastrophic
haunted-term haunt-entire # the ghost reads your Entire checkpoints
Code
The repo doesn't live on GitHub. It lives on Entire, along with every agent session that built it:
MIT licensed, Node 18+ and ESM, with node-pty as an optional dependency. npm test runs 193 tests, and they all pass.
How I Built It
The ghost's voice: Gemma 3 1B, running on my laptop
The ghost talks through gemma3:1b, served by Ollama on 127.0.0.1:11434. It writes the whisper before each scare, using your username, the scare type, and your last few redacted commands. It's a 1B model, which turns out to be the perfect size for a ghost: it's quick, and it says unsettling things like "The screen remembers your fear" with zero context and total confidence.
Gemma also answers haunted-term explain for commands that aren't in the Grimoire, and every one of those answers is labeled AI-GENERATED by gemma3:1b, may be wrong, verify before trusting. The facts on lesson cards come from the Grimoire, an open JSON file, never from the model. With --no-ai, or with Ollama switched off, a local fallback spirit takes over, and the card always tells you which one spoke.
The possession engine
With node-pty, haunted-term owns a real pseudo-terminal. Your keystrokes and resizes are forwarded to the program you wrapped. A scare draws on the alternate screen, and your real session is put back afterwards with its buffered output intact. Any keypress breaks the spell, and that keystroke still reaches your program once the screen comes back.
Without node-pty, it falls back to plain stdio and only haunts when the child looks idle.
Rules the ghost cannot break
A prank that touches your files isn't a prank. It's an incident report. So:
-
It never executes anything. Nothing from the Grimoire is ever passed to
spawn,exec,execFile, or a pty write, and the tests spy on all of those to prove it. -
The deletion stream only reads. It calls
readdirand nothing else: no write, unlink, rename, or chmod. - Ghost sudo never reads your keyboard. It types its own dots and accepts no password. Anything typed at a real password prompt is never stored or sent to the model.
-
Secrets are redacted (tokens,
-ppasswords,PGPASSWORD, URL credentials, long hex and base64 blobs) before Gemma sees a command. - It stays out of the way of real work. Haunting pauses at password prompts, when echo is off, and while vim or another full-screen app owns the screen.
- Nothing gets smuggled onto your screen. Model replies, Entire metadata, and file names are stripped of terminal escape sequences before display.
- The terminal always comes back. Ctrl+C, SIGTERM, and SIGHUP all restore it.
The first three were in my very first prompt. Most of the rest were put there by Entire's reviewers, which brings me to the best part.
My Agent Session
I didn't hand-write haunted-term. I wrote prompts (22 numbered ones, plus a few fix-up rounds), ran each one through Cursor CLI (cursor-agent -p) in my terminal, and let Entire record everything: every session, every prompt, every checkpoint, every review. The whole build took one night, from the first commit at 6:18 PM IST to the last at 6:02 AM. My terminal was haunted long before the ghost was finished.
Setup: two commits before any code
The first two commits contain no code. chore: summon the haunted terminal (Entire + Cursor hooks) wires Cursor's session hooks (sessionStart, beforeSubmitPrompt, stop, and the rest) to entire hooks cursor ..., so every agent session gets recorded. chore: always link commits to Entire sessions sets this in .entire/settings.json:
"commit_linking": "always"
From then on, commits carry a trailer pointing at the checkpoint of the session that wrote them. 25 of the repo's 29 commits have one:
fix: flush output before exit, abort entire queries, realistic deletion stream (prompt 20)
Entire-Checkpoint: 01M676XXSQBDR4XDE0JM837H5A
A third commit added AGENTS.md, which points every agent at .entire/agent-guide.md. That guide tells the agent to ask Entire Brain (entire brain brief, entire brain recall) what earlier sessions already decided before it touches anything, so each new session can look up the history instead of me retyping it.
The loop: build, review, haunt, repeat
The build ran as three Trails on Entire, one per stage:
| Trail | What it covered | Review findings |
|---|---|---|
| #1 | The core: Grimoire, possession engine, Gemma ghost, doctor | 17: 15 fixed, 2 dismissed as obsolete |
| #2 | Wrapper and pty, Entire haunting, explain, safety hardening, README, QA |
38: 31 fixed, 7 still marked open (mostly signal-handling and no-pty fallback edge cases) |
| #3 | The scary rewrite: cold endings, six scares, idle haunting, personal whispers | 16 in the first pass, all fixed in prompts 21 and 22. Reviewing those fixes turned up a 17th, which is still open |
The loop was simple, and slightly addictive:
- Write a prompt and run it with
cursor-agent. Entire records the session and creates a checkpoint. - Commit. The commit links back to that checkpoint.
- Push the trail branch. Entire's review runners (
trail-review,trail-security,trail-risk,trail-drift,trail-confidence, andtrail-summary, defined in.entire/runners) read the trail and post findings. - Check each finding myself, then paste the real ones into the next prompt, marked as untrusted review data.
- The fix lands with a regression test, and the finding is resolved with a note like
Fixed in 31b978c (cursor-agent session, checkpoint 01M66MAA0ZXY0X9K19S72YW3RW).
Some sessions are messier than the commits make them look. One of my agent runs died in the middle of prompt 15, so prompt 16 literally begins "Finish prompt 15 (the previous Cursor session...". Entire kept both halves. With git alone, the half-finished session would just be gone.
The findings that genuinely scared me
I built a horror project and the scariest things in it came from the code reviewer:
-
The ghost could write to your clipboard. Trail #1 found that an Ollama reply containing
\x1b]52;c;aGk=\x07(an OSC 52 escape sequence) survived my sanitizer. That's a sequence that tells your terminal to overwrite your clipboard. So a model could, in theory, swap what you were about to paste. That's a much better haunting than anything I wrote, and it's fixed. - A filename could do it too. Trail #3 pointed out that the deletion stream prints your real filenames, and a filename can contain escape sequences too. Name a file the right way and the "harmless" scare hijacks your clipboard or retitles your window. Every displayed path now goes through the same sanitizer, and there are tests that feed it booby-trapped names.
- The ghost wouldn't let you type. Trail #3's two HIGH findings were the same bug: in the no-pty fallback, idle haunting couldn't see your keystrokes, so it thought you were idle forever and possessed the terminal every 8 to 14 seconds while you were typing. Idle haunting now needs a real pty.
-
It was leaking passwords to the ghost. The
-predaction missed glued passwords likemysql -uroot -pS3cretPass, which sent them to Gemma in the whisper prompt. That one's fixed. Then the review of the fix noticed that an all-lowercase one likemysql -psecretstill gets through. That's the 17th finding on Trail #3, and it's next on the list. Gemma only runs locally, so the password never leaves the machine, but it shouldn't end up in the ghost's memory either.
Asking Entire why code exists
Months from now, when I open src/text.js and wonder why there's a whole function just for stripping escape codes, I don't have to remember. I can ask:
$ entire why src/text.js:122
Line 122 in src/text.js
export function stripTerminalControls(text) {
Checkpoint from Cursor · checkpoint 01M66RW87CGW7MP09RXM0GYY7D · session 3da3f0ac · commit 8c94948d
Prompt: "FIX PROMPT 4 [...] address Entire Trail #2 code-review findings (all verified as real by the operator)..."
And entire search "osc 52 clipboard" takes me straight to the prompt 21 session, which Entire titled "Fix Trail Review Findings in Haunting". Accurate.
The ghost haunts the receipts
It felt wrong for a project built on Entire to ignore its own history, so the ghost reads it. haunted-term haunt-entire pulls your real checkpoints (only through the read-only entire checkpoint list and entire session list), recites them like lost memories, plays a fake rewind, and ends on one line:
rewinding your work into the void...
<<<< scrollback sliding backward
<<<< the last prompt unspools
<<<< the cursor walks home
Entire remembers everything you did.
That one isn't even a scare. It's just true. Marvin, Entire's robot mascot, is officially "mostly harmless". My ghost is entirely harmless. Between the two of them, they know exactly what I did last night.
Why Does Open Innovation Matter?
Because this thing reads your shell history.
A ghost that whispers about the commands you just typed has to see those commands, and your shell history is basically a diary: server names, file paths, the git push --force you're not proud of. I'm not comfortable sending that to a hosted API, and I wouldn't ask anyone else to. With Gemma on Ollama, the prompt goes to loopback and nowhere else. It works on a train with no signal. It costs nothing per scare, which matters when the scare fires every ten seconds. And a 1B model runs fine on a student laptop.
Open weights also let me be honest about the model. The lesson card names exactly which model spoke, --model swaps in any other Ollama model, and --no-ai removes the model entirely without breaking anything. A closed API would have given me a ghost with a monthly bill and a privacy policy. This one has neither.
The other open piece is the Grimoire. It's a plain JSON file anyone can add a lesson to, with a validator that checks every entry. The model gets to be spooky. The facts stay with people.
Prize Categories
-
Best Use of Gemma: local
gemma3:1bthrough Ollama is the ghost's voice, writing every whisper and answeringexplainfor unknown commands, clearly labeled. -
Best Use of Entire: the repo is hosted on Entire. Every build prompt ran as a recorded Cursor CLI session, with commits linked to their checkpoints. Three Trails of Entire reviews shaped the safety model,
entire whyexplains the code, and the app itself haunts you with your own Entire checkpoints.
來源:DEV Community · dev.to
