Contents

Installing EONYX: build, run, and configure

EONYX is a terminal AI agent for working with code, written in Go. It connects directly to any OpenAI-compatible API (OpenAI, OpenRouter, DeepSeek, Moonshot, local Ollama / llama.cpp / LM Studio) — no external services, no telemetry, no OAuth. One process, one binary, everything local. Here: installation, the first run, and the full configuration.

  • Go 1.26+ — to build from source (prebuilt binaries don’t need it).
  • An API key for any OpenAI-compatible provider — or a local model server (Ollama, llama.cpp, LM Studio), in which case no key is needed.
  • A terminal: Windows Terminal / PowerShell, any Linux shell, macOS Terminal.
git clone https://github.com/avtotor/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 into the binary at build time (git describe --tags) — there are no hardcoded versions in the code:

eonyx --version
Releases are built by GoReleaser on every v* git tag — download the archive for your platform from the Releases page and put the binary in your PATH. Linux, macOS, and Windows are supported.
cd my-project
eonyx

An interactive TUI chat opens. If there is no configuration at any level yet, EONYX creates a working eonyx.json in the global directory itself — with two DeepSeek models and one local one (Ollama). All that’s left is to set the key:

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

The run subcommand executes a task without the TUI and then exits:

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

It also works in a pipe. The argument takes priority over stdin, so when piping don’t duplicate the prompt as an argument — otherwise the content from stdin is ignored:

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

The response text is streamed to stdout, while tool activity and token statistics go to stderr, so you can safely redirect the output into a file or a pipe.

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 distinguish outcomes 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
The output of a shell command started via ! is written into the conversation — the model sees what you ran and what changed (the current directory or environment variables, for example).

EONYX reads eonyx.json from three levels and merges them top down — each next level overrides the previous one:

  • 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 — searched for up the tree from the current directory

The directory where the project config is found becomes the project root: a service folder .eonyx (exports, logs) appears inside it. The paths can be overridden with the EONYX_GLOBAL_CONFIG, EONYX_GLOBAL_DATA, and EONYX_CACHE_DIR environment variables. To see what was loaded from where: eonyx info and eonyx config.

Strict schema
The parser rejects unknown fields, wrong types, and malformed JSON — EONYX refuses to start rather than silently ignoring a typo. For an unknown field the message contains its name and line number; for a wrong type or malformed JSON — a generic error message.

This is the default config EONYX creates on the first run — it contains 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 only 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"
  }
}

The api_key and base_url fields can reference environment variables ($OPENAI_API_KEY) — they are expanded after the layers are merged, so secrets are not stored 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 limits the number of tool-call rounds within a single turn (50 by default).

The safe tools — read, ls, glob, grep, git_status, git_diff, git_log, todo_write — are always allowed. Everything else (writing, editing, bash, git commits, MCP tools…) requires permission:

"permissions": {
  "allowed_tools": ["bash", "write", "edit"],
  "sandbox": true
}
  • allowed_tools — the list of tools that run without asking.
  • sandbox — locks the file tools (read/write/edit/multi_edit/ls) inside the project root; off by default.
  • --yolo (or /yolo in the TUI) — disables the gate entirely for the duration of the session.

In the TUI an interactive layer works on top of the static policy: for a tool that isn’t in the allowlist, EONYX shows an Allow once / Allow always (this session) / Deny dialog. A denial doesn’t kill the turn — the model receives it as a tool error and adapts.

Planning mode
/plan enables a stricter gate: only read tools are available, the agent explores the code and proposes a plan. Once the plan is ready, EONYX asks you: implement it, allow edits, or keep planning.

Hooks are shell commands around tool calls. The format: 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 before the tool; a non-zero exit code blocks the call.
  • after runs after it; its output is appended to the tool result (the model sees it). Errors in after don’t break the result.
  • Context is passed through environment variables rather than by substitution into the command text — injection via the input content 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; hooks run in the same shell session as the bash tool.
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