Skip to content

Commit 8d7b8ca

Browse files
committed
Support for plugins
1 parent fcfe91d commit 8d7b8ca

13 files changed

Lines changed: 976 additions & 9 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ ECA Agent Guide (AGENTS.md)
3131
- Avoid adding too many comments, only add essential or when you think is really important to mention something.
3232
- ECA's protocol specification of client <-> server lives in docs/protocol.md
3333
- When adding support to a new feature or fixing a existing github issue, add a entry to Unreleased in CHANGELOG.md if not already there.
34+
- If changing ECA config structure, remember to update its docs/config.json

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add `plugins` config for loading external configuration from git repos or local paths.
6+
- Plugins can provide skills, MCP servers, agents, commands, hooks, rules, and arbitrary config overrides.
57
- Fix MCP server initialization crash (`String cannot be cast to IPersistentCollection`) when OAuth metadata endpoint returns a non-JSON or error response.
68

79
## 0.110.3

docs/config.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,57 @@
281281
}
282282
}
283283
},
284+
"plugins": {
285+
"type": "object",
286+
"description": "Plugin system for loading external configuration from git repos or local paths. Each key (except 'autoInstall') is a named plugin source with a 'source' URL or path. 'autoInstall' lists plugin names to install from any registered source.",
287+
"markdownDescription": "Plugin system for loading external configuration from git repos or local paths. Each key (except `autoInstall`) is a named plugin source with a `source` URL or path. `autoInstall` lists plugin names to install from any registered source.",
288+
"examples": [
289+
{
290+
"my-org": {
291+
"source": "https://github.com/org/ai-plugins.git"
292+
},
293+
"autoInstall": ["plugin-a", "plugin-b"]
294+
}
295+
],
296+
"properties": {
297+
"autoInstall": {
298+
"type": "array",
299+
"description": "List of plugin names to automatically install from registered sources.",
300+
"markdownDescription": "List of plugin names to automatically install from registered sources.",
301+
"items": {
302+
"type": "string"
303+
}
304+
}
305+
},
306+
"additionalProperties": {
307+
"oneOf": [
308+
{
309+
"type": "object",
310+
"description": "A named plugin source.",
311+
"properties": {
312+
"source": {
313+
"type": "string",
314+
"description": "Git URL or local path to a plugin repository containing .eca-plugin/marketplace.json.",
315+
"markdownDescription": "Git URL or local path to a plugin repository containing `.eca-plugin/marketplace.json`.",
316+
"examples": [
317+
"https://github.com/org/ai-plugins.git",
318+
"git@github.com:org/ai-plugins.git",
319+
"/home/user/local-plugins"
320+
]
321+
}
322+
},
323+
"required": ["source"],
324+
"additionalProperties": false
325+
},
326+
{
327+
"type": "array",
328+
"items": {
329+
"type": "string"
330+
}
331+
}
332+
]
333+
}
334+
},
284335
"network": {
285336
"type": "object",
286337
"description": "Network configuration for custom CA certificates and mTLS client certificates. Values support dynamic string interpolation (e.g. '${env:SSL_CERT_FILE}').",

docs/config/plugins.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
---
2+
description: "Configure ECA plugins: load external skills, agents, commands, rules, hooks, MCP servers and config overrides from git repos or local paths."
3+
---
4+
5+
# Plugins / Marketplace
6+
7+
Plugins let you share and reuse ECA configuration across projects and teams. A plugin source is a git repository or local directory containing a **marketplace** of plugins, each providing any combination of skills, agents, commands, rules, hooks, MCP servers, and config overrides.
8+
9+
## How it works
10+
11+
```mermaid
12+
flowchart TD
13+
A[ECA has a 'plugins' in config.json] --> B{Git URL or local path?}
14+
B -->|git| C[Clone to ~/.eca/cache/plugins]
15+
B -->|local| D[Use directory directly]
16+
C --> E[Read .eca-plugin/marketplace.json]
17+
D --> E
18+
E --> F[Read plugins from 'autoInstall']
19+
F --> G[Merge into final ECA config]
20+
```
21+
22+
1. You register one or more **sources** (git URL or local path) and list plugin names in **`autoInstall`**.
23+
2. ECA resolves each source — cloning git repos to a local cache or using the local path directly.
24+
3. Each source provides a **marketplace** (`.eca-plugin/marketplace.json`) listing its available plugins.
25+
4. ECA matches `autoInstall` names against the marketplace, then **discovers components** from each matched plugin directory.
26+
5. All components are **merged** into the config waterfall — user config always takes precedence on conflicts.
27+
28+
## Pointing to a plugin source / marketplace
29+
30+
Add a `plugins` key to your config with one or more named sources and an `autoInstall` array:
31+
32+
=== "Git source"
33+
34+
```javascript title="~/.config/eca/config.json"
35+
{
36+
"plugins": {
37+
"my-org": {
38+
"source": "https://github.com/my-org/eca-plugins.git"
39+
},
40+
"autoInstall": ["code-review", "security-scanner"]
41+
}
42+
}
43+
```
44+
45+
=== "Local path (for development)"
46+
47+
```javascript title=".eca/config.json"
48+
{
49+
"plugins": {
50+
"local-dev": {
51+
"source": "/home/user/my-eca-plugins"
52+
},
53+
"autoInstall": ["my-plugin"]
54+
}
55+
}
56+
```
57+
58+
=== "Multiple sources"
59+
60+
ECA searches all registered sources when resolving `autoInstall` entries:
61+
62+
```javascript title="~/.config/eca/config.json"
63+
{
64+
"plugins": {
65+
"company": {
66+
"source": "https://github.com/company/eca-plugins.git"
67+
},
68+
"community": {
69+
"source": "https://github.com/community/shared-plugins.git"
70+
},
71+
"autoInstall": ["company-standards", "linter-setup", "shared-skills"]
72+
}
73+
}
74+
```
75+
76+
## Creating a plugin source (Plugins marketplace)
77+
78+
A plugin source is a directory (typically a git repo) with a `.eca-plugin/marketplace.json` file that lists available plugins.
79+
80+
### Marketplace file
81+
82+
```json title=".eca-plugin/marketplace.json"
83+
{
84+
"plugins": [
85+
{
86+
"name": "code-review",
87+
"description": "Agents and skills for thorough code review",
88+
"source": "plugins/code-review"
89+
},
90+
{
91+
"name": "security-scanner",
92+
"description": "Security-focused rules and hooks",
93+
"source": "plugins/security-scanner"
94+
}
95+
]
96+
}
97+
```
98+
99+
Each plugin entry has:
100+
101+
| Field | Description |
102+
|-------|-------------|
103+
| `name` | Unique plugin name (used in `autoInstall`) |
104+
| `description` | Human-readable description |
105+
| `source` | Relative path from the repo root to the plugin directory |
106+
107+
### Plugin directory structure
108+
109+
Each plugin directory can contain any combination of:
110+
111+
```
112+
plugins/code-review/
113+
├── skills/
114+
│ └── review-checklist/
115+
│ └── SKILL.md
116+
├── agents/
117+
│ └── reviewer.md
118+
├── commands/
119+
│ └── review.md
120+
├── rules/
121+
│ └── code-standards.md
122+
├── hooks/
123+
│ └── hooks.json
124+
├── .mcp.json
125+
└── eca.json
126+
```
127+
128+
| Path | What it provides | Details |
129+
|------|-----------------|---------|
130+
| `skills/` | Skill definitions | Each subfolder follows the [agentskills.io](https://agentskills.io/) spec with a `SKILL.md` |
131+
| `agents/*.md` | Agent definitions | Markdown files with YAML frontmatter, same format as local agents |
132+
| `commands/*.md` | Custom commands | Markdown command files, same format as local commands |
133+
| `rules/**` | Rule files | Any files under `rules/` are loaded as rules |
134+
| `hooks/hooks.json` | Hooks | ECA hook format or Claude Code hook format (auto-translated) |
135+
| `.mcp.json` | MCP server definitions | Standard `{"mcpServers": {...}}` format |
136+
| `eca.json` | Config overrides | Arbitrary ECA config keys deep-merged into config |
137+
138+
All paths are optional — include only what your plugin needs.
139+
140+
=== "Skill-only plugin"
141+
142+
```
143+
plugins/gif-maker/
144+
└── skills/
145+
└── gif-generator/
146+
├── SKILL.md
147+
└── scripts/
148+
└── generate.py
149+
```
150+
151+
=== "Hooks + MCP plugin"
152+
153+
```
154+
plugins/security-scanner/
155+
├── hooks/
156+
│ └── hooks.json
157+
└── .mcp.json
158+
```
159+
160+
=== "Config overrides only"
161+
162+
```
163+
plugins/team-defaults/
164+
└── eca.json
165+
```
166+
167+
=== "Full plugin"
168+
169+
```
170+
plugins/company-standards/
171+
├── skills/
172+
│ └── internal-api/
173+
│ └── SKILL.md
174+
├── agents/
175+
│ └── reviewer.md
176+
├── commands/
177+
│ └── deploy.md
178+
├── rules/
179+
│ └── coding-standards.md
180+
├── hooks/
181+
│ └── hooks.json
182+
├── .mcp.json
183+
└── eca.json
184+
```
185+
186+
### Hook format
187+
188+
Plugins support both ECA native hook format and Claude Code hook format. Claude Code hooks are automatically translated:
189+
190+
=== "ECA format"
191+
192+
```json title="hooks/hooks.json"
193+
{
194+
"my-hook": {
195+
"type": "preToolCall",
196+
"matcher": "eca__shell_command",
197+
"actions": [
198+
{
199+
"type": "shell",
200+
"shell": "echo 'checking tool call...'"
201+
}
202+
]
203+
}
204+
}
205+
```
206+
207+
=== "Claude Code format (auto-translated)"
208+
209+
```json title="hooks/hooks.json"
210+
{
211+
"hooks": {
212+
"PreToolUse": [
213+
{
214+
"matcher": "eca__shell_command",
215+
"hooks": [
216+
{
217+
"type": "command",
218+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/check.sh"
219+
}
220+
]
221+
}
222+
]
223+
}
224+
}
225+
```
226+
227+
The `${CLAUDE_PLUGIN_ROOT}` variable is replaced with the plugin's absolute path at load time.
228+
229+
| Claude Code Event | ECA Event |
230+
|-------------------|-----------|
231+
| `PostToolUse` | `postToolCall` |
232+
| `PreToolUse` | `preToolCall` |
233+
| `UserPromptSubmit` | `preRequest` |

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ nav:
2424
- Completion: config/completion.md
2525
- Rewrite: config/rewrite.md
2626
- Context Management: config/context-management.md
27+
- Plugins / Marketplace: config/plugins.md
2728
- Metrics: config/metrics.md
2829
- Network / Enterprise: config/network.md
2930
- User Examples: config/examples.md

src/eca/cache.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,18 @@
4141

4242
(def ^:private logger-tag "[CACHE]")
4343
(def ^:private tool-call-outputs-dir-name "toolCallOutputs")
44+
(def ^:private plugins-dir-name "plugins")
4445

4546
(defn tool-call-outputs-dir
4647
"Returns the File object for the tool call outputs cache directory."
4748
^File []
4849
(io/file (global-dir) tool-call-outputs-dir-name))
4950

51+
(defn plugins-dir
52+
"Returns the base directory for caching cloned plugin sources."
53+
^java.io.File []
54+
(io/file (global-dir) plugins-dir-name))
55+
5056
(defn save-tool-call-output!
5157
"Saves the full tool call output text to a cache file.
5258
Returns the absolute path of the saved file as a string."

0 commit comments

Comments
 (0)