gleshell#
A structured-data shell written in Gleam, inspired by Nushell.
Instead of piping opaque text between programs, gleshell pipelines pass typed values: strings, numbers, lists, records, and tables. Built-in commands like ls, where, and select work on that structure.
~/code/gleshell on main
❯ ls | where type == file | select name size | first 5
╭──────┬──────╮
│ name │ size │
├──────┼──────┤
│ … │ … │
╰──────┴──────╯
The interactive prompt is zero-config and Starship-inspired: full path with ~,
optional git branch (read from .git, no external tools), and a Nerd Font shell
icon () before the ❯ character (which turns red after a non-zero exit).
Install a Nerd Font so the glyphs render (flake: nix profile install .#nerd-fonts, or put nerd-fonts.symbols-only in your system/fonts packages;
the devenv shell already includes it).
Quick start#
Requires Gleam and Erlang (or use Nix):
# install a self-contained `gle` (Erlang shipment in the Nix store — no repo checkout)
nix profile install .
gle -c 'ls | first 3'
# one-shot without installing
nix run . -- -c 'ls | first 3'
# dev shell (source tree; gleam, erlang, rebar3)
# Preferred: direnv auto-loads the flake shell (see .envrc). Once:
# direnv allow
# then cd into the repo (nushell needs a direnv pre_prompt hook; see below).
# Manual alternative — bare `nix develop` fails under pure flake eval:
nix develop --no-pure-eval
gleam run # interactive REPL
gleam run -- -c 'ls | first 3'
gleam test
direnv#
This repo ships a .envrc that runs use flake . --no-pure-eval
so devenv can see the real project directory (pure flake eval cannot).
- Install direnv and hook it into your shell (nushell, bash, zsh, …).
- From the repo root:
direnv allow - Open a new shell (or
cd .) — you should see the devenv enter message and havegleamonPATHwithout runningnix develop.
Bare nix develop still needs --no-pure-eval for the same reason; with
direnv you usually do not need nix develop at all.
REPL editing#
On a TTY the interactive REPL uses a raw-mode line editor with Nushell-style syntax highlighting as you type (commands cyan, strings green, numbers purple, pipes purple, flags blue, variables purple, …).
| Key | Action |
|---|---|
| ↑ / ↓ | History |
| Tab | Command completion (builtins + PATH) at the start of a pipeline stage; filename completion for arguments (common prefix; list matches if ambiguous) |
| Ctrl+R | Reverse-i-search through history (Enter accepts onto the line) |
| Ctrl+A / Ctrl+E | Beginning / end of line |
| Ctrl+W | Delete previous word |
| Ctrl+U / Ctrl+K | Kill to start / end of line |
| Ctrl+L | Clear screen |
| Ctrl+C | Cancel current line at the prompt; while an external command runs, send SIGINT to that process (does not exit the shell) |
| Ctrl+D | EOF (empty line) or delete under cursor |
History is persisted under the user cache as gleshell-history/lines.
Non-TTY input falls back to Erlang’s edlin/get_until path.
Examples#
# list files as a table, filter, project columns
ls | where type == file | select name size
ls | find toml md
echo [moe larry curly] | find l
# ranges and list ops
range 10 | reverse | first 3
range 5 | length
# records and JSON
echo {name: "gleshell", cool: true}
echo "{\"a\": 1}" | from json | get a
open data.json | get users | first
range 3 | to json
# variables
let n = range 3 | length
echo $n
# process environment (Nushell-style)
$env.HOME
$env | get PATH
$env.MY_VAR = hello
echo $env.MY_VAR
# external programs (stdout captured as a string)
^uname -a
which ls
which -a ls
Language sketch#
| Feature | Syntax |
|---|---|
| Pipeline | cmd | cmd | cmd |
| Strings | "hello" or bare words hello |
| Numbers | 42, 3.14 |
| Bools | true / false |
| Nothing | null / nothing |
| Lists | [1 2 3] |
| Records | {name: alice, age: 30} |
| Variables | let x = … then $x (pipeline input is $in) |
| Env | $env, $env.HOME, $env.FOO = value |
| Flags | --flag / --flag value |
| Force external | ^command args… |
| Comments | # … |
Built-ins#
Filesystem: ls, cd, pwd, cat, open, save
Table/list: where/filter, find, select, get, first, last, take, skip, sort-by, reverse, length, columns, table, flatten, uniq, wrap, unwrap, keys, values
Data: echo, range, lines, to/from (subcommand json), type, describe, env, sys, which, help, about, exit
Pager: less — builtin color-aware pager for pipeline input or files (ls | less,
less README.md). ANSI from tables and external tools is kept; short output is
printed without an interactive session. Interactive keys include live / search
(case-insensitive, finds as you type) and n/N next/previous match. ^less
still runs the external binary.
Unknown command names fall through to external executables on PATH.
Layout#
src/
gleshell.gleam # entry + REPL
gleshell_ffi.erl # line editor (Ctrl+R), cwd, process spawn
gleshell/
value.gleam # structured Value type
lexer.gleam / parser.gleam
eval.gleam # pipeline evaluator
builtins.gleam # Nu-inspired commands
pager.gleam # color-aware builtin less
display.gleam # table pretty-printer
color.gleam # Nushell-style ANSI colors
highlight.gleam # live input syntax highlighting
env.gleam / sys.gleam
Input and output are colorized on a TTY (Nu-like shapes on the command line;
headers bold green, numbers purple, bools cyan, dirs blue, errors red, …).
External tools (jj, git, fastfetch, …) keep their own colors: final-stage
commands inherit the real TTY; captured pipeline stages (e.g. jj log | less,
git log | less) run under a throwaway PTY when the shell wants color so tools
that only colorize on a terminal still emit ANSI — without per-tool env hacks.
Nested pagers are forced to cat so the producer cannot hang in its own
less. If script is unavailable, capture falls back to pipes plus
FORCE_COLOR / CLICOLOR_FORCE and a git GIT_CONFIG_* overlay. Pre-colored
text is not re-painted by the shell. While the raw line editor is active, the
TTY is switched to cooked mode for those children so LF-only output does not
staircase. The builtin less keeps ANSI (like less -R); external pagers
still get LESS=FRX when unset so they pass colors through.
Disable with NO_COLOR=1; force with FORCE_COLOR=1.
Status#
Early but usable: core pipeline model, tables, filters, JSON, and external commands work. Not a full Nushell clone (no closures/plugins yet). Contributions and experiments welcome.
License#
Apache-2.0 (same as the Gleam project template unless you change it).