Yeesh
A LiveView terminal component with sandboxed command execution.
Yeesh provides a browser-based CLI with fish/zsh-like features (tab completion, command history, prompt customization) and Dune-powered sandboxed Elixir evaluation.
Features
- xterm.js-powered terminal -- full terminal emulation in the browser with GPU-accelerated rendering, ANSI colors, scrollback, selection, and web links
- Command behaviour -- define custom commands with a simple behaviour
- Tab completion -- command name completion out of the box
- Command history -- up/down arrow navigation through previous commands
- Sandboxed Elixir REPL -- evaluate Elixir code safely via Dune, with configurable allowlists, memory/reduction limits, and atom leak prevention
- ANSI output helpers --
Yeesh.Outputprovides colored/styled output - Per-session state -- each terminal instance gets isolated history, environment variables, and Dune session state
Installation
Add yeesh to your dependencies in mix.exs:
def deps do
[
{:yeesh, "~> 0.1.0"}
]
endInstall the JavaScript dependencies:
npm install --prefix assets @xterm/xterm @xterm/addon-fit @xterm/addon-web-links
Register the hook in your app.js:
import { YeeshTerminal } from "../../deps/yeesh/assets/js/yeesh/hook.js"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { YeeshTerminal }
})
The hook imports @xterm/xterm/css/xterm.css, which esbuild extracts into a
separate CSS bundle alongside your app.js output. Add a second stylesheet
link in your root.html.heex to load it:
<link phx-track-static rel="stylesheet" href={~p"/assets/js/app.css"} />Quick Start
Add the terminal component to any LiveView:
<.live_component
module={Yeesh.Live.TerminalComponent}
id="terminal"
commands={[]}
prompt="app> "
/>
This gives you a working terminal with all built-in commands: help, clear,
history, echo, env, and elixir (sandboxed REPL).
Custom Commands
Implement the Yeesh.Command behaviour:
defmodule MyApp.Commands.Deploy do
@behaviour Yeesh.Command
@impl true
def name, do: "deploy"
@impl true
def description, do: "Deploy the application"
@impl true
def usage, do: "deploy [environment]"
@impl true
def execute([], session), do: {:error, "specify an environment", session}
def execute([env], session) do
# Your deployment logic here
{:ok, "Deployed to #{env}", session}
end
endRegister it in the component:
<.live_component
module={Yeesh.Live.TerminalComponent}
id="terminal"
commands={[MyApp.Commands.Deploy]}
/>Elixir REPL
The built-in elixir command provides a sandboxed Elixir evaluation
environment powered by Dune:
$ elixir 1 + 2
3
$ elixir
Entering sandboxed Elixir REPL (powered by Dune).
Type 'exit' to return to the shell.
iex> x = 42
42
iex> x * 2
84
iex> exit
$Variables persist within the session. Dangerous functions (file system, network, code loading) are restricted by Dune's allowlist.
Configure the sandbox:
<.live_component
module={Yeesh.Live.TerminalComponent}
id="terminal"
sandbox_opts={[timeout: 10_000, max_reductions: 100_000]}
/>Configuration
:prompt-- prompt string (default:"$ "):commands-- list of command modules (default:[]):theme-- terminal theme,:defaultor:light(default::default):context-- arbitrary map passed to commands (default:%{}):sandbox_opts-- Dune sandbox configuration (default:[])
Execution Model
Command execution is currently synchronous -- the LiveView process blocks until the command completes (with a configurable timeout, default 5s).
Async streaming execution is planned for Milestone 3.
Roadmap
- Milestone 2: Argument-level tab completion, fish-style auto-suggestions, syntax highlighting, Ctrl+R history search, aliases, theming, OS command passthrough (explicit opt-in with allowlist)
- Milestone 3: Async streaming execution for long-running commands, pipe support, output paging, session persistence
License
MIT