Yeesh

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

Installation

Add yeesh to your dependencies in mix.exs:

def deps do
  [
    {:yeesh, "~> 0.1.0"}
  ]
end

Install the JavaScript dependencies:

npm install --prefix assets @xterm/xterm @xterm/addon-fit @xterm/addon-web-links lit

Import the Yeesh terminal web component into your app.js:

import "phoenix-colocated/yeesh"

Insert the import line high above in the app.js, ideally immediately after the import {LiveSocket} from "phoenix_live_view" line.

Under the <link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} /> line in your root.html.heex add the following line:

<Yeesh.Live.TerminalComponent.xterm_style/>

Quick Start

Add the terminal component to any LiveView:

<.live_component
  module={Yeesh.Live.TerminalComponent}
  id="terminal"
  commands={[]}
  prompt="app> "
/>

By default, only the help built-in command is registered.

Built-in Commands

Yeesh ships with several built-in commands: help, clear, history, echo, env, and elixir (sandboxed REPL). The :builtins assign controls which of these are available:

Value Effect
:help (default) Only the help command
:all All built-in commands
:none No built-in commands at all
list of modules Exactly those modules
<%!-- All built-ins --%>
<.live_component
  module={Yeesh.Live.TerminalComponent}
  id="terminal"
  builtins={:all}
/>

<%!-- Only help + history --%>
<.live_component
  module={Yeesh.Live.TerminalComponent}
  id="terminal"
  builtins={[Yeesh.Builtin.Help, Yeesh.Builtin.History]}
/>

<%!-- No built-ins at all --%>
<.live_component
  module={Yeesh.Live.TerminalComponent}
  id="terminal"
  builtins={:none}
  commands={[MyApp.Commands.Status]}
/>

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
end

Register it in the component:

<.live_component
  module={Yeesh.Live.TerminalComponent}
  id="terminal"
  builtins={:all}
  commands={[MyApp.Commands.Deploy]}
/>

Command Grouping

The help command groups output automatically based on command names. Command names may contain dots (.), dashes (-), and underscores (_) as separators. The text before the first separator determines the group:

Groups are displayed in order: Built-in first, Generic second, then custom groups alphabetically.

Explicit groups

Implement the optional group/0 callback to override automatic grouping:

defmodule MyApp.Commands.Migrate do
  @behaviour Yeesh.Command

  @impl true
  def name, do: "db.migrate"

  @impl true
  def group, do: "Database"

  @impl true
  def description, do: "Run database migrations"

  @impl true
  def usage, do: "db.migrate [--step N]"

  @impl true
  def execute(_args, session), do: {:ok, "Migrated", session}
end

Without group/0, this command would appear under "Db" (derived from the name prefix). With it, it appears under "Database" instead.

Example output

Built-in:
  help            Show available commands or help for a specific command
  clear           Clear the terminal screen

Generic:
  deploy          Deploy the application

Database:
  db.migrate      Run database migrations
  db.seed         Seed the database

Sys:
  sys.info        Show system information
  sys.health      Run health checks

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

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

License

MIT