Contents

Installing EONYX: build, run, and configure

EONYX is a terminal AI agent for working with code, written in Go.
It connects straight to any OpenAI-compatible API.
OpenAI, OpenRouter, DeepSeek, Moonshot.
Or a local Ollama, llama.cpp or LM Studio server.
No telemetry, no OAuth.
One process, one binary.

  • Go 1.26+ to build from source. A prebuilt binary needs nothing.
  • An API key for any OpenAI-compatible provider. A local model server needs no key at all.
  • A terminal: Windows Terminal, PowerShell, any Linux shell, macOS Terminal.
  • A licence. One key binds one machine, and it is verified at every startup.
git clone https://github.com/eonyx-core/eonyx
cd eonyx
make build        # builds bin/eonyx with the version from the git tag

Or install into $GOPATH/bin:

make install

The version is baked in at build time from git describe --tags.
Nothing is hardcoded in the code:

eonyx --version
GoReleaser builds a release on every v* tag.
Grab the archive for your platform from the Releases page and put the binary on your PATH.
Linux, macOS and Windows are covered.
cd my-project
eonyx

The interactive TUI opens.
With no config at any level, EONYX writes a working eonyx.json into the global directory itself.
It ships two DeepSeek models and one local Ollama one.
All that is left is the key:

export DEEPSEEK_API_KEY=sk-...   # or your own provider, see below
Headless mode for scripts and CI

run does the task without the TUI and exits:

eonyx run "describe what this repository does"
eonyx run -m deepseek-v4-pro --yolo "fix the failing test"

It works in a pipe too.
The argument beats stdin, so when piping do not repeat the prompt as an argument.
Otherwise stdin is ignored:

{ echo "explain the error:"; cat error.log; } | eonyx run

The answer streams to stdout.
Tool activity and token stats go to stderr.
So redirecting into a file or a pipe is safe.

Flags:

  • --model/-m — the model
  • --session/-s <id> — continue a specific session
  • --continue/-c — continue the most recent one
  • --yolo — disable tool confirmations
  • --json — events as NDJSON instead of text
  • --system-prompt — replace the system prompt

Exit codes tell outcomes apart for CI:

  • 0 — success
  • 2 — usage error
  • 3 — API error
  • 4 — the task finished, but a tool was blocked by the permission gate
  • 5 — hit the max_steps limit
  • 130 — interrupted with Ctrl+C
CommandPurpose
eonyxinteractive TUI chat
eonyx run "…"headless agent run
eonyx sessionslist of saved sessions
eonyx infoconfiguration, data, and project paths
eonyx configthe final (merged) configuration
eonyx cleanremove its own configuration files
KeyAction
Entersend the message
Ctrl+Jline break in the input field
↑ / ↓history of your messages
Escinterrupt the running turn
/command palette (on empty input)
@file path autocompletion (Tab inserts)
?help (on empty input)
Ctrl+Pcommand palette
Ctrl+Nswitch model
Ctrl+Ssession manager
Ctrl+Kskills and MCP servers (on/off)
Ctrl+Oexpand/collapse tool output
Ctrl+Rexpand/collapse the model’s reasoning
! <cmd>run a shell command
Ctrl+Cquit

Slash commands: /new, /clear, /add [path], /rename <title>, /fork, /export, /yolo, /plan, /timestamps, /model, /sessions [query], /help, /quit.

The '!' command
Output from a ! command is written into the conversation.
The model then sees what you ran and what changed, such as the directory or an environment variable.

EONYX reads eonyx.json from three levels and merges them top down.
Each level overrides the one before it.

  • Global config
    • Windows — %LOCALAPPDATA%\eonyx\eonyx.json
    • Linux/macOS — $XDG_CONFIG_HOME/eonyx/eonyx.json (~/.config/… by default)
  • Global data
    • Windows — the same path
    • Linux/macOS — $XDG_DATA_HOME/eonyx/eonyx.json (~/.local/share/… by default)
  • Project config
    • eonyx.json or .eonyx.json, found by walking up from the current directory

Wherever the project config is found becomes the project root.
A service folder .eonyx for exports and logs appears inside it.
EONYX_GLOBAL_CONFIG, EONYX_GLOBAL_DATA and EONYX_CACHE_DIR override the paths.
eonyx info and eonyx config show what was loaded and from where.

Strict schema
The parser rejects unknown fields, wrong types and malformed JSON.
EONYX refuses to start rather than quietly ignore a typo.
An unknown field is reported with its name and line number.
A wrong type or broken JSON gets a generic message.

This is the default config written on the first run.
It carries every supported top-level key:

{
  "providers": {
    "deepseek": {
      "type": "openai",
      "base_url": "https://api.deepseek.com",
      "api_key": "$DEEPSEEK_API_KEY"
    },
    "local": {
      "type": "openai",
      "base_url": "http://localhost:11434/v1",
      "api_key": ""
    }
  },
  "models": [
    {"id": "deepseek-v4-flash", "provider": "deepseek", "context_window": 1000000, "input_price": 0.14, "output_price": 0.28},
    {"id": "deepseek-v4-pro", "provider": "deepseek", "context_window": 1000000, "input_price": 0.435, "output_price": 0.87},
    {"id": "qwen2.5-coder", "provider": "local", "context_window": 32768, "input_price": 0, "output_price": 0}
  ],
  "default_model": "deepseek-v4-flash",
  "max_steps": 50,
  "permissions": {
    "allowed_tools": []
  },
  "skills": [],
  "mcp": [],
  "hooks": {},
  "report": {
    "database_url": ""
  },
  "system_prompt": "",
  "log_file": "",
  "retry": {}
}

A provider is a way to reach an API.
There is one type so far, "openai", and it covers any OpenAI-compatible endpoint:

"providers": {
  "openrouter": {
    "type": "openai",
    "base_url": "https://openrouter.ai/api/v1",
    "api_key": "$OPENROUTER_API_KEY"
  }
}

api_key and base_url may reference environment variables such as $OPENAI_API_KEY.
They expand after the layers merge, so no secret sits in the file.

A model binds an id to a provider:

FieldPurpose
idthe model name as the API understands it
providera key from the providers block
context_windowcontext size in tokens — triggers auto-summarization of history
input_priceprice per 1M input tokens (USD), for the cost counter in the header
output_priceprice per 1M output tokens (USD)

max_steps caps the tool-call rounds inside one turn.
The default is 50.

The safe tools are always allowed: read, ls, glob, grep, git_status, git_diff, git_log, todo_write.
Everything else needs permission: writing, editing, bash, commits, MCP tools.

"permissions": {
  "allowed_tools": ["bash", "write", "edit"],
  "sandbox": true
}
  • allowed_tools — tools that run without asking.
  • sandbox — locks read, write, edit, multi_edit and ls inside the project root. Off by default.
  • --yolo, or /yolo in the TUI — drops the gate for the whole session.

The TUI adds an interactive layer on top.
A tool outside the allowlist raises an Allow once / Allow always (this session) / Deny dialog.
A denial does not kill the turn: the model gets a tool error and adapts.

Planning mode
/plan switches on a stricter gate.
Only read tools stay available, so the agent studies the code and proposes a plan.
When the plan is ready EONYX asks: implement it, allow edits, or keep planning.

Hooks are shell commands around tool calls.
The format is tool name → before / after:

"hooks": {
  "write": {
    "before": "echo \"writing: $EONYX_INPUT\" >> .eonyx/audit.log",
    "after":  "gofmt -l ."
  },
  "bash": {
    "before": "[ \"$EONYX_CWD\" = \"$HOME\" ] && exit 1 || exit 0"
  }
}

Rules:

  • before runs first. A non-zero exit code blocks the call.
  • after runs on success. Its output is appended to the tool result, so the model sees it. An error there does not break the result.
  • Context arrives through environment variables, never substituted into the command text. Injection through the input is impossible.
    • EONYX_TOOL — the tool name
    • EONYX_PHASEbefore / after
    • EONYX_CWD — the working directory
    • EONYX_INPUT — the JSON input
    • EONYX_RESULT — the result, only in after
  • Each hook gets 30 seconds, in the same shell session the bash tool uses.
KeyPurpose
skillsadditional skill directories (see the Skills article)
mcpthe list of MCP servers (see the MCP article)
report.database_urlPostgreSQL for the read-only reporting tools
empty — the tools are not registered
system_prompta full replacement for the generated system prompt
log_filea log path instead of the standard one; supports $ENV
retrymax_retries — 3 by default
base_delay_ms — 500, doubles up to 30 s
response_header_timeout_sec — 60
Cheat sheet
  • Installation
    • make build from source or a binary from GitHub Releases
    • check: eonyx --version
  • Running
    • eonyx — interactive TUI
    • eonyx run "task" — headless, stdout/stderr separated
  • Configuration
    • the eonyx.json cascade: global → data → project
    • strict schema, $ENV references for secrets
  • Models
    • any OpenAI-compatible API
    • context_window triggers auto-summarization
    • prices — the cost counter in the header
  • Permissions
    • reading is always allowed
    • everything else — via allowed_tools, the TUI dialog, --yolo, or /plan
  • Hooks
    • before / after around tools
    • context through EONYX_* variables
    • before can block