Skip to content

Commit 7c6581a

Browse files
ericdalloeca-agent
andcommitted
Support parameterized skills with argument substitution
Skills invoked as slash commands now substitute $ARGS/$ARGUMENTS and positional $1/$2 placeholders directly into the skill body, e.g. `/review-pr https://github.com/org/repo/pull/123`. Also supports legacy $ARGn placeholders for backwards compatibility. Closes #384 🤖 Generated with [eca](https://eca.dev) Co-Authored-By: eca <git@eca.dev>
1 parent f40651b commit 7c6581a

File tree

5 files changed

+81
-12
lines changed

5 files changed

+81
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Improve summary of filesystem and shell functions making cleaner.
66
- Fix `move_file` not working for renaming.
7+
- Support parameterized skills via slash commands with `$ARGS`/`$ARGUMENTS`/`$1`/`$2` substitution, e.g. `/review-pr URL`. #384
78

89
## 0.121.1
910

docs/config/commands.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ description: "Configure ECA commands: built-in slash commands like /init and /co
77
![](../images/features/commands.png)
88

99
You can configure custom command prompts for project, global or via `commands` config pointing to the path of the commands.
10-
Prompts can use variables like `$ARGUMENTS`, `$ARG1`, `ARG2`, to replace in the prompt during command call.
10+
Prompts can use variables like `$ARGUMENTS`, `$1`, `$2`, to replace in the prompt during command call.
11+
12+
!!! tip "Skills support arguments too"
13+
14+
[Skills](./skills.md#parameterized-skills) also support the same variable substitution when invoked as slash commands, e.g. `/review-pr URL`.
1115

1216
You can configure in multiple different ways:
1317

@@ -16,7 +20,7 @@ You can configure in multiple different ways:
1620
A `.eca/commands` folder from the workspace root containing `.md` files with the custom prompt.
1721

1822
```markdown title=".eca/commands/check-performance.md"
19-
Check for performance issues in $ARG1 and optimize if needed.
23+
Check for performance issues in $1 and optimize if needed.
2024
```
2125

2226
ECA will make available a `/check-performance` command after creating that file.
@@ -26,7 +30,7 @@ You can configure in multiple different ways:
2630
A `$XDG_CONFIG_HOME/eca/commands` or `~/.config/eca/commands` folder containing `.md` files with the custom command prompt.
2731

2832
```markdown title="~/.config/eca/commands/check-performance.md"
29-
Check for performance issues in $ARG1 and optimize if needed.
33+
Check for performance issues in $1 and optimize if needed.
3034
```
3135

3236
ECA will make available a `/check-performance` command after creating that file.

docs/config/skills.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,28 @@ Check the examples:
7575
}
7676
```
7777

78+
## Parameterized skills
79+
80+
Skills can receive arguments when invoked as slash commands, using the same variable substitution as [custom commands](./commands.md): `$ARGS`, `$ARGUMENTS`, and positional `$1`, `$2`, etc.
81+
82+
When arguments are provided, ECA substitutes them directly into the skill body instead of asking the LLM to load the skill via `eca__skill`.
83+
84+
```markdown title="~/.config/eca/skills/review-pr/SKILL.md"
85+
---
86+
name: review-pr
87+
description: Review a pull request given its URL
88+
---
89+
90+
Review the following pull request: $ARGS
91+
Focus on code quality, correctness, and test coverage.
92+
```
93+
94+
Then invoke it with:
95+
96+
```
97+
/review-pr https://github.com/org/repo/pull/123
98+
```
99+
100+
ECA will substitute `$ARGS` with the URL and send the full skill body as the prompt.
101+
78102
You can have more directories and contents like `scripts/`, `references/`, `assets/` for a skill making it really powerful, check [the spec](https://agentskills.io/specification#optional-directories) for more details.

src/eca/features/commands.clj

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,22 @@
180180
skills-cmds
181181
custom-cmds)))
182182

183+
(defn ^:private substitute-args [content args]
184+
(let [args-joined (string/join " " args)
185+
content-with-args (-> content
186+
(string/replace "$ARGS" args-joined)
187+
(string/replace "$ARGUMENTS" args-joined))]
188+
(reduce (fn [c [i arg]]
189+
(-> c
190+
(string/replace (str "$ARG" (inc i)) arg)
191+
(string/replace (str "$" (inc i)) arg)))
192+
content-with-args
193+
(map-indexed vector args))))
194+
183195
(defn ^:private get-custom-command [command args custom-cmds]
184196
(when-let [raw-content (:content (first (filter #(= command (:name %))
185197
custom-cmds)))]
186-
(let [args-joined (string/join " " args)
187-
content-with-args (-> raw-content
188-
(string/replace "$ARGS" args-joined)
189-
(string/replace "$ARGUMENTS" args-joined))]
190-
(reduce (fn [content [i arg]]
191-
(string/replace content (str "$ARG" (inc i)) arg))
192-
content-with-args
193-
(map-indexed vector args)))))
198+
(substitute-args raw-content args)))
194199

195200
(defn ^:private format-tool-permissions [{:keys [toolCall]}]
196201
(when-let [approval (:approval toolCall)]
@@ -530,6 +535,8 @@
530535
:prompt custom-command-prompt}
531536
(if-let [skill (first (filter #(= command (:name %)) skills))]
532537
{:type :send-prompt
533-
:prompt (str "Load skill: " (:name skill))}
538+
:prompt (if (seq args)
539+
(substitute-args (:body skill) args)
540+
(str "Load skill: " (:name skill)))}
534541
{:type :text
535542
:text (str "Unknown command: " command)})))))

test/eca/features/commands_test.clj

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,36 @@
3131
(let [custom [{:name "test" :content "Process $ARGUMENTS here"}]]
3232
(is (= "Process one two here"
3333
(#'f.commands/get-custom-command "test" ["one" "two"] custom))))))
34+
35+
(deftest substitute-args-test
36+
(testing "replaces $ARGS with all args joined"
37+
(is (= "Review https://github.com/org/repo/pull/1"
38+
(#'f.commands/substitute-args "Review $ARGS" ["https://github.com/org/repo/pull/1"]))))
39+
40+
(testing "replaces $ARGUMENTS with all args joined"
41+
(is (= "Review https://github.com/org/repo/pull/1"
42+
(#'f.commands/substitute-args "Review $ARGUMENTS" ["https://github.com/org/repo/pull/1"]))))
43+
44+
(testing "replaces positional $ARGn placeholders"
45+
(is (= "First:a Second:b"
46+
(#'f.commands/substitute-args "First:$ARG1 Second:$ARG2" ["a" "b"]))))
47+
48+
(testing "unmatched positional placeholders remain"
49+
(is (= "A:x B:$ARG2"
50+
(#'f.commands/substitute-args "A:$ARG1 B:$ARG2" ["x"]))))
51+
52+
(testing "returns content as-is when no placeholders"
53+
(is (= "No placeholders here"
54+
(#'f.commands/substitute-args "No placeholders here" ["ignored"]))))
55+
56+
(testing "works with empty args"
57+
(is (= "Hello world"
58+
(#'f.commands/substitute-args "Hello $ARGS world" []))))
59+
60+
(testing "replaces Claude Code compatible $n positional placeholders"
61+
(is (= "First:a Second:b"
62+
(#'f.commands/substitute-args "First:$1 Second:$2" ["a" "b"]))))
63+
64+
(testing "replaces both $ARGn and $n placeholders"
65+
(is (= "A:x B:x"
66+
(#'f.commands/substitute-args "A:$ARG1 B:$1" ["x"])))))

0 commit comments

Comments
 (0)