$ git show console-shell
Console Shell
ACTIVE$ git clone
https://github.com/PaulNonatomic/console-shell.git
✓ cloned
// OVERVIEW
Hides a styled CLI inside a website's developer console. Ships a banner that fires once per page load, a window.yourbrand namespace with custom commands, and an auto-generated colour-coded help screen — curious devs who open dev tools find it; the rest of the world never sees it. The pattern extracted from the easter egg Nonatomic ships at nonatomic.co.uk: a styled banner, a window.nonatomic namespace with commands like .about() and .contact(), and a colour-coded .help() listing. Zero runtime dependencies; ESM + CJS + types, roughly 5 KB gzipped. Pairs with console-adventure to layer a branching text adventure on top of the same shell.
// DETAIL
Hides a styled CLI inside a website's developer console. Ships a banner that fires once per page load, a window.yourbrand namespace with custom commands, and an auto-generated colour-coded help screen. Zero runtime dependencies, ESM + CJS + types, roughly 5 KB gzipped. Pairs with console-adventure to layer a branching text adventure on top of the same shell.
// README
Hide a styled CLI inside your site's developer console.
console-shell lets you ship a tiny CLI inside your site's browser console — a styled banner, a window.yourbrand namespace with custom commands, and an auto-generated colour-coded help screen. Curious devs who open dev tools find it; the rest of the world never sees it.
Why
You spent months on a site. A handful of visitors will open dev tools. That handful are often the ones you most want to impress — other devs, recruiters, potential collaborators. Reward them with something more than minified JavaScript.
This library extracts the easter-egg pattern Nonatomic ships at nonatomic.co.uk:
- A styled ASCII/Unicode banner that fires once per page load
- A
window.nonatomicnamespace with commands like.about(),.contact(),.colours() - An auto-generated
.help()listing with per-command colour
Want to layer a branching text adventure on top of the shell? See the companion package console-adventure.
Install
npm install console-shell
# or
pnpm add console-shell
# or
yarn add console-shell
Zero runtime dependencies. ESM + CJS + types, ~5 KB gzipped.
Quick start
import { createShell } from 'console-shell';
const shell = createShell({
namespace: 'mybrand', // → window.mybrand
banner: {
wordmark: 'M Y B R A N D',
tagline: 'We make things.',
hint: 'try mybrand.help()'
},
commands: {
about: {
description: 'who we are',
color: 'primary',
run: () => console.log('We make things, since 2024.')
},
contact: {
description: 'say hi',
color: 'danger',
run: () => window.open('mailto:hi@example.com')
},
site: {
description: 'open the site',
color: 'info',
run: () => window.open('https://example.com', '_blank')
}
}
});
shell.install();
Open dev tools and you'll see the banner. Type mybrand.help() and you'll get a colour-coded list of every command:
mybrand.* — commands
.about() — who we are
.contact() — say hi
.help() — list available commands
.site() — open the site
API
createShell(config: ShellConfig): Shell
| Field | Type | Notes |
|---|---|---|
namespace |
string |
Attached to window[namespace] (frozen) on install. |
banner? |
Banner |
{ art?, wordmark?, tagline?, hint? }. Each layer is optional. |
theme? |
Partial<Theme> |
Shallow-merged over DEFAULT_THEME. |
commands? |
Record<string, Command> |
Each Command = { description, color?, run }. |
onCommand? |
(name: string) => void |
Fires on every invocation. Wire it to analytics or leave it unset. |
logger? |
{ log(msg, ...styles) } |
Defaults to console. Tests pass a capturing stub. |
The returned Shell exposes:
install()— print the banner, freeze the namespace, attach it towindow.registerCommand(name, command)— add a command before install.attach(plugin)— sugar forplugin.attachTo(shell). TheShellPlugininterface ({ attachTo(shell): void }) is intentionally structural so any third-party module (text adventure, debug REPL, in-page tutorial) can plug in without console-shell knowing about it.theme,namespace,logger— resolved values, for plugins to use.
Theme
DEFAULT_THEME ships a phosphor-on-void palette (lime + amber + magenta + cyan on near-black). Override any field:
createShell({
namespace: 'mybrand',
theme: { primary: '#3aa0ff', accent: '#ffd400', danger: '#ff3ad0' }
});
Theme colour slots referenced by name: primary, accent, danger, info, text, dim.
Analytics
The library is analytics-agnostic. Wire onCommand into whatever you use:
createShell({
namespace: 'mybrand',
onCommand: (name) => analytics.track('console_command', { name })
});
The hook is called raw — no built-in dedupe. If you want once-per-session-per-command semantics (recommended for budget-conscious analytics), wrap it yourself with sessionStorage.
Layering an adventure on top
The companion package console-adventure provides a branching scene-graph text adventure engine that plugs in via the ShellPlugin interface:
import { createShell } from 'console-shell';
import { createAdventure } from 'console-adventure';
const game = createAdventure({ start: 'entrance', scenes: { /* ... */ } });
const shell = createShell({ namespace: 'mybrand' });
shell.attach(game.asShellPlugin()); // adds .play(), .choose(n), .share()
shell.install();
You can use either package on its own — console-shell for a pure easter egg, console-adventure for a game that drives its own UI without dev-console wrapping.
No mutual dependency. Neither package imports from the other; both ship with
"dependencies": {}. They meet at a shared structural type —ShellPluginhere in console-shell,ShellLikeover in console-adventure — that describes the contract without binding the implementations together. Anyone could write a third plugin (a debug REPL, an in-page tutorial, a quest engine in a different language) that satisfies the same interface and attaches to this shell with no upstream changes.
Example
A minimal working example ships in examples/minimal/ — open index.html in a browser and pop dev tools to try it.
Dev
npm install
npm test # vitest
npm run typecheck # tsc --noEmit
npm run build # tsup → dist/
Tests run in jsdom. The library is browser-only at runtime (it touches window) but the test harness stubs the logger so tests don't pollute stdout.
Support
If you like my work then please consider showing your support for console-shell by giving the repo a star or buying me a brew

License
MIT © Paul Stamp / Nonatomic Digital Foundry.
See also
console-adventure— branching text adventure engine that plugs into this shell.- nonatomic.co.uk — open dev tools, type
nonatomic.help(). - paulstamp.com — same thing, different brand.