Skip to main content

guides/tui.md

# Terminal chat (mix athena.chat)

`mix athena.chat` drops you into a full-screen TUI powered by
[ExRatatui](https://github.com/livebook-dev/ex_ratatui). It connects to
the ExAthena agent loop, streams tokens live, and surfaces tool calls and
their results inline.

## Prerequisites

The TUI requires the optional `:ex_ratatui` dependency:

```elixir
# mix.exs
{:ex_ratatui, "~> 0.10"}
```

ExAthena declares it `optional: true` so projects that only use the
programmatic API don't pull it in. Without it, `mix athena.chat` prints
an error and exits.

## Launching

```bash
mix athena.chat
mix athena.chat --provider llamacpp
mix athena.chat --provider ollama --model qwen2.5-coder:14b
mix athena.chat --mode plan_and_solve
mix athena.chat -p ~/projects/my_app
```

### Flags

| Flag | Alias | Description |
|---|---|---|
| `--provider NAME` | | Which provider to use (`:ollama`, `:llamacpp`, `:openai_compatible`, `:claude`, `:gemini`, `:openrouter`, or a JSON-defined name). Defaults to `config :ex_athena, default_provider:`. |
| `--model NAME` | | Initial model. Overrides the provider's configured default. |
| `--mode NAME` | | Agent mode: `react`, `plan_and_solve`, or `reflexion`. |
| `--path PATH` | `-p` | Working directory for filesystem tools. `~` is expanded. The chat process does **not** `cd` — tools just receive this as their `cwd`. |

## Selecting a provider

### Built-in providers

Providers are configured in `config/config.exs`. The TUI reads the same
configuration your application uses at runtime:

```elixir
# Ollama (local, free — requires `ollama serve`)
config :ex_athena, default_provider: :ollama
config :ex_athena, :ollama,
  base_url: "http://localhost:11434",
  model: "qwen2.5-coder:14b"

# llama.cpp (local, free — requires `llama-server --model ...`)
config :ex_athena, :llamacpp,
  base_url: "http://localhost:8080",
  model: "qwen2.5-coder"

# OpenAI / OpenAI-compatible
config :ex_athena, :openai_compatible,
  base_url: "https://api.openai.com/v1",
  api_key: System.get_env("OPENAI_API_KEY"),
  model: "gpt-4o-mini"

# Anthropic Claude
config :ex_athena, :claude,
  api_key: System.get_env("ANTHROPIC_API_KEY"),
  model: "claude-opus-4-5"

# Google Gemini
config :ex_athena, :gemini,
  api_key: System.get_env("GOOGLE_API_KEY"),
  model: "gemini-2.5-flash"

# OpenRouter
config :ex_athena, :openrouter,
  api_key: System.get_env("OPENROUTER_API_KEY"),
  model: "anthropic/claude-opus-4-5"
```

Then launch with the provider you configured:

```bash
mix athena.chat --provider openai_compatible
mix athena.chat --provider gemini --model gemini-2.5-pro
```

### JSON-defined providers

ExAthena loads every `*.json` file in `~/.config/ex_athena/providers/` at
startup. Drop a file there to define a named provider without touching
`config.exs` — useful for personal keys that shouldn't live in a project
repository:

```json
{
  "name": "groq",
  "adapter": "req_llm",
  "req_llm_provider_tag": "openai",
  "base_url": "https://api.groq.com/openai/v1",
  "api_key_env": "GROQ_API_KEY",
  "default_model": "llama-3.3-70b-versatile"
}
```

```bash
mix athena.chat --provider groq
```

The `api_key_env` field names the environment variable that holds the key.
The key is read once at application startup and held in memory for the
session — it is never written to disk by the TUI. Use `api_key_env`
instead of a literal `api_key` field for any file that may be shared.

The `api_key_prompt: true` field has no effect in the TUI — it only
activates the inline key field in the web sidebar (`mix athena.web`).
See the [Web UI guide](web.md#inline-api-key-prompt) for details.

See the [providers guide](providers.md) for the full JSON schema and
ready-to-copy examples for Groq, Together AI, Fireworks, DeepSeek, and
others.

## Layout

```
┌─────────────────────────────────────────────────────┬────────────────────┐
│ ollama · qwen2.5-coder:14b · react  iter 3  1.2k t  │ Timeline  Changes  │
├─────────────────────────────────────────────────────┤                    │
│                                                     │ iteration 1        │
│  You: refactor the auth module                      │ → read             │
│                                                     │   {"path":"lib/…"} │
│  Assistant: I'll start by reading the current …     │ ← result           │
│                                                     │   defmodule Auth…  │
│  ● read(lib/auth.ex)                                │ iteration 2        │
│    └ defmodule Auth do                              │ → edit             │
│                                                     │   …                │
│  ● edit(lib/auth.ex)                                │                    │
│    └ patch applied (14 lines)                       │                    │
│                                                     │                    │
├─────────────────────────────────────────────────────┤                    │
│ ┌─────────────────────────────────────────────────┐ │                    │
│ │ /                                               │ │                    │
│ └─────────────────────────────────────────────────┘ │                    │
├─────────────────────────────────────────────────────┴────────────────────┤
│ Enter: send  Ctrl+C: quit  /help                                          │
└───────────────────────────────────────────────────────────────────────────┘
```

- **Header** — active provider, model, mode, iteration count, token usage, and accumulated cost.
- **Messages** (left, flex height) — streaming assistant text, tool-call blocks with inline output previews.
- **Details** (right pane) — Timeline tab shows the full tool argument / result for every call; Changes tab shows `git diff HEAD` refreshed after each tool result.
- **Input** — multiline textarea. `Enter` sends; `Shift+Enter` inserts a newline.
- **Footer** — context-sensitive keyboard hints.

## Model and mode pickers

Typing `/model` or `/mode` with no argument opens a scrollable popup over
the messages area:

```
┌──────────────────────────────┐
│ Select model                 │
│ ▶ qwen2.5-coder:14b          │
│   llama3.1                   │
│   mistral-nemo               │
│   phi-3.5                    │
└──────────────────────────────┘
```

- `` / `k` — move up
- `` / `j` — move down
- `Enter` — confirm selection
- `Esc` — close without selecting

Or set directly to skip the picker:

```
/model mistral-nemo
/mode plan_and_solve
```

## Slash commands

Type `/` in the input to open autocomplete. A dropdown shows matching
commands and their descriptions as you type the verb. Press `Tab` or
`Enter` on a suggestion to complete it.

| Command | Description |
|---|---|
| `/help`, `/?` | Show full usage help |
| `/clear` | Wipe the conversation history and start a fresh session |
| `/tools` | List the tools currently available to the agent |
| `/model [name]` | Open the model picker, or set the model directly |
| `/mode [name]` | Open the mode picker, or set the mode directly (`react`, `plan_and_solve`, `reflexion`) |
| `/expand [N]` | Show the full text of the Nth most-recent tool result (default: 1 = most recent) |
| `/details [on\|off]` | Show or hide the right details pane; no argument toggles |
| `/tab` | Cycle the details pane tab: Timeline ↔ Changes |
| `/timeline` | Switch to the Timeline tab |
| `/diff [side\|inline]` | Switch to the Changes tab; optionally set the layout (`side` = side-by-side, `inline` = unified diff) |
| `/cd PATH` | Set the working directory for filesystem tools (`~` is expanded; the chat process does not chdir) |
| `/pwd` | Print the current working directory |
| `/mouse [on\|off]` | Toggle crossterm mouse capture; `off` restores native terminal text selection |
| `/exit`, `/quit`, `/q` | Leave the chat |

## Keyboard shortcuts

| Key | Action |
|---|---|
| `Enter` | Send message |
| `Shift+Enter` | Insert newline |
| `Ctrl+C` | Quit |
| `` / `` | Navigate input history (outside a picker) |
| `` / `k`, `` / `j` | Move picker / autocomplete selection |
| `Esc` | Close picker without selecting |
| `PgUp` / `PgDn` | Scroll the messages pane by ~10 rows |
| `Shift+PgUp` / `Shift+PgDn` | Scroll the details pane |
| `End` | Jump both panes back to the bottom (auto-follow mode) |
| Mouse wheel | Scroll the pane under the cursor (requires mouse capture on) |

Mouse capture is on by default. Use `/mouse off` to drop back to native
terminal copy/paste (click-drag to select). Many terminals also let you
bypass capture temporarily by holding `Option` (macOS) or `Shift` while
click-dragging.

## Streaming and thinking

Tokens stream directly into the messages pane as they arrive. Models that
emit `<think>` or `<thinking>` blocks have those routed to the details
pane in real time — you see the reasoning on the right while the final
answer builds on the left.

## Tool output

Each tool call renders as a collapsible block in the messages pane:

```
● read(lib/auth.ex)
  └ defmodule Auth do
      @moduledoc "…
      …4 more lines (18 total)
```

The preview shows up to four lines. To see the full output, use `/expand`
(or `/expand 2` for the second-most-recent result). The details pane
always shows the complete text.

## Tips

- **Working directory**: set `-p ~/projects/my_app` at launch or use `/cd`
  during the session. The TUI shows the active path in the details pane
  header after `/cd`.
- **Long responses**: if the model is still running and you need to scroll
  up to re-read earlier output, `PgUp` pauses auto-follow. `End` or
  sending your next message resumes it.
- **Side-by-side diff**: `/diff side` switches the Changes tab to a
  two-column layout. `/diff inline` or `/diff` switches back to unified.
- **Mode switching**: `plan_and_solve` adds an explicit planning phase
  before the agent executes tools — useful for complex multi-file tasks.
  `reflexion` adds a self-critique loop after each iteration.