Current section
Files
Jump to
Current section
Files
README.md
# GSH (Gleam Shell)
[](https://hex.pm/packages/gsh)
[](https://hexdocs.pm/gsh/)
Copyright 2026 v4rm4n
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [here](http://www.apache.org/licenses/LICENSE-2.0).
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
> GSH is an interactive REPL for the [Gleam Programming Language](https://gleam.run/) written in Gleam and Erlang.
## Installation
Add `gsh` to your project as a dependency:
```bash
gleam add gsh
```
> ⚠ This was previously `gleam add gsh --dev` ⚠
## Usage
`gsh` can either be used as a standalone REPL or a live-app bootloader.
### Standalone
```bash
gleam run -m gsh
```
### Call functions from GSH
```gleam
Erlang/OTP 28 [erts-16.1.2] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
Interactive Gleam (GSH 1.1.1) - press Ctrl+C to exit (type h() ENTER for help)
gsh(1)> import your_app/config
ok
gsh(2)> config.load()
Runtime Error: "error:undef"
gsh(3)> :cc
Compiling your_app
Compiled in 0.25s
Ok (Imports hot-reloaded)
gsh(4)> config.load()
Config("0.1.0", "0.0.0.0", 8000) : Config
gsh(5)>
```
### App loader
```bash
gleam run -m gsh -- my_app worker_pool bg_module_1
```
## Built-in Commands
GSH includes several built-in commands to manage your session:
- `:h` - Show the help menu
- `:v` - Show the current GSH version
- `:b` - List all currently active variable bindings
- `:hs` - Show the history of executed commands
- `:cc` - Recompile the host Gleam project without leaving the shell
- `:c` - Clear the terminal screen (or Ctrl + L)
- `pid()` - Create a pid from a string (e.g. pid("<0.34.0>"))
- `:h <module/function>` - Retrieve module/function documentation
- `:q` - Exit the shell
## Target limitations
> **Note:** GSH is heavily tied to the Erlang VM (BEAM) for state persistence and dynamic evaluation. It **does not** support the JavaScript target.
## Why a REPL?
After using Elixir's `iex`, OCaml's `utop` or even Rust's `evcxr`. I really wanted to build a tool for Gleam that gets me closer to the BEAM. **GSH**, expanded as **Gleam SHell** is a materialization of that dream.
### REPL use cases
- Function & module debugging with mock data.
- Interaction with actors & the supervision tree.
- Quick scratch-pad for validating logic & trivial constructs.
- Working with the Gleam ecosystem and libraries.
## Demo
- Tab-completion with auto suggestions

- Input/Output syntax highlighting + Multi-line + Pattern matching

- Processes & built-ins (pid)

## Latest Bugfixes
### State issues
- **Automatic Stale Binding Pruning (:cc):** Fixed compilation failures after host-code hot-reloading where historical variable assignments referenced renamed or removed host functions. `evaluator.gleam` now intercepts compiler failures, isolates broken historical bindings, and purges them from session scope automatically.
```gleam
// Defined here
gsh(7)> let resp = client.check_ip("8.8.8.8", ctx)
Ok(Response(...)) : Result(Response(String), IpqsError)
// check_ip --> renamed to --> do_check_ip
gsh(20)> client.do_check_ip("8.8.8.8", ctx)
error: Unknown module value
┌─ REPL:26:23
│
26 │ let resp = client.check_ip("8.8.8.8", ctx)
│ ^^^^^^^^ Did you mean `do_check_ip`?
The module `example/integrations/ipqs/client` does not have a `check_ip`
value.
```
- **Lexical Scope Resynchronization:** Replaced manual state tracking with dynamic `active_bindings` propagation in `gsh.gleam`, ensuring session state stays synchronized across hot-code swaps without dropping active shell history.
```gleam
gsh(8)> client.do_check_ip("8.8.8.8", ctx)
Ok(IpQualityScore(True, "Success", 0, "US", "California", "Mountain View", "Google DNS", False, "America/Los_Angeles", "dns.google", False, False, False, False, False, False, False, "N/A", 37.38999939, -122.06999969)) : Result(IpQualityScore, IpqsError)
[debug] latency: 2119.150ms | bindings: 2 | imports: 3
gsh(9)> :cc
Compiling t4z3r
Compiled in 0.22s
// do_check_ip --> renamed to --> check_ip
Ok (Imports hot-reloaded)
gsh(10)> client.check_ip("8.8.8.8", ctx)
Ok(IpQualityScore(True, "Success", 0, "US", "California", "Mountain View", "Google DNS", False, "America/Los_Angeles", "dns.google", False, False, False, False, False, False, False, "N/A", 37.38999939, -122.06999969))
[debug] latency: 1847.289ms | bindings: 2 | imports: 3
```
### Latency & Type Resolution (Hot/Cold Path)
- **Replaced Subprocess Overhead:** Gated gleam export package-interface calls behind needs_export (`is_import` || `is_type` || `is_function`). This eliminated the synchronous **~250ms** CLI process on standard expressions, bringing Hot Path evaluation down to **~20ms**.
- **Added Fast Fallback Inference:** Implemented `infer_or_get_type` in types.gleam to instantly parse primitives (`Int`, `Float`, `Bool`, `String`), operators, and custom constructors locally without spawning an OS shell.
### Compiler Directory & Path Scrubbing
- **Replaced File Location (test/ $\rightarrow$ src/):** Moved temporary `gsh_eval_X.gleam` outputs to `src/` so the compiler includes them in `package_interface.json`.
- **Cleaned Up Output Scrubbing:** Updated `formatter.gleam` to strip `./src/` path prefixes from compiler error traces (rendering as `REPL:line:col`), and updated startup sweeps in `gsh.gleam` to purge leftover orphan files from `src/`.
### Terminal Rendering & Output Alignment
- **Eliminated Blank Line Bugs:** Updated debug_output formatting logic in ` evaluator.gleam` so `"\n"` isn't concatenated when debug mode is disabled.
### Error Formatting & Decoder Fixes
- **Dev Dependency Warning Suppressor:** Added `filter_dev_dep_errors` to `formatter.gleam` to strip out compiler warnings triggered when dynamic `src/` application modules import internal gsh runtime packages.
- **Aligned Decoder Error Types:** Replaced `Nil` mismatch errors in `runner.gleam` with `simplifile.FileError` types to ensure compiler type parity across disk reads.
## How it works
### In-RAM Fast Compilation Pipeline (Sub-20ms Latency)
Rather than spawning heavy OS subprocesses with `gleam build` or writing `.beam` files to disk, GSH compiles and executes code directly in memory:
- **Fast AST Emission:** Executes gleam compile-package --no-beam to instantly convert Gleam code into raw Erlang (.erl) source, bypassing disk artifact writes.
- **Native In-VM Bytecode Loading:** Uses an Erlang FFI bridge (compile:file with [binary] + code:load_binary) to compile .erl files directly into RAM and hot-load the bytecode into the running VM.
- **Result:** Evaluation latency drops from ~375ms down to ~18ms (~20x speedup), delivering real-time interactive feedback below human perception thresholds.
### Single Persistent Node & Side-Effect Memoization
GSH runs inside a single, long-lived Erlang VM node. To prevent historic variable assignments from re-executing side effects (like spawning processes, printing logs, or hitting a database) during session re-evaluations:
- Each `let` binding is automatically wrapped in a type-safe Process Dictionary cache.
- Subsequent prompts reuse the cached memory pointer, ensuring side-effecting code executes exactly once.
### Stateful Lexical Scope Tracking
Session scope is tracked in an explicit ShellState record across evaluations. GSH dynamically merges, prunes, and re-injects:
- Active variable bindings and shadowed variables
- Global module imports and custom type definitions
- Interactive function declarations and command history
### Raw Terminal TUI & I/O Engine
Powered by `etch_erlang`, GSH toggles terminal raw mode on the fly to support character-by-character key handling, live TAB completion, multiline syntax buffering (`...>`), and ANSI color formatting without corrupting background process stdout.
## Feature set comparison with `iex`
| Feature | GSH (Gleam Shell) | IEx (Interactive Elixir) |
|---|---|---|
| **Live App Bootstrapping** | `gleam run -m gsh -- app` | `iex -S mix` |
| **Syntax** | Gleam (Rust-like, strict types) | Elixir (Ruby-like, dynamic) |
| **Syntax Highlighting** | Yes (ANSI-based) | Yes (Configurable ANSI) |
| **Type System** | Static (recompiles on the fly) | Dynamic |
| **Evaluation Engine** | File-backed generation + Hot code reload | Direct Erlang AST evaluation |
| **Side-Effect Safety** | Yes (Process Dictionary memoization) | Yes (Native to AST loop) |
| **VM State Persistence** | Yes (Actors, PIDs, ETS stay alive) | Yes |
| **Fault Tolerance** | Yes (Catches `Badarg` / VM crashes) | Yes |
| **Multiline Input** | Yes (Buffer completion) | Yes (Native AST parsing) |
| **Built-in Helpers** | `pid()` (easily extensible) | `h()`, `i()`, `v()`, `pid()`, etc. |
| **Autocomplete** | Keywords, bound vars, module exports | Deeply context-aware + docstrings |
## Elixir-Style Live Documentation (h command)
By combining intelligent package path resolution with a live `glexer` token stream, the shell locates raw `.gleam` source files, lexes them on the fly, and extracts both module-level documentation and function signatures. This brings the legendary, tactile developer experience of Elixir's `iex` to Gleam, allowing developers to read rich, ANSI-formatted markdown documentation directly in the REPL without requiring modifications to the Gleam compiler.
## Acknowledgments
GSH stands on the shoulders of some excellent Gleam libraries:
- [etch_erlang](https://hex.pm/packages/etch) for non-blocking raw terminal events.
- [contour](https://hex.pm/packages/contour) for beautiful ANSI syntax highlighting.
- [shellout](https://hex.pm/packages/shellout) for seamless Gleam compiler orchestration.
## Contributing
Contributions are massively appreciated! A REPL would be a nice to have tool in the Gleam ecosystem, and there is plenty of room to grow.
## License
This project is licensed under the [Apache-2.0](LICENSE).