Documentation

Eaon docs

The desktop app, the terminal agent, and the HTTP API, in one place. I took every request shape and response field here from the running service. Where the service today differs from the design, this page tells you which one you are looking at.

Quick start

Three steps, and the third one is optional.

  1. 1

    Download and open it

    Grab the build for your platform from the download section, drag it into Applications on macOS, and open it. macOS will need one extra step the first time, covered below.

  2. 2

    Start typing

    Eaon ships connected, so there is no key to paste and no account to make before your first message. Pick a model from the picker at the top and ask it something.

  3. 3

    Make it yours, if you want

    Add your own provider key for direct billing, or install a local model so nothing leaves the machine.

Install on macOS

  1. Open the .dmg.
  2. Drag Eaon into your Applications folder.
  3. Open it from Applications.

Requires macOS 14 Sonoma or later. The build is Universal, so Apple silicon and Intel each run their own native slice.

Opening it the first time

Nothing to do. From 2026.4.0 onward Apple signs and notarizes Eaon, and the notarization ticket ships stapled inside the download. Drag it to Applications and double-click. No right-click trick, no Terminal command, nothing to approve under Privacy & Security. The ticket travels with the app, so it opens on a Mac that has never touched the internet.

If you are updating from an older build

macOS ties the permissions you granted to an app's signature, and Eaon's changed when it became signed. So macOS asks you again for Accessibility, Automation and microphone access, once each, the first time it needs them. Approve them and they stay approved.

Builds before 2026.4.0

Older downloads were unsigned and macOS blocked them on first open. If you are running one of those, the fix is simply to download the current version.

Windows and Linux

Both come from the cross-platform build. They ship on their own release tag, which today sits one version behind the Mac builds.

Windows

Run the x64-setup.exe installer. It is not code-signed yet, so SmartScreen shows a blue "Windows protected your PC" box on first run: click More info, then Run anyway. That is expected, not a sign anything is wrong.

Linux

Use the .AppImage. Make it executable and run it, with no install step and no package manager involved.

bash
chmod +x Eaon_2026.4.0_amd64.AppImage
./Eaon_2026.4.0_amd64.AppImage

Picking a model

The picker at the top of the window groups every model Eaon can reach:

  • Hosted by Eaon: no key, no account, nothing to configure.
  • Your own keys: anything you added under Settings, billed to you.
  • On this Mac: whatever Ollama, llama.cpp or MLX has already pulled.

Switching mid-conversation is fine. The thread comes with you and the next reply arrives from the model you just chose, with a small header naming it so a mixed thread stays readable.

Using your own API key

  1. Open Settings → Add provider.
  2. Pick the company and paste the key. Eaon fetches the models you have access to.
  3. They appear in the picker under that provider's name.

Eaon puts your keys in the macOS Keychain, beside the rest of your passwords, and sends one only as an authorization header on a request you made. No plain-text file, and no copy on Eaon's servers.

Running models locally

Eaon drives three local runtimes and detects whichever you already have:

  • Ollama is the easiest. Install it, pull a model, and Eaon finds it.
  • llama.cpp takes any GGUF from Hugging Face, or a file already on disk.
  • MLX is Apple-silicon-native. Install it with pip3 install mlx-lm.
bash
# install Ollama from ollama.com, then:
ollama pull deepseek-r1:7b

# Eaon picks it up automatically — no restart needed

With a local model loaded, nothing leaves the machine. Not the prompt, not the reply. You can pull the network cable and keep working.

The command palette

K opens a command bar. Jump to any conversation, switch the model, flip the theme, or drop straight into a settings page, all with the arrow keys and Enter.

Shortcuts
KeysDoes
NNew chat
KSearch chats and run commands
PNew project
\Toggle the sidebar
Send
New line
SpaceToggle the floating assistant

Projects and history

  • Projects group related chats so a week on one thing does not bury everything else.
  • History is files on your disk. Export to Markdown or JSON, import it back, or delete the lot from Privacy settings.
  • Pinned chats sit above the by-date list.

Eaon CLI

An agentic coding agent in your terminal: plan mode, sub-agents and real tools, for any model, local or hosted. Nothing to install if you have Node.

bash
# run it without installing
npx eaon-cli

# or install it globally — note the command is `eaon`, not `eaon-cli`
npm install -g eaon-cli
eaon
Package
eaon-cli on npm
Command
eaon
Requires
Node 18.17 or newer
Licence
MIT

On first run it asks you to set up a key, opening a local page in your browser where you can paste an Eaon key, add your own provider keys, or import what Eaon Desktop already has. Press Esc to skip that and use a local Ollama model instead.

It needs an interactive terminal. For scripts and CI, use -p with --auto, which is the only non-interactive form it accepts.

bash
eaon -p "summarise what changed in the last commit" --auto

CLI commands

Type these at the prompt. Type an unrecognised /name and it goes to the model as ordinary text instead of raising an error.

Slash commands
CommandDoes
/helpEvery command and shortcut
/model [name]Switch model; no argument opens a picker
/modelsList hosted, your-own-key and local models
/pull <name>Download an Ollama model
/permission [plan|sandboxed|auto]Change what it may do without asking. Alias /perm
/planJump straight to plan mode
/linkSet keys and providers in the browser
/diffShow uncommitted git changes
/rewind [id]Restore an earlier point; no argument lists them
/resume [id]Reopen a session. Alias /sessions
/clear, /newStart a fresh session
/compactSummarise the conversation to reclaim context
/contextWhat is currently in context
/costToken and cost estimate for this session
/statusModel, mode and session state. Alias /stats
/initWrite an EAON.md for this project
/memoryOpen EAON.md
/export [path]Save the transcript as Markdown
/copyCopy the last reply
/bashesBackground commands. Alias /jobs
/doctorCheck Node, Ollama, your key and the config file
/configConfig path and settings, keys redacted
/exitQuit. Alias /quit

Input prefixes

The CLI handles ! and # itself. Neither reaches the model, and neither interrupts a turn in flight.

PrefixDoes
!commandRun a shell command; the output joins the conversation
@pathReference a file, with autocomplete. Its contents are included
#noteAppend a note to the project's EAON.md
/nameA slash command, with autocomplete

Keys

KeysDoes
TabCycle plan → sandboxed → auto
EscInterrupt the current turn
while workingQueue the message instead of interrupting
TabAccept the highlighted completion
Command history
CtrlC twiceExit
\ then Newline in the composer

CLI flags

FlagDoes
-v, --versionPrint the version
-h, --helpPrint usage
-p, --print <prompt>One-shot, non-interactive. Reply on stdout, tool and status lines on stderr. Requires --auto
--model <key>Start with a given model, e.g. ollama:deepseek-r1:7b
--autoStart in auto permission mode, skipping confirmations
--permission-mode <mode>plan, sandboxed or auto. Prefix-matched. Defaults to sandboxed
--cwd <path>Project root. Defaults to the current directory
--max-steps <n>Cap tool-call steps per turn. Default 40
-c, --continueResume the most recent session in this project
-r, --resume <id>Resume a specific session; an id prefix is enough
--welcomeForce the first-run screen

There are no subcommands. A bare argument is ignored, so eaon "fix the test" opens the interactive UI instead of running the prompt. Use -p for that.

CLI configuration

Settings live in ~/.eaon/cli/config.json, which the CLI writes to a temp file and renames, so a crash mid-save cannot corrupt it. Sessions sit in ~/.eaon/cli/sessions.

Environment variables
VariableDoes
EAON_AQUA_API_KEYYour Eaon API key. Takes precedence over the config file
EAON_OLLAMA_URLOllama base URL. Default http://127.0.0.1:11434
EAON_CONFIG_DIRMove the whole config, session and log directory
EAON_NO_BROWSERNever launch a browser; print the URL instead

The Eaon API

Eaon's gateway speaks the OpenAI chat-completions protocol, so anything that can talk to OpenAI can talk to Eaon by changing two settings: the base URL and the key.

Base URL
https://api.eaon.dev/v1
Protocol
OpenAI-compatible, JSON over HTTPS
Auth
Authorization: Bearer sk-eaon-…
Body limit
1 MB

Every path also works under an /api prefix, so /api/v1/chat/completions reaches the same handler as /v1/chat/completions. Use whichever your proxy setup prefers.

Calling it from a browser

The gateway allows cross-origin requests from eaon.dev, labs.eaon.dev, and localhost or 127.0.0.1 on any port. It returns no permissive origin header to anything else, so a fetch from another site fails in the browser. Server-side calls, curl and native apps are unaffected. A public web page is the wrong place to keep a key in the first place.

What it costs

Nothing. There is no paid tier and no card. The limits further down are about keeping a shared free service usable, not about upselling you.

Before you build against it

Model availability moves. Eaon routes to upstream providers, and which of them are reachable changes, which means the set of servable model ids changes with it. Two habits keep an integration alive through that: ask /v1/models at runtime instead of hardcoding an id, and treat model_not_found and 502 as ordinary conditions you handle. The endpoint contract holds still. The catalog behind it moves.

Authentication

Send your key as a bearer token. Keys look like sk-eaon- followed by 32 hex characters.

http
Authorization: Bearer sk-eaon-0123456789abcdef0123456789abcdef

Getting a key

  1. Create an Eaon account and sign in.
  2. Redeem an invite code. Until you do, Eaon refuses to create one and answers invite_required.
  3. Create a key in the dashboard, or let the CLI's browser flow mint one for you.

When you do not need a key

The Instant tier is designed to need no key at all: no account, rate-limited by IP, with auto as its model id. The protocol below is what an Instant request looks like.

Two models serve it, and you can call either right now with no key:

Instant tier
Model idWhat it is
kimi-k2-thinking-extendedKimi K2 Thinking, via Featherless
minimax-m2-extendedMiniMax M2, via Featherless

Both think before they answer, so give them room. Each spends a couple of hundred tokens on internal reasoning before writing a word, and that comes out of your max_tokens. Ask for 20 and you will get an empty content back, with the budget gone on reasoning you never see. A few hundred is a sensible floor. The reasoning arrives in reasoning_content when the provider sends it.

One provider serves both, and its plan allows four requests at once, so a busy moment can answer 502 where a keyed request on another tier would fail over to a second provider. Retry once before treating it as broken.

bash
# No key, no account. Copy this and run it.
curl https://api.eaon.dev/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"minimax-m2-extended",
       "messages":[{"role":"user","content":"Hello"}],
       "max_tokens":400}'

Everything above the Instant tier needs a key, and which models the gateway can serve shifts with provider availability. Read the live model list instead of hardcoding an id.

Chat completions

POST /v1/chat/completions Key, unless Instant

The main endpoint. Takes an OpenAI chat request, routes it to a provider that can serve the model, and fails over to another if the first one errors.

Body
FieldTypeNotes
messagesrequired array Standard OpenAI messages. Must not be empty.
modeloptional string Defaults to auto. Append :online to any id to turn on web search.
streamoptional boolean true streams the reply as Server-Sent Events.
web_searchoptional boolean Search the web first and add the results to the prompt.
anything elseoptional any Forwarded to the provider untouched: temperature, max_tokens, top_p, tools, response_format, stop, seed and so on. Whether a given provider honours one is up to that provider.

Example

bash
curl https://api.eaon.dev/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $EAON_API_KEY" \
  -d '{
    "model": "auto",
    "messages": [
      {"role": "system", "content": "You are terse."},
      {"role": "user", "content": "Why is the sky blue?"}
    ],
    "temperature": 0.7,
    "max_tokens": 300
  }'

Response

A standard OpenAI completion object, passed through from whichever provider answered, plus one extra eaon object describing how it was routed.

json
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1770000000,
  "model": "deepseek-v3.2",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Rayleigh scattering." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 18, "completion_tokens": 4, "total_tokens": 22 },
  "eaon": {
    "model": "deepseek-v3.2",   // the id actually sent upstream
    "attempted": 1,             // how many providers were tried
    "plan": "instant"          // the plan this request was billed against
  }
}

Eaon does not tell you which upstream served a request. With model: "auto", eaon.model is how you find out which model answered.

Streaming

Set "stream": true. The response is text/event-stream: data: lines carrying OpenAI-shaped delta chunks, ending with data: [DONE]. Eaon forwards the provider's stream byte for byte, so an OpenAI client reads it without changes.

Token usage arrives as a final data: chunk when the provider supports it. Eaon asks for it on your behalf, so you do not have to. Disconnect and the upstream request is dropped too.

bash
curl -N https://api.eaon.dev/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $EAON_API_KEY" \
  -d '{"model":"auto","stream":true,
       "messages":[{"role":"user","content":"Count to five."}]}'

# data: {"choices":[{"delta":{"content":"One"}}]}
# data: {"choices":[{"delta":{"content":", two"}}]}
# data: [DONE]

Using the OpenAI SDKs

Point the official client at Eaon's base URL. Nothing else changes: streaming, tool calls and async behave as they do against OpenAI.

javascript
// npm install openai
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.eaon.dev/v1",
  apiKey: process.env.EAON_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "auto",
  messages: [{ role: "user", content: "Explain a mutex in two sentences." }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

One rough edge: /v1/models returns only id per entry, not the object, created and owned_by fields OpenAI sends. A strict client that validates client.models.list() against the full schema may complain. Chat completions are unaffected.

Listing models

GET /v1/models Optional

The OpenAI-compatible list, filtered to what your key can run, with auto first. Without a key you see the open models. With one you also see whatever your plan reaches.

json
{ "data": [ { "id": "auto" }, { "id": "glm-5.2" } ] }
GET /v1/models/catalog Optional

The richer catalog behind the models page: display names, vendors, descriptions and tiers, plus the plan table. It does not include auto.

Response
KeyHolds
dataEvery entry: id, name, vendor, vendorName, description, tag, tier, and contextLength or weeklyTokens where they apply
groupsEntries bucketed by tier, as { tier, tierLabel, tierNote, models }. Empty tiers are dropped
plansThe same payload as /v1/plans

Do not hardcode model ids. Which models are servable depends on which upstream providers are configured and healthy at that moment, and it changes. Ask /v1/models at runtime and fall back to auto. At the time of writing the open catalog is small, so a named id that worked last month may answer model_not_found today.

Plans and limits

GET /v1/plans Open

Every plan is free. They differ in what they can reach and how fast.

Plans
PlanRequests / minModelsNeeds
instant 10, per IP Instant tier Nothing
plus 10 Instant and Plus Account, invite code, API key
pro 20 Instant, Plus and Ultra, with unlimited flagship use Account, invite code, API key
beta Not capped Preview models, on a weekly token budget Invite only

How the rate limit is counted

A sixty-second sliding window. Eaon counts signed-in callers per account, so every key you own draws on one bucket, and anonymous callers per IP. Go over and you get 429 with a retry_after in seconds.

Enforcement is approximate. The counter lives in the memory of whichever edge instance served you, so a short burst can slip through. Code to the numbers above and back off on 429, and the difference will not reach you.

Response length on frontier models

For the largest models, Eaon caps max_tokens at 30,000 per request, even if you ask for more or leave it out. Everything else passes through as you sent it.

One-shot text

GET /v1/text/{prompt} Key, unless Instant

The prompt goes in the URL and the reply comes back as text/plain, which suits a shell or a keyboard shortcut with no JSON on either side. Errors come back as plain text too.

Query parameters
NameDefaultNotes
modeldeepseek-v4-flashPass auto if you have no key, since the default is not an open model
max_tokens512Anything unparseable falls back to 512
bash
curl "https://api.eaon.dev/v1/text/Name%20three%20primary%20colours?model=auto" \
  -H "Authorization: Bearer $EAON_API_KEY"

# Red, blue, and yellow.

Your account

These describe your own usage and keys. Reads accept either a dashboard session or an API key; anything that changes state requires a session, so a leaked key cannot mint more keys.

GET /v1/account/usage Session or key

Returns windows (today, week, month, year, all time), a daily series, a per-models breakdown, your resolved plan, and betaBudget if you are on the beta tier. Each window carries requests, successes, errors, token totals, success rate and average latency.

GET /v1/account/keys Session or key
POST /v1/account/keys Session only
DELETE /v1/account/keys/{id} Session only

POST takes an optional name and returns the new key once. Listing returns your keys with their usage counts and last-used timestamps.

Service status

GET /health Open

A liveness probe: { "ok": true, "name": "eaon", "version": "…" }. Cheap enough to poll.

GET /v1 Open

Service discovery: the version, whether it is OpenAI-compatible, and a map of the main endpoints. A good first call if you are exploring.

GET /v1/providers Open

One row: ready, degraded or offline, with a model count and whether failover is available. Eaon does not name the individual upstreams.

Errors

Errors are { "error": { "message": …, "code": … } }. Switch on code. The messages are written for people to read and get reworded.

json
{
  "error": {
    "message": "Rate limit exceeded. Try again in 12s.",
    "code": "rate_limit_exceeded",
    "retry_after": 12,
    "limit": 10
  }
}
Codes you should handle
CodeStatusMeans
model_not_found404No provider serves that id. Retry with auto
invalid_api_key401Key missing or unrecognised on a model that needs one
api_key_required401The plan requires a key and none was sent
invite_required403The account has not redeemed an invite code yet
plan_upgrade_required403The model is above your plan. required_plan says which one it needs
flagship_not_included403Flagship models are not on your plan
rate_limit_exceeded429Too many requests. Back off for retry_after seconds
daily_quota_exceeded429Daily flagship allowance used up
beta_weekly_budget_exhausted429Beta weekly budget spent. percent_used is included
appwrite_not_configured503Accounts are unavailable server-side right now

Two shapes that are not like the others

  • A 400 for a missing messages array, and a 502 when every provider failed, carry a message but no code. Read a missing code as "unknown" and carry on.
  • /v1/text/* answers in plain text throughout, including its errors.

Retrying well

A 502 means Eaon already worked through its failover candidates, so an instant retry lands in the same place. Wait a second or two. For 429, honour retry_after. Anything else in the 4xx range is a request you need to change before sending it again.

Skills and MCP servers

A skill is a reusable instruction the model follows on request. Type /skill-name in the message box, or switch one on and let it fire by itself. Install a library from GitHub, point Eaon at local Claude Code skills, or write your own.

An MCP server connects an outside service so a model can read from it and act in it. Eaon ships connectors for a few dozen services, and you can add any custom server by URL.

Eaon treats text a model reads from a file, a webpage or a tool result as information, not as instructions. A booby-trapped document cannot redirect the model, and Eaon shows you anything that tries.

Local API server

Eaon can expose an OpenAI-compatible server on your own machine, so any tool that speaks that protocol reaches whichever model you have configured, hosted or local. Turn it on under Settings → Local API Server.

It binds to the loopback interface, so nothing on your network or the internet can reach it whatever your firewall says. It forwards the conversation you give it and adds nothing: no memory, no custom instructions, no plugin tools.

bash
# the app shows the exact port and key to use
export OPENAI_BASE_URL=http://127.0.0.1:1234/v1
export OPENAI_API_KEY="<the key Eaon shows you>"

Troubleshooting

macOS says the app is damaged or cannot be opened

You have a build from before 2026.4.0, which was unsigned. Download the current version and it opens on a double-click.

A model is failing or timing out

Eaon fails over on its own, so one bad response tends to sort itself out. If a model keeps failing, pick another, or use auto and let the router choose.

The API returns model_not_found for an id that used to work

Model availability follows provider availability, and that moves. Ask /v1/models for the current list and fall back to auto. More integrations break on this than on anything else, which is why the advice above is to skip hardcoded ids.

A local model will not load

Check the runtime is up: ollama list should answer. Then check the model fits in the memory you have free. Settings → Hardware shows the same numbers Eaon reads before it tries.

Something else

Email support@eaon.dev or ask in the Discord. If it is an API problem, include the code from the error and the rough time it happened.

Something here wrong or missing? Tell us and it gets fixed.