A modern, lightweight, cross-platform command-line shell, written in Zig.
NovaShell is inspired by PowerShell's ergonomics — but it isn't trying to clone every PowerShell feature. The goal is a shell that's fast, visually clean, covers the commands you actually use every day, and has its own identity.
⚡ NovaShell v0.1.0
───────────────────────────────────────
📂 /home/nova/projects
❯
Most cross-platform shells fall into two camps: decades-old and unopinionated about visuals (bash, zsh), or feature-rich but heavy and Windows-centric (PowerShell). NovaShell is a bet on a third option — a small, fast, native binary with a genuinely nice-looking prompt out of the box, that runs the same way on Windows, Linux, and macOS, without needing a runtime installed or a 50MB download. If you've ever spent an evening configuring Starship/oh-my-posh on top of your shell just to get a prompt that looks decent, NovaShell tries to make that the default instead of an add-on.
| NovaShell | bash/zsh | PowerShell | Nushell | |
|---|---|---|---|---|
| Written in | Zig | C | C# (.NET) | Rust |
| Cross-platform | ✅ native | mostly (Unix-first) | ✅ | ✅ |
| Colored prompt/theme out of the box | ✅ | ❌ (needs a framework) | partial | ✅ |
| Startup time | fast (no runtime) | fast | slower (.NET) | fast |
| Scripting language | — (not yet a goal) | POSIX shell | full .NET-backed language | structured/typed |
| Binary size | ~400KB stripped | varies | large (.NET runtime) | larger |
NovaShell isn't trying to win on scripting power — it's trying to win on being fast, good-looking, and easy to reason about for everyday interactive use. See Known simplifications & limitations for an honest list of what it doesn't do yet.
Status: All 12 phases of the development plan are complete, plus every "extra feature" from the original spec. See Development plan & status below.
- Why NovaShell?
- How does it compare?
- Feature list
- Requirements
- Getting started
- Installing a release build
- Using NovaShell
- Configuration
- How it works (architecture)
- Known simplifications & limitations
- Contributing
- Development plan & status
Core shell
- Interactive REPL (Read-Eval-Print Loop)
- Executes external programs on
$PATH(git,node,npm,python,code, anything else) - Relative and absolute path support everywhere paths are used
- Friendly, colored error messages instead of raw Zig errors or crashes (missing command, permission denied, bad exit code, etc.)
- Graceful fallback to plain line-buffered input when stdin isn't a real terminal (piped input, scripts, CI) — everything still works, just without live editing
Line editing & history
- A real interactive line editor: type, Backspace, Enter
- Command history recalled with ↑ / ↓, persisted across sessions to
~/.novashell_history - Consecutive duplicate commands aren't stored twice
- Ctrl+C cancels the current line without exiting the shell
- Ctrl+D exits cleanly on an empty line (same convention as bash/zsh)
Tab completion
- Completes builtins, aliases, and
$PATHexecutables in command position - Completes files and directories everywhere else — directories get a trailing
/(no space) so you can keep tabbing deeper; finished files/commands get a trailing space - Multiple matches complete as far as their shared prefix allows, or list all options if there's nothing more in common
Directory navigation
cd [path]— relative, absolute, or no argument at all (goes to your home directory)pwd— prints the current working directory
Built-in commands
cd,pwd,clear/cls,help,about,version(see Built-in commands)
"Did you mean...?" suggestions
- Typing an unknown command checks it against every builtin, alias, and
$PATHexecutable using edit-distance matching, and suggests the closest one if it's a plausible typo
Environment variables
$VAR,${VAR}, and%VAR%all expand in command arguments- Unset variables expand to nothing rather than erroring out the whole command
Visual design
- Full ANSI color support with automatic detection — colors are disabled automatically when output isn't a real terminal, or when
NO_COLORis set - Two-line styled prompt:
📂 <colored current directory>then a colored❯glyph - Colorful startup banner
- Colored error/warning output, distinct from normal command output
Themes
- Four built-in palettes:
default,ocean,sunset,mono - Individual color overrides (
prompt_color,glyph_color) on top of whichever theme is active
Configuration
- Config file at
~/.novashellrc(or%USERPROFILE%\.novashellrcon Windows), auto-created with a commented-out example on first run - Theme selection and color overrides
- User-defined aliases, including argument passthrough (
alias ll = ls -la+ typingll /tmprunsls -la /tmp) - Alias loops are detected and reported instead of hanging
- Unrecognized config lines are reported as warnings at startup, not silently ignored and not fatal
Cross-platform
- Builds natively for Windows, Linux, and macOS from a single codebase
- Windows raw-mode terminal input uses
ENABLE_VIRTUAL_TERMINAL_INPUT, so arrow keys/Tab/Ctrl+C are decoded through the exact same code path as Unix terminals — one line editor, not two
- Zig 0.16.0 or newer
If you don't have Zig installed, the easiest cross-platform option is the ziglang PyPI package, which ships a prebuilt Zig binary:
pip install ziglang --break-system-packages
python3 -m ziglang build run # use this instead of `zig build run` belowIf you already have Zig on your PATH normally, just use zig directly as shown in the rest of this doc.
# Build (output goes to zig-out/bin/novashell or novashell.exe)
zig build
# Build and launch immediately
zig build run
# Run the test suite
zig build testCross-compiling to another platform works out of the box, no extra SDKs:
zig build -Dtarget=x86_64-windows -Doptimize=ReleaseSafe
zig build -Dtarget=x86_64-linux -Doptimize=ReleaseSafe
zig build -Dtarget=aarch64-macos -Doptimize=ReleaseSafe(ReleaseSafe is what release builds use — see Installing a release build — it keeps bounds/overflow checks on, trading a little speed for turning bugs into a caught error instead of undefined behavior. ReleaseFast is available too if you want to trade that safety net for the last bit of performance.) Building all five release targets at once, packaged into archives: scripts/release.sh.
The first time you run NovaShell, it creates ~/.novashellrc for you — see Configuration.
If you'd rather not build from source, grab the archive for your platform from the releases page — or, to build them yourself locally exactly as CI does:
scripts/release.sh # builds + packages all 5 platform archives into dist/Each archive contains the novashell binary alongside the README, LICENSE, and CHANGELOG. A dist/SHA256SUMS file is generated alongside them — worth checking after downloading:
sha256sum -c SHA256SUMS # Linux
shasum -a 256 -c SHA256SUMS # macOSWindows — unzip novashell-vX.Y.Z-x86_64-windows.zip, then either run novashell.exe directly from that folder, or move it somewhere already on your PATH (or add the folder to PATH yourself) so you can launch it from anywhere. Since this binary isn't code-signed, Windows SmartScreen may warn about it the first time — click "More info" → "Run anyway" if you trust the source it came from.
macOS — extract the tarball (tar xzf novashell-vX.Y.Z-*-macos.tar.gz), then chmod +x novashell and move it into somewhere on your PATH, e.g. /usr/local/bin. Since this binary isn't notarized, Gatekeeper will likely block it the first time you run it — either right-click → Open once to approve it, or run xattr -d com.apple.quarantine novashell after extracting.
Linux — extract the tarball, chmod +x novashell, then move it onto your PATH (e.g. ~/.local/bin or /usr/local/bin).
Type a command and press Enter, same as any shell:
❯ git status
❯ node app.js
❯ python script.py --flag value
❯ cd ../other-project
Arguments with spaces need quotes (single or double both work):
❯ cd "Program Files"
❯ code 'my project'
| Key | Action |
|---|---|
Tab |
Complete commands, aliases, or file/directory paths |
↑ / ↓ |
Browse command history |
Ctrl+C |
Cancel the current line, get a fresh prompt |
Ctrl+D |
Exit NovaShell (only when the line is empty) |
Backspace |
Delete the last character typed |
Typing exit or quit and pressing Enter also exits.
| Command | What it does |
|---|---|
cd [path] |
Change directory. No argument → go to your home directory. |
pwd |
Print the current working directory. |
clear / cls |
Clear the screen. |
help |
List built-in commands and key shortcuts. |
about |
What NovaShell is, and where its files live. |
version |
Print the NovaShell version. |
Everything else (git, node, npm, python, code, ...) is run as an external program from $PATH, with its output shown directly — NovaShell doesn't try to reinterpret it.
Reference environment variables in any command's arguments:
❯ echo $HOME
❯ echo ${HOME}/projects
❯ cd %USERPROFILE%
An unset variable just expands to nothing, rather than causing an error.
NovaShell reads ~/.novashellrc (%USERPROFILE%\.novashellrc on Windows) on startup. If the file doesn't exist yet, NovaShell creates one for you with everything commented out, so there's something to find and edit:
# NovaShell configuration
#
# Lines starting with '#' are comments. Blank lines are ignored.
# Pick a theme: default, ocean, sunset, mono
# theme = default
# Override individual prompt colors (optional; takes priority over
# the theme). Available colors: black, red, green, yellow, blue,
# magenta, cyan, white, and a bright_ version of each.
# prompt_color = cyan
# glyph_color = bright_magenta
# Aliases: alias <name> = <command...>
# alias ll = ls -la
# alias gs = git statusUncomment and edit whatever you want. A line NovaShell doesn't understand (a typo'd key, an unknown theme name) is reported once at startup as a warning — it won't stop the shell from starting.
| Theme | Feel |
|---|---|
default |
Cyan path, magenta prompt glyph |
ocean |
Blues and cyans |
sunset |
Warm yellows, reds, and magenta |
mono |
No color at all — bold/dim only |
Set with theme = <name> in the config file. prompt_color and glyph_color override just those two elements on top of whichever theme is active.
alias ll = ls -la
alias gs = git status
alias gohome = cd $HOME- Extra arguments typed after the alias name are appended to its expansion: with
alias ll = ls -la, typingll /tmprunsls -la /tmp. - Aliases can use environment variables (
alias gohome = cd $HOME). - Alias loops (
alias a = bwherebeventually expands back toa) are detected and reported instead of hanging the shell.
build.zig, build.zig.zon Build system + package metadata.
LICENSE, CHANGELOG.md Release documentation.
CONTRIBUTING.md, CODE_OF_CONDUCT.md Contributor docs.
scripts/release.sh Builds + packages all platform release archives.
.github/workflows/ci.yml Build + test on every push/PR.
.github/workflows/release.yml Publishes a GitHub Release on a version tag push.
.github/ISSUE_TEMPLATE/ Bug report / feature request forms.
src/
main.zig Entry point — wires everything together.
repl.zig The Read-Eval-Print Loop and interactive line editor.
parser.zig Turns a line of input into a structured command.
executor.zig Runs a parsed command: env var + alias expansion, builtin or child process.
context.zig Shared execution context threaded through the above.
commands/
builtins.zig cd, pwd, clear/cls, help, about, version.
suggest.zig "Did you mean...?" typo suggestions (edit distance).
ui/
banner.zig Startup banner.
prompt.zig The two-line styled prompt.
colors.zig ANSI color helpers + terminal/NO_COLOR detection.
theme.zig Named color palettes.
history/
history.zig Command history: recording, persistence, recall.
config/
config.zig ~/.novashellrc loading: theme, colors, aliases.
completion/
completion.zig Tab-completion for commands and paths.
utils/
version.zig Version string, in one place.
env.zig Home-directory lookup (cross-platform).
tty.zig Raw terminal mode + keystroke decoding.
- NovaShell is a shell, not a terminal emulator. It runs inside whatever terminal you launch it from (Windows Terminal, iTerm2, GNOME Terminal, tmux, SSH, VS Code's integrated terminal, ...) and talks to it via ANSI escape codes — exactly like bash, zsh, fish, and PowerShell itself all do. There's no separate GUI window.
- One line editor for every platform.
utils/tty.zigputs the terminal into raw mode (termioson Unix-likes, the Win32 console API on Windows) and, on Windows, also turns onENABLE_VIRTUAL_TERMINAL_INPUT— which makes Windows' console send arrow keys and other special keys as the same ANSI escape sequences Unix terminals use. That means the actual key-decoding and line-editing logic inrepl.zigis written once and shared across platforms; only entering raw mode differs per OS. - Everything that can go wrong during a command is a printed message, not a crash. A missing program, a bad path, a permission error, a nonzero exit code —
executor.zigand the builtins turn all of these into a friendly colored line of output. Zig errors are reserved for things actually wrong with NovaShell itself (out of memory, a broken pipe), which is what still propagates up and would stop the shell. - Config-driven, not hardcoded. The active theme and alias table live in one
Configstruct threaded through a sharedContext, so the prompt, banner, error messages, and command dispatch all read from the same source instead of each having their own copy of "what color is an error" or "what doesllexpand to."
Being upfront about what's not here yet:
- No cursor movement within a line. Left/Right arrows don't move the cursor — editing only happens at the end of the line you're typing. History recall and Tab completion both work fine within that constraint; only "go back and fix a typo in the middle of what you typed" isn't supported yet.
- Quoting doesn't suppress environment variable expansion. Unlike bash,
'$HOME'still expands — the parser doesn't currently track which quote style produced each argument. - No pipes or redirection (
|,>,<) yet — each line runs as a single command. - Command-name completion doesn't check the executable bit. It lists files in each
$PATHdirectory by name; Windows has no equivalent concept anyway, and checking properly on POSIX would mean an extra filesystem call per candidate. - Windows and macOS are verified by successful cross-compilation, not by running the binary on real hardware. This project has been developed and runtime-tested on Linux.
utils/tty.zig's Windows console-mode handling (talking tokernel32directly) is the piece most worth double-checking first if you have access to a Windows machine.
Bug reports, feature ideas, and PRs are welcome — see CONTRIBUTING.md for how to get set up, what to check before opening a PR, and a few genuinely approachable places to start. This project follows a Code of Conduct.
Built incrementally, each phase reviewed before the next started:
- Project setup ✅ — build system, module skeleton, cross-compile check.
- REPL ✅ — interactive read/print loop with graceful exit handling.
- Command parser ✅ — whitespace + quote-aware argv splitting.
- Process execution ✅ — spawns external programs, forwards stdio, reports exit codes.
cdimplementation ✅ — pluspwd; relative, absolute, and home-directory paths.- Prompt UI ✅ — two-line styled prompt (📂 path, then
❯) and welcome banner. - Colors ✅ — ANSI helpers with TTY detection and
NO_COLORsupport. - Command history ✅ — arrow-key recall, persisted to
~/.novashell_history. - Auto-completion ✅ — Tab-completes commands, aliases,
$PATHexecutables, and file/directory paths. - Config system ✅ —
~/.novashellrc, auto-created on first run; theme selection, color overrides, aliases. - Themes ✅ — four built-in palettes, switchable via config.
- Packaging and release ✅ — cross-platform release archives, checksums, and a GitHub Actions release workflow.
Plus, beyond the phase list, everything from the original feature spec:
clear/cls,help,about,versionbuiltins ✅- "Did you mean...?" typo suggestions ✅
- Environment variable expansion (
$VAR,${VAR},%VAR%) ✅
- Bump the version in
build.zig.zonandsrc/utils/version.zig, and add a new entry at the top ofCHANGELOG.md. git tag vX.Y.Z && git push origin vX.Y.Z..github/workflows/release.ymlpicks up the tag, builds all 5 platform targets withscripts/release.sh, and opens a draft GitHub Release with the archives and checksums attached — reviewed and published by hand, not auto-published, so nothing ships without a final look.
Building the same artifacts locally without pushing anything: scripts/release.sh (requires Zig on PATH, or the ziglang pip package as a fallback).
MIT — see the LICENSE file. (The copyright holder name in there is a placeholder; swap in your own name/org before publishing.)