A minimal, browser-based terminal game for kids and high-school students who have never used a command line. The goal is to make people comfortable with a CLI so they don't feel forced to rely solely on graphical interfaces.
Every computer ships with a terminal. Phones, tablets, servers, Raspberry Pis, self-driving cars, satellites, cloud services — they all speak the language of the shell. And yet most users are taught to fear it, or simply never taught it at all. They click through 17 menus to do what a single typed command would do in half a second.
Eggshell is a gentle introduction. It is not a "real" shell — it is a training shell. It teaches the shape of a command line:
- a prompt with a user, a host, and a current directory,
- a vocabulary of short verbs (
ls,cd,cat,mkdir,touch,edit,unzip,sudo, …), - arguments, flags, absolute vs. relative paths,
- the idea that files and folders are the API of the system,
- the idea that some doors are locked until a config file says otherwise,
- the idea that some actions require a password and elevated privileges.
The game is an easter-egg hunt. Five eggs are hidden in the virtual filesystem. To reach each one, the player must earn the next skill, and that skill unlocks the next room. Nothing is "fake" — every concept in Eggshell maps 1:1 onto something you would do in a real Linux, macOS, or WSL terminal.
- Minimal visual style. Black background. Green text. A blinking cursor. No animations, no gradients, no chrome. A CLI should look like a CLI.
- Failure is friendly. Unknown commands print
command not found— just like a real shell — but the game never yells at the player. Thehelpcommand is always one keystroke away. - Staged progression. The filesystem is a graph of locked rooms. You
can see the next room in
ls(it shows[locked]), but you cannot enter it until you complete the previous task. This creates a clear "what's next?" feeling without being strictly linear. - Config files as puzzles. The text editor is not a toy. Players must
actually change a line like
path_to_vault_locked = truetofalseto proceed. This mirrors how/etc/sudoers,nginx.conf,sshd_config, and a thousand other real-world files actually work. - Privilege escalation as a plot point.
sudois not just another command — it is the penultimate boss. The player must find a password hidden as a base64-encoded string, decode it, and use it to edit a system file that lives in/etc. That is, in a nutshell, what being a sysadmin is. - Archive extraction as the final step. The last egg is locked inside a
zip archive in
/root. The player mustunzipit before they cancatthe prize — teaching one more everyday skill as the grand finale.
npm install
npm run devThen open the URL Vite prints. You start in /home/player. Type start.
When you start, pick a difficulty in the intro modal — or change it any time with difficulty <level>:
| level | stage output |
hint command |
map panel |
|---|---|---|---|
| easy | full step-by-step guidance | verbose walkthrough | always visible |
| medium | objective summary | verbose walkthrough | toggle with map |
| hard | stage label only | one-step nudge | toggle with map |
| impossible | silent | forbidden | forbidden |
| command | what it does |
|---|---|
help |
print the command list |
clear / cls |
clear the screen |
pwd |
print current directory |
ls [path] |
list directory contents |
cd <path> |
change directory (.. goes up) |
cat <file> |
print a file |
mkdir <name> |
create a directory (inside /home/player) |
touch <name> |
create an empty file |
rm <name> |
remove a file or empty directory |
edit <file> |
open the built-in text editor |
unzip <file> |
extract a zip archive |
wifi list |
show available Wi-Fi networks |
wifi connect <SSID> |
connect (prompts for a password) |
base64 -d <string> |
decode a base64 string |
sudo <command> |
run a command as root (prompts for password) |
hint |
get a hint for the current stage (adapts to difficulty) |
difficulty [level] |
show or set difficulty (easy/medium/hard/impossible) |
map |
toggle the map panel (medium/hard only) |
save |
export progress to a save string (copied to clipboard) |
load <string> |
restore progress from a save string |
eggs |
show how many eggs you've found |
stage |
print the current stage objective |
Arrow-up / arrow-down walk through command history. Ctrl+L clears the screen, just like a real shell.
save prints a base64 string encoding your full game state. Copy it somewhere safe.
load <string> restores from that string.
The game also autosaves to localStorage on every action. When you reload the page,
a [ restore save ] button appears in the intro modal if an autosave exists.
The filesystem is a tree. Some directories are locked — they are
visible in ls (so the player always knows what to aim for), but cd
is refused until a predicate on the game state becomes true.
The stages form a DAG — not strictly linear, but each stage depends on most of the previous ones being complete.
Stage 1: OFFLINE ──► connect Wi-Fi ──► unlocks /home/player/documents ──► egg1
Stage 2: CREATE ──► mkdir magic + touch magic/token.txt ──► unlocks /home/player/secrets ──► egg2
Stage 3: EDIT ──► edit secrets.cfg (flip a boolean) ──► unlocks /home/player/vault ──► egg3
Stage 4: DECODE ──► base64 -d <clue> ──► reveals the sudo password
Stage 5: SUDO ──► sudo edit /etc/privilege.cfg ──► unlocks /admin and /root ──► egg4
Stage 6: ROOT ──► sudo cd /root ──► unzip final_egg.zip ──► cat final_egg.txt ──► egg5
Each stage also contains an egg file (egg1.txt, egg2.txt, …).
Reading the egg with cat adds it to the player's collection (check with
eggs). The game is won when all five are collected.
The non-linearity: a curious player can explore /etc from the very start,
read /etc/hostname, /etc/issue, and /etc/wifi.json — all useful
clues. They can also pre-read the riddle files inside documents once
Stage 1 is done, before solving Stage 2. But they cannot skip a stage
entirely, because each locked directory guards the next.
Spoilers below. Teachers: keep this section for yourself, or hand it out only after students have tried on their own.
- Type
start— see the quick-start guide. cat README.txt— read the intro. It mentions Wi-Fi.wifi list— shows 4 networks.EggHunt-5Ghas the strongest signal.wifi connect EggHunt-5G— password prompt.- Password:
yolk-yolk-123(also visible in plaintext inside/etc/wifi.jsonif the playercats it — on purpose, so they learn that "config files contain secrets"). - You are ONLINE.
/home/player/documentsis now unlocked. cd documentsthencat egg1.txt— 🥚 #1 collected.
cat clue2.riddle(insidedocuments) — it tells you to create a directory calledmagicand inside it a file calledtoken.txt.cd ..(back to/home/player).mkdir magic— creates the directory.touch magic/token.txt— creates the empty file.lsnow showssecrets/without the[locked]tag.cd secretsthencat egg2.txt— 🥚 #2 collected.
lsinsidesecrets— you seesecrets.cfgandeditor-tips.txt.edit secrets.cfg— opens the built-in editor.- Change the line
path_to_vault_locked = truetopath_to_vault_locked = false. Save (Ctrl+Sor click Save). cd ..thenls. Thevault/directory is now unlocked.cd vaultthencat egg3.txt— 🥚 #3 collected.
cat sudo_clue.json(insidevault) — shows a JSON object with apassphrasefield containingc2VjcmV0LWVnZw==.base64 -d c2VjcmV0LWVnZw==— outputssecret-egg. This is thesudopassword.
cat how-to-sudo.txt— explainssudo.- Try
edit /etc/privilege.cfg— permission denied. The file requires root. sudo edit /etc/privilege.cfg— password prompt. Entersecret-egg.- Change
allow_admin=falsetoallow_admin=true. Save. cd /home/playerthenls— theadmin/directory is now unlocked.cd adminthencat egg4.txt— 🥚 #4 collected.
egg4.txttells you the final egg is inside/root/final_egg.zip.cd /root— permission denied. Usesudo cd /root.sudo cd /root— password prompt. Entersecret-eggagain.ls— you seefinal_egg.zip.unzip final_egg.zip— extractsfinal_egg.txt.cat final_egg.txt— 🏆 the final egg!- Type
eggsto confirm: 5 / 5. You win.
The filesystem is pure JavaScript objects — no real files on disk. buildFilesystem() in src/game/fs.ts returns a nested FsNode tree of { kind: "dir", children: [...] } and { kind: "file", content: "..." } nodes. Commands like ls, cd, and cat traverse this in-memory tree at runtime.
Three layers of "files" coexist:
- Built-in FS — the static tree from
buildFilesystem(). Immutable. state.edits— aRecord<path, string>of player-edited file contents.catchecks here first.state.userCreated— an array of{ type, path, content }for files/dirs created by the player withmkdir/touch.
Lock predicates live directly on directory nodes: locked: (state: GameState) => boolean. They are evaluated live on every ls/cd — no event system, no pub/sub. Example:
dir("documents", [...], { locked: (s) => !s.wifiConnected })
dir("secrets", [...], { locked: (s) => !(s.createdMagicDir && s.createdTokenFile) })onEdit callbacks on files return { error: string | null, patch: Partial<GameState> }. Editing secrets.cfg and flipping the flag causes onEdit to return { patch: { secretsUnlocked: true } }, which the command handler applies via setState.
GameState is a flat React state object (~15 boolean/array fields). All progression is derived from it.
src/game/stage.ts holds a STAGES array. Each entry has:
gate: (s: GameState) => boolean— true means this stage is still incompletedescription/partial/hints.verbose/hints.brief— four verbosity tiers matched to difficulty
activeStage(s) finds the first incomplete stage. stageOf(s) picks which text tier to show.
The current design couples world content (filesystem nodes, stage text, lock predicates) tightly with the game engine (commands.ts, App.tsx). For v2.0, the goal is a world-loader architecture where each "world" lives in its own file and the engine is world-agnostic. See todo.md → Phase D for the full spec.
If you are an AI assistant or a developer reading this file and you want to rebuild or extend Eggshell, here is the complete specification.
- React 19 + Vite + TypeScript + Tailwind CSS (already in
package.json). - No other runtime dependencies. The terminal is a
<textarea>-driven React component — noxterm.js, no heavy shell emulators.
Built With
- Game Engine: React 19 + Vite + TypeScript + Tailwind CSS
- AI Collaborator: Anthropic's Claude (used for code generation, heavy debugging, and architecture refactoring). All game design decisions, puzzle logic, and product direction were human-directed.
src/
App.tsx — terminal UI: prompt, history, scroll, modals
game/
fs.ts — virtual filesystem (tree of FsNode), game state
commands.ts — command dictionary: ls, cd, cat, edit, sudo, unzip, …
stage.ts — stageOf + stageLabel shared utilities
utils/cn.ts — clsx + tailwind-merge helper
main.tsx, index.css — entry points (unchanged from default template)
README.md — this file (manifesto + solutions)
CLAUDE.md — AI assistant guidance
todo.md — improvement backlog
index.html — <title> set to "Eggshell — The CLI Easter Egg Hunt"
A single GameState object lives in src/game/fs.ts. Locked directories
carry a predicate (state) => boolean — when it returns true, the
directory is shown with [locked] in ls and cd refuses to enter it.
Commands mutate this state via setState((s) => …).
- Wi-Fi gate.
wifi listshows 4 networks. OnlyEggHunt-5Gwith passwordyolk-yolk-123gives internet. Unlocks/home/player/documents. - Create gate. Player must
mkdir magicandtouch magic/token.txt. Unlocks/home/player/secrets. - Editor gate.
edit secrets.cfg, flippath_to_vault_lockedfromtruetofalse. Unlocks/home/player/vault. - Decoder gate.
base64 -d c2VjcmV0LWVnZw==→secret-egg. - sudo gate.
sudo edit /etc/privilege.cfg, flipallow_adminfromfalsetotrue. Unlocks/home/player/adminand/root. - Root + unzip.
sudo cd /root, thenunzip final_egg.zipto extractfinal_egg.txt, thencat final_egg.txtfor the win.
- Strictly monochrome terminal palette: black background, emerald/gray/red foreground, monospace font only.
- A fake "window chrome" at the top with three dots (red/yellow/green) and
a counter
🥚 eggs N/5 · stage: <stage>. - Command history on arrow-up/arrow-down.
Ctrl+Lorclearclears the screen. - Password prompts are inline in the terminal (not a modal).
- The text editor opens as a centered modal with Save/Cancel buttons.
Ctrl+Ssaves;Esccancels. - Terminal text is selectable by mouse so players can copy commands.
- Unknown commands print
command not found. Non-existent paths printNo such file or directory. Locked directories printcd: '<name>' is locked.with a short hint.
Once a player finishes Eggshell, point them at a real shell:
- Windows: install WSL. Open the Ubuntu terminal. Run
ls. - macOS: open
/Applications/Utilities/Terminal.app. - Linux: you already have one.
Suggested first commands in a real shell: ls -la, cd ~, pwd,
mkdir projects, nano hello.txt, cat hello.txt, sudo apt update
(or brew install). They already know the concepts — they just need real
muscle memory.