Configuration#
Installation#
Installation has moved to its own page. See Install ECA for a guided walkthrough covering editor plugins (Emacs, VS Code, Neovim, IntelliJ), the Desktop app, and manual install methods (script, Homebrew, mise, GitHub Releases).
Ways to configure#
There are multiples ways to configure ECA:
Convenient for users and multiple projects
{
"defaultAgent": "plan"
}
Convenient for users
{
"defaultAgent": "plan"
}
Convenient for editors but uncommon
Client editors can pass custom settings when sending the initialize request via the initializationOptions object:
"initializationOptions": {
"defaultAgent": "plan"
}
Via env var during server process spawn:
ECA_CONFIG='{"myConfig": "my_value"}' eca server
Merge extra files last, useful for machine-specific or uncommitted overrides (e.g. gitignored *.local.json files):
{
"extraConfigs": ["~/.config/eca/config.local.json", ".eca/config.local.json"]
}
Relative paths resolve against the workspace root; ~ and absolute paths are supported. Missing files are logged and skipped.
Merge order#
Config sources are deep merged from lowest to highest priority (later sources win on conflicts):
- Built-in defaults
initializationOptions(from the editor'sinitializerequest)ECA_CONFIGenv var- Global config file
~/.config/eca/config.json(or the--config-filepath, which replaces the global/local lookup) - Local project config
.eca/config.json(per workspace root) - Files listed in
extraConfigs, in their listed order
Deep merge means nested maps are merged recursively, while scalars and arrays from a higher-priority source replace (not concatenate) the lower-priority value.
Dynamic string contents#
It's possible to retrieve content of any configs with a string value using the ${key:value} approach, being key:
file:${file:/path/to/my-file}or${file:../rel-path/to/my-file}to get a file contentenv:${env:MY_ENV}to get a system env value with support for default values:${env:MY_ENV:foo}classpath:${classpath:path/to/eca/file}to get a file content from ECA's classpathnetrc: Support Unix RC credential filescmd:${cmd:some command}to run a command via the platform shell (bash -con POSIX, PowerShell on Windows) and use its trimmed stdout — useful for password managers like${cmd:pass show eca/api-key}or${cmd:op read op://vault/Item/credential}. On non-zero exit/timeout the value falls back to an empty string and a warning is logged. Since config is re-read frequently, a successful (non-empty) result is cached for 5 minutes per command, so slow commands like token fetchers are not respawned on every read; failures are not cached and are retried on the next read. The command itself cannot contain}(the closing brace ends the placeholder); for commands likeawk '{print $1}' …, wrap them in a small script or shell function.
macOS GUI launches
When ECA runs from Finder/Dock (e.g. via ECA Desktop) the inherited PATH is minimal and Homebrew, mise/asdf shims, etc. are not visible. To fix this, on macOS the cmd backend spawns the user's interactive login shell once ($SHELL -ilc '…') and reuses the captured $PATH for subsequent ${cmd:...} resolutions — so anything sourced from .zshrc/.zprofile/.bash_profile is picked up automatically. If the shell query fails or your shell isn't bash/zsh/sh/dash/ksh, ECA falls back to prepending /opt/homebrew/bin, /usr/local/bin and ~/.local/bin. As a last resort, use an absolute path (e.g. ${cmd:/opt/custom/bin/my-tool ...}).
Linux GUI launches can have a similar (less severe) PATH gap, but shell-PATH discovery is currently macOS-only — Linux users with the same problem should use absolute paths in ${cmd:...} for now.
Info
This is supported in markdown configurations of agents and skills as well, giving flexibility to template it.
Schema#
ECA has a config.json schema to validate and autocomplete in your editor.
Default config#
By default ECA consider the following as the base configuration:
{
"providers": {
"openai": {"url": "https://api.openai.com"},
"anthropic": {"url": "https://api.anthropic.com"},
"github-copilot": {"url": "https://api.githubcopilot.com"},
"google": {"url": "https://generativelanguage.googleapis.com/v1beta/openai"},
"ollama": {"url": "http://localhost:11434"}
},
"defaultModel": null, // let ECA decides the default model.
"netrcFile": null, // search ~/.netrc or ~/_netrc when null.
"hooks": {},
"rules" : [],
"commands" : [],
"skills": [],
"disabledTools": [],
"mcpToolSearch": {
"deferAllWhenTotalTokensExceedPercentOfContext": null,
"includePattern": [],
"excludePattern": []
},
"toolCall": {
"approval": {
"byDefault": "ask",
"allow": {"eca__directory_tree": {},
"eca__read_file": {},
"eca__grep": {},
"eca__preview_file_change": {},
"eca__editor_diagnostics": {}},
"ask": {},
"deny": {}
},
"readFile": {
"maxLines": 2000
},
"shellCommand": {
"summaryMaxLength": 35
},
"editorNav": {
"enabled": true
}
},
"mcpTimeoutSeconds" : 60,
"mcpKeepAliveSeconds" : 30,
"lspTimeoutSeconds" : 30,
"mcpServers" : {},
"agent": {
"code": {"prompts": {"chat": "${classpath:prompts/code_agent.md}"},
"disabledTools": ["preview_file_change"]},
"plan": {"prompts": {"chat": "${classpath:prompts/plan_agent.md}"},
"disabledTools": ["edit_file", "write_file", "move_file"],
"toolCall": {"approval": {"deny": {"eca__shell_command":
{"argsMatchers": {"command": [".*[12&]?>>?\\s*(?!/dev/null($|\\s))(?!&\\d+($|\\s))\\S+.*",
".*\\|\\s*(tee|dd|xargs).*",
".*\\b(sed|awk|perl)\\s+.*-i.*",
".*\\b(rm|mv|cp|touch|mkdir)\\b.*",
".*git\\s+(add|commit|push).*",
".*npm\\s+install.*",
".*-c\\s+[\"'].*open.*[\"']w[\"'].*",
".*bash.*-c.*[12&]?>>?\\s*(?!/dev/null($|\\s))(?!&\\d+($|\\s))\\S+.*"]}}}}}}
},
"defaultAgent": "code",
"welcomeMessage" : "Welcome to ECA!\n\nType '/' for commands\n\n",
"autoCompactPercentage": 85,
"index" : {
"ignoreFiles" : [ {
"type" : "gitignore"
} ],
"repoMap": {
"maxTotalEntries": 800,
"maxEntriesPerDir": 50
}
},
"prompts": {
"chat": "${classpath:prompts/code_agent.md}", // default to code agent
"chatTitle": "${classpath:prompts/title.md}",
"compact": "${classpath:prompts/compact.md}",
"init": "${classpath:prompts/init.md}",
"completion": "${classpath:prompts/inline_completion.md}",
"rewrite": "${classpath:prompts/rewrite.md}"
},
"completion": {
"model": "openai/gpt-4.1"
}
}
Network / Enterprise
For more details about configuring eca with mTLS or custom CA certificates, check network page
Templates#
ECA supports templating in configurable prompts and rules, so instructions can adapt to the current chat context.
Custom agent prompts and rules are rendered with Selmer. Use condition variables when one prompt or rule should behave differently depending on the current chat.
Available variables:
| Variable | Type | Description |
|---|---|---|
isSubagent |
boolean | true when the chat is running as a subagent |
workspaceRoots |
string | The current workspace root paths |
toolEnabled_<tool-name> |
boolean | true when a tool is enabled, using its exact full name, e.g. toolEnabled_eca__shell_command |
{% if isSubagent %}
Be concise and return only the final result.
{% else %}
Explain important trade-offs and assumptions.
{% endif %}
{% if toolEnabled_eca__shell_command %}
You can run shell commands to verify your work.
{% endif %}
Current workspace roots: {{ workspaceRoots }}
If a rule renders to an empty string, ECA skips it and does not add an empty rule block to the system prompt.