Module 03·Systems Programming

zy · A shell built from scratch in C

ReleasedExpertUpdated 2026-06-06

Shell, prompt, fuzzy picker, file manager, HTTP client, and structured data — all built into one C binary. ~205,000 lines, 533 builtins, no plugins.

zy is a shell. It is the program you type commands into. You already know one shell: bash. zy runs your bash scripts unchanged.

Beyond that, zy is a prompt, a fuzzy picker, a frecency jumper, a file manager, and a structured-data pipeline. All built into one binary. One config file. One apt remove if you change your mind.

This page is a first day with zy. Try it, install it, meet the prompt, explore your files, shape the prompt, then take it with you. The deeper engineering sits at the end, marked optional.

zy welcome banner on first run
zy on first run, with a build-stamped version.

Try it in five minutes

You do not need to install anything to look around. zy runs in a throwaway container.

Drop into a real zy session inside a container that deletes itself on exit:

sh
docker run --rm -it ghcr.io/iskandarputra/zyshell:latest

Type exit when you are done. The container removes itself.

Your first session

Once you are at the prompt, three commands map the whole tool. Run them in order.

help

See every command. A three-pane browser opens over all 533 builtins. Filter as you type.

explore

Browse your files. A full file manager opens inside the shell. Arrow keys to move.

customize

Style the prompt. Pick a theme and a pill shape with a live preview.

A few more worth typing while you are in there.

🚀

Jump anywhere

Type zi. Recent directories, grouped by recency. Filter and press Enter to jump.

🔢

Treat output as data

Try ls | where size > 1mb | first 5. Real records, not text.

🌐

Fetch from the web

Try http get https://api.github.com. The JSON comes back as a table.


Install it for real

When you want to keep zy, install the Debian package from the GitHub Releases page.

Download the .deb

Open the Releases page and grab the latest zy_*_amd64.deb. Shipped builds are amd64.

Install with apt

Run sudo apt install ./zy_*_amd64.deb. apt pulls the runtime libraries for you.

Follow the wizard

A short setup wizard runs in the same terminal. Press Enter to take each default.

There is one .deb. zy is not statically linked, so apt resolves what it needs at install: sqlite3, pcre2, openssl, expat, libarchive. Installing recommends git and suggests chafa, poppler-utils, ffmpeg, ghostscript, ripgrep, and neovim. zy runs fine without all of them. Missing pieces degrade gracefully.

The first-run wizard

Right after install, a setup wizard runs. It asks at most six things. Each step is skipped when there is nothing to do, so most people see two or three.

Import a previous backup

Only if a backup tarball is found. Restores your old setup.

Install a Nerd Font

Only if none is detected. The prompt uses pill glyphs from a Nerd Font.

Install Neovim

Only if nvim is missing. Installs it for you.

Enable rich previews

Offers to install whichever of chafa, poppler-utils, ffmpeg, or ghostscript are missing, for image, PDF, video, and PostScript thumbnails in explore.

Set zy as your login shell

Runs chsh for your user. Not as root.

Launch zy now

Hands you straight into a session.

Your first prompt

The banner is the version, build-stamped down to the minute. Below it is the prompt. Type a command and it runs. Scripts you wrote for bash run unchanged.

The POSIX surface is fully present. Pipes, redirections, globs, command substitution, parameter expansion, functions, jobs, history. The bash and zsh conveniences are here too. BASH_REMATCH, FUNCNAME, trap DEBUG, mapfile, the kill ring, !! and ^old^new^, bind, shopt.

sh
echo ${var:-default} ${#var} ${var//old/new} ${var:0:5}   # parameter expansion
cmd > out.txt 2>&1                                         # redirections
ls **/*.c                                                  # recursive glob

To see what zy can do, type help. A three-pane browser opens. Categories on the left, commands in the middle, full docs on the right. Type to filter, Tab to switch panes, y to copy a name, Esc to leave. The same text prints at the prompt with help <cmd>.

zy help TUI, a three-pane builtin browser
help. Every builtin, three panes, filter as you type.

Explore your files

Type explore. A file manager opens inside the shell. No helper binary on your $PATH.

It is three-pane Miller columns. The left sidebar holds bookmarks, pinned folders, and mounted disks, each with a usage chip, plus a frecency list of where you go often. The middle column is the focused directory. The right column is a live preview.

zy explore file manager, three-pane Miller columns
explore. Bookmarks and disks, focused directory, live preview.

The preview is real. Syntax-highlighted code, sixel or kitty images, and PDF, PostScript, or video thumbnails when the tools are present. Text fallback otherwise.

Keys worth knowing

  • , cycles the detail columns.
  • I opens a file-info card: owner, perms, recursive size, times, binary architecture, SHA-256.
  • s opens a sort picker. Listings already sort naturally, so img2 lands before img10.
  • / filters the directory with fuzzy matching. The cursor is remembered per directory.

Press H on a file and it opens as a real HTML page in your browser. A full render through the bundled exporter, not a recolor.

zy explore rendering a file as a styled HTML page
Press H. The file opens as a real HTML page.

If the file has uncommitted changes, that page becomes a side-by-side diff against the version on disk. What did I change, without leaving the file manager.

zy explore HTML side-by-side diff for uncommitted changes
Uncommitted file? The HTML page becomes a diff.

Every explore key is remappable through an [explore.keys] table in ~/.zy/config.toml.

Jump where you live — zi

Type zi for the directory jumper. Recent directories, grouped Today, Yesterday, This week, Older, with a Bookmarks tab pinned in front. Type to filter, Tab or 1 through 5 to switch tabs, Enter to jump. The store is shared with z, so the prompt feeds it automatically.

zy zi directory jumper, Today tab
zi. Recent directories, grouped by recency.

The Bookmarks tab holds destinations you starred. They bypass the time-decay scoring, so they always render first.

zy zi Bookmarks tab, pinned destinations
Bookmarks. Pinned destinations, always first.

Shape the prompt

Type customize. Pick a theme, a pill shape, which pills show, how long directories truncate. The right pane previews every choice live. Each change writes through to ~/.zy/config.toml, and the next prompt renders with it. No restart, no source.

zy customize wizard with live preview
customize. Every choice previews against your theme.

Past the 22 named cap shapes, the Mix & Match tab pairs a left cap and a right cap from the full glyph set. The sample pill updates as you move.

zy customize Mix and Match pill cap picker
Mix & Match. Pick each cap from the set.

The prompt is one TOML file with hot reload. 39 themes ship in the binary, light variants and editor classics included. GitHub, Nord, Tokyo Night, Rosé Pine, Catppuccin, Dracula, Solarized, Gruvbox, and more. Over 100 modules cover languages, cloud and version-control context, and system signals. Slow modules render on background workers, so a noisy git repo never holds the prompt back.

Switching theme also retints the terminal. zy speaks the OSC sequences that GNOME Terminal, Konsole, Kitty, Alacritty, and Ghostty already listen to. New windows open in the new palette.


Take it with you

Your whole setup is portable in one command.

sh
migrate export        # timestamped tarball of ~/.zy/, ~/.zyrc, history, policy
migrate import ~/zy_migrate_<timestamp>.tar.gz

Theme, palette overrides, pinned pill shapes, custom modules, bookmarks, frecency database, and history restore verbatim on a new machine. The same flow doubles as a backup.

That is a working first day. Install, explore, jump, style, and move your setup. The rest of this page is for the curious. A beginner can stop here.


The stack you would have built

A productive terminal usually means a shell plus a dozen tools, a plugin manager, and a dotfiles repo to keep them in sync. zy ships the equivalent in one binary, wired together from the first prompt.

Instead of installingzy gives you
starship + a font scriptA TOML prompt with 39 themes, 22 pill shapes, 100+ modules, hot reload, and a customize wizard
fzfA built-in fzf with the 'exact / ^prefix / suffix$ / !negate grammar, plus multi-select
zoxidez and zi over one frecency store, shared with explore and the help TUI
yazi, ranger, lfexplore — Miller columns, sixel and kitty preview, archive browsing in place
jq, yqfrom-json, to-yaml, get, where, sort-by against any supported format
ripgrepfff-grep, in-process with no fork per query, so warm repeat searches stay fast
sqlite3query-db, stor-open, stor-create, schema over an in-memory SQLite store
nushellTyped pipelines with a vocabulary you already know
batSyntax-highlighted preview in explore and the fff-preview helper
curl, httpiehttp get / post / put / patch / delete / head, HTTPS included
A task runnertask, reading [task-name] sections from a .zytasks file with depends-based ordering
Plugin managers (zinit, antigen)Nothing. There is nothing to install on top of the binary
A dotfiles repoOne ~/.zy/config.toml and a one-command migrate export

For the curious — how it is built

Data, not strings

Most shells treat output as a wall of text. zy treats it as data.

Pipelines move records, tables, lists, durations, file sizes, and dates between commands in a typed, in-process form. No serialization tax, no awk gymnastics, no fragile column counting.

sh
# Strings out, data in. Reads like Nushell, runs in zy.
ls | where size > 1mb | sort-by modified | first 5
 
# Group files by extension, count each group, show the four largest.
ls | group-by extension | each { |g| { ext: $g.0, count: ($g.1 | length) } } | sort-by count --reverse | first 4
 
# Edit a JSON config in place. No jq, no temp file.
open package.json | upsert version "2.0.0" | save package.json
 
# Pull JSON from any endpoint and treat it as a table.
http get https://api.github.com/repos/iskandarputra/zyshell/releases | get tag_name | first 5

When every stage is a builtin, the chain runs in-process and passes typed values between stages. The moment an external command joins, zy falls back to a POSIX pipe with serialization at the boundary.

Pipeline mode per chain: typed transport, or a pipe.

Seventeen value types. Sixty-plus filter and transform commands. zy reads JSON, CSV, TSV, INI, TOML, YAML, XML, Markdown, NUON, and SSV. It writes JSON, CSV, TSV, TOML, YAML, XML, HTML, Markdown, NUON, and plain text. It decodes PNG, JPEG, GIF, and BMP.

The seventeen ZyValue types

null, bool, int, float, string, list, record, table, filesize, duration, datetime, binary, glob, cell-path, closure, range, error. Stored as a C tagged union.

The build and the layers

zy is a single C binary in gnu17, built -Wall -Wextra -Werror -O3 -g -flto. No readline, no libedit, no yacc. The parser is handwritten recursive descent, for direct control over shell corner cases and precise diagnostics.

The source is organized strictly by dependency direction. Nine numbered layers, low-level primitives at the bottom, terminal UI at the top. Parser and runtime changes stay contained. UI and module work stays fast.

Nine layers, strict dependency direction.

The signature design choice: policy and audit live inside the execution path, not beside it. Every command is parsed, policy-checked, executed, and recorded in one loop. Consistent by construction, not by convention.

Parse, policy-check, execute, audit. One loop.

Trust and accountability

For a shell that sits between people and shared infrastructure, zy ships two governance primitives with no external tooling.

A policy engine loads /etc/zy/policy.conf, through --policy at launch or the policy builtin. An administrator expresses rules there about which commands run in which contexts.

An audit ledger records every execution, with cwd, exit status, and duration, into a SHA-256 hash chain. Inspect it, export it, or verify the chain.

Day to day they stay out of the way. On the day you need the record, it is already there.


By the numbers

Every figure below is produced from the source tree, not a marketing slide.

~205Klines of C
Source
870+
Files
533
Builtin commands
17
Runtime value types
100+
Prompt modules
39
Themes
22
Pill cap shapes
71
Unit test files
Format coverage (read)JSON, CSV, TSV, INI, TOML, YAML, XML, Markdown, NUON, SSV
Format coverage (write)JSON, CSV, TSV, TOML, YAML, XML, HTML, Markdown, NUON, plain text
Terminals with explicit integration9 (Alacritty, Ghostty, GNOME Terminal, Kitty, Konsole, Ptyxis, Tilix, XFCE Terminal, xterm)
Architecture shippedamd64 (builds from source on arm64)
Vendored libraries2 (tomlc99, stb_image)
External runtime dependenciesapt resolves them at install

Public repository state

The public GitHub repository is the release channel. Binaries ship there. The source tree lives in a private development repo for now. This page documents the architecture and engineering decisions while the source stays closed.


Acknowledgments

zy stands on the work of others. It borrows ideas, not code. Every surface is an original C implementation.

InfluenceWhat zy learned from it
Bash / ZshThe POSIX scripting model and the conveniences (trap, mapfile, kill ring, completion ergonomics)
NushellThe structured-data pipeline model and a shared command vocabulary
StarshipThe TOML-driven, hot-reloading, module-shaped prompt
YaziThe file-manager experience explore aspires to
fzfThe fuzzy-picker grammar and flag shape
zoxide / z.luaThe frecency idea behind z and zi

zy contains no source code from any project above. Inspiration is reproduced through original implementation only, which is what keeps zy MIT.


Why it is here

For my portfolio, zy is end-to-end systems engineering in one artifact. A language frontend and runtime. Low-level memory and ownership discipline. Interactive terminal UX. Operational security designed into the execution path. Complete systems, not isolated components.