Skip to content

Commit f9c29d2

Browse files
committed
Merge branch 'master' into call-test
2 parents 99f7140 + 75f5c1d commit f9c29d2

24 files changed

+1550
-78
lines changed

.github/FUNDING.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# These are supported funding model platforms
22

3-
github: ericdallo
3+
github: editor-code-assistant
4+
open_collective: editor-code-assistant

AGENTS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,18 @@ Code Style
2424
- Unit tests should have a single `deftest` for function to be tested with multiple `testing`s for each tested case.
2525
- Unit tests that use file paths and uris should rely on `h/file-path` and `h/file-uri` to avoid windows issues with slashes.
2626

27+
Secrets Management
28+
- `src/eca/secrets/netrc.clj` - Netrc format parser (multi-line format: machine/login/password/port keywords)
29+
- `src/eca/secrets/authinfo.clj` - Authinfo format parser (single-line format: space-separated key-value pairs)
30+
- `src/eca/secrets.clj` - Main secrets manager for credential file operations:
31+
- File discovery and priority order (.authinfo.gpg → .netrc.gpg → .authinfo → _authinfo → .netrc → _netrc)
32+
- Cross-platform path construction using io/file (handles / vs \ separators automatically)
33+
- GPG decryption with caching (5-second TTL) and timeout (30s, configurable via GPG_TIMEOUT env var)
34+
- keyRc format parsing: [login@]machine[:port] (named after Unix "rc" config file tradition)
35+
- Credential matching logic (exact login match when specified, first match otherwise)
36+
- Permission validation (Unix: warns if not 0600; Windows: skipped)
37+
- Authentication flow: config `key` → credential files `keyRc` → env var `keyEnv` → OAuth
38+
- Security: passwords never logged; GPG decryption via clojure.java.process; cache with short TTL; subprocess timeout protection
39+
2740
Notes
2841
- CI runs: bb test and bb integration-test. Ensure these pass locally before PRs.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
## Unreleased
44

5+
## 0.66.1
6+
7+
- Improve plan behavior prompt. #139
8+
9+
## 0.66.0
10+
11+
- Add support for secrets stored in authinfo and netrc files
512
- Added tests for stopping concurrent tool calls. #147
13+
- Improve logging.
14+
- Improve performance of `chat/queryContext`.
615

716
## 0.65.0
817

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,7 @@ For developer details, check [development docs](https://eca.dev/development).
9393
## Support the project 💖
9494

9595
Consider [sponsoring the project](https://github.com/sponsors/ericdallo) to help grow faster, the support helps to keep the project going, being updated and maintained!
96+
97+
These are all the incredible people who helped make ECA better!
98+
99+
[<img src="https://opencollective.com/editor-code-assistant/contributors.svg?width=890&button=false" alt="Code Contributors" style="max-width:100%;">](https://github.com/editor-code-assistant/eca/graphs/contributors)

deps-lock.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"git-dir": "https/github.com/borkdude/gh-release-artifact",
99
"hash": "sha256-HllBWtwTtYYM0KjHB9UVDBjB57CtpvF+gncQseGXgf8="
1010
},
11+
{
12+
"lib": "br.dev.zz/parc",
13+
"url": "https://github.com/souenzzo/parc",
14+
"rev": "76f0714e6b93a053d67d473c33dfc279dfb94d23",
15+
"git-dir": "https/github.com/souenzzo/parc",
16+
"hash": "sha256-6ISYa9g5kOGkay3ums4Tys2/krPTIFqQKAtT8rfaZ4k="
17+
},
1118
{
1219
"lib": "io.github.clojure/tools.build",
1320
"url": "https://github.com/clojure/tools.build.git",
@@ -2215,6 +2222,16 @@
22152222
"mvn-repo": "https://repo1.maven.org/maven2/",
22162223
"hash": "sha256-JUvpyKuMzDArR9fFaj/KEUl+WcMFvxX6YFTD3/TrkZ0="
22172224
},
2225+
{
2226+
"mvn-path": "org/clojure/clojure/1.12.2/clojure-1.12.2.jar",
2227+
"mvn-repo": "https://repo1.maven.org/maven2/",
2228+
"hash": "sha256-pYv4B+zv7K6iIri4tH4UNo7o4yy0VAs//v/4yglTSA0="
2229+
},
2230+
{
2231+
"mvn-path": "org/clojure/clojure/1.12.2/clojure-1.12.2.pom",
2232+
"mvn-repo": "https://repo1.maven.org/maven2/",
2233+
"hash": "sha256-55suCRfnPnPCX7N5PzFV+PD4jYAvUMJf1Sl3l3rDQiA="
2234+
},
22182235
{
22192236
"mvn-path": "org/clojure/core.async/1.5.648/core.async-1.5.648.jar",
22202237
"mvn-repo": "https://repo1.maven.org/maven2/",

deps.edn

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{:paths ["src" "resources"]
2-
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
2+
:deps {org.clojure/clojure {:mvn/version "1.12.2"}
33
org.clojure/core.async {:mvn/version "1.8.741"}
44
org.babashka/cli {:mvn/version "0.8.65"}
55
com.github.clojure-lsp/jsonrpc4clj {:mvn/version "1.0.2"}
66
io.modelcontextprotocol.sdk/mcp {:mvn/version "0.14.1"}
77
borkdude/dynaload {:mvn/version "0.3.5"}
88
babashka/fs {:mvn/version "0.5.26"}
99
babashka/process {:mvn/version "0.6.23"}
10+
br.dev.zz/parc {:git/url "https://github.com/souenzzo/parc"
11+
:git/sha "76f0714e6b93a053d67d473c33dfc279dfb94d23"}
1012
com.cognitect/transit-clj {:mvn/version "1.0.333"}
1113
hato/hato {:mvn/version "1.0.0"}
1214
ring/ring-codec {:mvn/version "1.3.0"}

docs/configuration.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ For MCP servers configuration, use the `mcpServers` config, examples:
6868
"command": "npx",
6969
"args": ["-y", "@modelcontextprotocol/server-memory"],
7070
// optional
71-
"env": {"FOO": "bar"}
71+
"env": {"FOO": "bar"}
7272
}
7373
}
7474
}
7575
```
76-
76+
7777
=== "HTTP-streamable"
7878

7979
`~/.config/eca/config.json`
@@ -138,7 +138,7 @@ Check some examples:
138138
}
139139
}
140140
```
141-
141+
142142
=== "Matching by a tool argument"
143143

144144
__`argsMatchers`__ is a map of argument name by list of [java regex](https://www.regexplanet.com/advanced/java/index.html).
@@ -156,7 +156,7 @@ Check some examples:
156156
}
157157
}
158158
```
159-
159+
160160
=== "Denying a tool"
161161

162162
```javascript
@@ -336,7 +336,7 @@ There are 3 possible ways to configure rules following this order of priority:
336336
"rules": [{"path": "my-rule.md"}]
337337
}
338338
```
339-
339+
340340
## Behaviors / prompts
341341

342342
ECA allows to totally customize the prompt sent to LLM via the `behavior` config, allowing to have multiple behaviors for different tasks or workflows.
@@ -379,6 +379,7 @@ To configure, add your OTLP collector config via `:otlp` map following [otlp aut
379379
urlEnv?: string;
380380
key?: string; // when provider supports api key.
381381
keyEnv?: string;
382+
keyRc?: string; // credential file lookup in format [login@]machine[:port]
382383
completionUrlRelativePath?: string;
383384
models: {[key: string]: {
384385
extraPayload?: {[key: string]: any}

docs/models.md

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ Example:
3232
"providers": {
3333
"openai": {
3434
"key": "your-openai-key-here", // configuring a key
35-
"models": {
35+
"models": {
3636
"o1": {} // adding models to a built-in provider
3737
"o3": {
3838
"extraPayload": { // adding to the payload sent to LLM
3939
"temperature": 0.5
4040
}
4141
}
4242
}
43-
}
43+
}
4444
}
4545
}
4646
```
@@ -68,6 +68,7 @@ Schema:
6868
| `urlEnv` | string | Environment variable name containing the API URL | No* |
6969
| `url` | string | Direct API URL (use instead of `urlEnv`) | No* |
7070
| `keyEnv` | string | Environment variable name containing the API key | No* |
71+
| `keyRc` | string | Lookup specification to read the API key from Unix RC [credential files](#credential-file-authentication) | No* |
7172
| `key` | string | Direct API key (use instead of `keyEnv`) | No* |
7273
| `completionUrlRelativePath` | string | Optional override for the completion endpoint path (see defaults below and examples like Azure) | No |
7374
| `models` | map | Key: model name, value: its config | Yes |
@@ -121,27 +122,54 @@ Defaults by API type:
121122

122123
Only set this when your provider uses a different path or expects query parameters at the endpoint (e.g., Azure API versioning).
123124

125+
### Credential File Authentication
126+
127+
Use `keyRc` in your provider config to read credentials from `~/.authinfo(.gpg)` or `~/.netrc(.gpg)` without storing keys directly in config or env vars.
128+
129+
Example:
130+
131+
```javascript
132+
{
133+
"providers": {
134+
"openai": {"keyRc": "api.openai.com"},
135+
"anthropic": {"keyRc": "[email protected]"}
136+
}
137+
}
138+
```
139+
140+
keyRc lookup specification format: `[login@]machine[:port]` (e.g., `api.openai.com`, `[email protected]`, `api.custom.com:8443`).
141+
142+
Further reading on credential file formats:
143+
- [Emacs authinfo documentation](https://www.gnu.org/software/emacs/manual/html_node/auth/Help-for-users.html)
144+
- [Curl Netrc documentation](https://everything.curl.dev/usingcurl/netrc)
145+
- [GNU Inetutils .netrc documentation](https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html)
146+
147+
Notes:
148+
- Preferred files are GPG-encrypted (`~/.authinfo.gpg` / `~/.netrc.gpg`); plaintext variants are supported.
149+
- Authentication priority (short): `key` > `keyRc files` > `keyEnv` > OAuth.
150+
- All providers with API key auth can use credential files.
151+
124152
## Providers examples
125153

126154
=== "Anthropic"
127-
155+
128156
1. Login to Anthropic via the chat command `/login`.
129157
2. Type 'anthropic' and send it.
130158
3. Type the chosen method
131159
4. Authenticate in your browser, copy the code.
132160
5. Paste and send the code and done!
133161

134162
=== "Github Copilot"
135-
163+
136164
1. Login to Github copilot via the chat command `/login`.
137165
2. Type 'github-copilot' and send it.
138166
3. Authenticate in Github in your browser with the given code.
139167
4. Type anything in the chat to continue and done!
140-
168+
141169
_Tip: check [Your Copilot plan](https://github.com/settings/copilot/features) to enable models to your account._
142-
170+
143171
=== "Google / Gemini"
144-
172+
145173
1. Login to Google via the chat command `/login`.
146174
2. Type 'google' and send it.
147175
3. Choose 'manual' and type your Google/Gemini API key. (You need to create a key in [google studio](https://aistudio.google.com/api-keys))
@@ -165,7 +193,7 @@ Only set this when your provider uses a different path or expects query paramete
165193
```
166194

167195
=== "OpenRouter"
168-
196+
169197
[OpenRouter](https://openrouter.ai) provides access to many models through a unified API:
170198

171199
1. Login via the chat command `/login`.
@@ -175,7 +203,7 @@ Only set this when your provider uses a different path or expects query paramete
175203
5. Done, it should be saved to your global config.
176204

177205
or manually via config:
178-
206+
179207
```javascript
180208
{
181209
"providers": {
@@ -196,15 +224,15 @@ Only set this when your provider uses a different path or expects query paramete
196224
=== "DeepSeek"
197225

198226
[DeepSeek](https://deepseek.com) offers powerful reasoning and coding models:
199-
227+
200228
1. Login via the chat command `/login`.
201229
2. Type 'deepseek' and send it.
202230
3. Specify your Deepseek API key.
203231
4. Inform at least a model, ex: `deepseek-chat`
204232
5. Done, it should be saved to your global config.
205-
233+
206234
or manually via config:
207-
235+
208236
```javascript
209237
{
210238
"providers": {
@@ -215,7 +243,7 @@ Only set this when your provider uses a different path or expects query paramete
215243
"models": {
216244
"deepseek-chat": {},
217245
"deepseek-coder": {},
218-
"deepseek-reasoner": {}
246+
"deepseek-reasoner": {}
219247
}
220248
}
221249
}
@@ -230,7 +258,7 @@ Only set this when your provider uses a different path or expects query paramete
230258
4. Specify your API url with your resource, ex: 'https://your-resource-name.openai.azure.com'.
231259
5. Inform at least a model, ex: `gpt-5`
232260
6. Done, it should be saved to your global config.
233-
261+
234262
or manually via config:
235263

236264
```javascript
@@ -256,7 +284,7 @@ Only set this when your provider uses a different path or expects query paramete
256284
3. Specify your API key.
257285
4. Inform at least a model, ex: `GLM-4.5`
258286
5. Done, it should be saved to your global config.
259-
287+
260288
or manually via config:
261289

262290
```javascript
@@ -278,7 +306,7 @@ Only set this when your provider uses a different path or expects query paramete
278306
=== "Same model with different settings"
279307

280308
For now, you can create different providers with same model names to achieve that:
281-
309+
282310
```javascript
283311
{
284312
"providers": {

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
pkgs = nixpkgs.legacyPackages.${system};
1717
cljpkgs = clj-nix.packages."${system}";
1818

19-
jdk = pkgs.jdk24_headless;
19+
jdk = pkgs.jdk21_headless;
2020
graalvm = pkgs.graalvmPackages.graalvm-ce;
21-
clojure = pkgs.clojure.override { jdk = pkgs.jdk24_headless; };
21+
clojure = pkgs.clojure.override { jdk = pkgs.jdk21_headless; };
2222

2323
in
2424
{

0 commit comments

Comments
 (0)