Skip to content

Commit cd66ce7

Browse files
committed
Add /skill-create command to create a ECA skill from a prompt.
Fixes #277
1 parent 6e1ff0a commit cd66ce7

8 files changed

Lines changed: 74 additions & 22 deletions

File tree

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 `/skill-create` command to create a ECA skill from a prompt. #277
6+
57
## 0.95.0
68

79
- (OpenAI Chat) - Configurable reasoning history via `reasoningHistory` (model-level, default: all)

resources/META-INF/native-image/eca/eca/native-image.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Args=-J-Dborkdude.dynaload.aot=true \
1919
-H:IncludeResources=prompts/agent_behavior.md \
2020
-H:IncludeResources=prompts/additional_system_info.md \
2121
-H:IncludeResources=prompts/init.md \
22+
-H:IncludeResources=prompts/skill_create.md \
2223
-H:IncludeResources=prompts/title.md \
2324
-H:IncludeResources=prompts/compact.md \
2425
-H:IncludeResources=prompts/inline_completion.md \

resources/prompts/skill_create.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Task
2+
3+
Create the ECA skill {{skillName}}.
4+
The skill file should be a markdown file {{skillFilePath}} starting with meta containing name and description.
5+
6+
## Example
7+
8+
```markdown
9+
---
10+
name: the-skill-name
11+
description: Consise text of what the skill does, useful to LLM knows when to load it.
12+
---
13+
14+
Skill content goes here.
15+
```
16+
17+
## Description and content
18+
19+
For the skill description and content, consider user request: {{userPrompt}}

src/eca/config.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
:chatTitle "${classpath:prompts/title.md}"
111111
:compact "${classpath:prompts/compact.md}"
112112
:init "${classpath:prompts/init.md}"
113+
:skillCreate "${classpath:prompts/skill_create.md}"
113114
:completion "${classpath:prompts/inline_completion.md}"
114115
:rewrite "${classpath:prompts/rewrite.md}"}
115116
:hooks {}

src/eca/features/chat.clj

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,24 +1307,30 @@
13071307
content))))
13081308

13091309
(defn ^:private handle-command! [{:keys [command args]} chat-ctx]
1310-
(let [{:keys [type on-finished-side-effect] :as result} (f.commands/handle-command! command args chat-ctx)]
1311-
(case type
1312-
:chat-messages (do
1313-
(doseq [[chat-id {:keys [messages title]}] (:chats result)]
1314-
(let [new-chat-ctx (assoc chat-ctx :chat-id chat-id)]
1315-
(send-chat-contents! messages new-chat-ctx)
1316-
(when title
1317-
(send-content! new-chat-ctx :system (assoc-some
1318-
{:type :metadata}
1319-
:title title)))))
1320-
(finish-chat-prompt! :idle chat-ctx))
1321-
:new-chat-status (finish-chat-prompt! (:status result) chat-ctx)
1322-
:send-prompt (let [prompt-contents (:prompt result)]
1323-
;; Keep original slash command in :message for hooks (already in parent chat-ctx)
1324-
(prompt-messages! [{:role "user" :content prompt-contents}]
1325-
:eca-command
1326-
(assoc chat-ctx :on-finished-side-effect on-finished-side-effect)))
1327-
nil)))
1310+
(try
1311+
(let [{:keys [type on-finished-side-effect] :as result} (f.commands/handle-command! command args chat-ctx)]
1312+
(case type
1313+
:chat-messages (do
1314+
(doseq [[chat-id {:keys [messages title]}] (:chats result)]
1315+
(let [new-chat-ctx (assoc chat-ctx :chat-id chat-id)]
1316+
(send-chat-contents! messages new-chat-ctx)
1317+
(when title
1318+
(send-content! new-chat-ctx :system (assoc-some
1319+
{:type :metadata}
1320+
:title title)))))
1321+
(finish-chat-prompt! :idle chat-ctx))
1322+
:new-chat-status (finish-chat-prompt! (:status result) chat-ctx)
1323+
:send-prompt (let [prompt-contents (:prompt result)]
1324+
;; Keep original slash command in :message for hooks (already in parent chat-ctx)
1325+
(prompt-messages! [{:role "user" :content prompt-contents}]
1326+
:eca-command
1327+
(assoc chat-ctx :on-finished-side-effect on-finished-side-effect)))
1328+
nil))
1329+
(catch Exception e
1330+
(logger/error e)
1331+
(send-content! chat-ctx :system {:type :text
1332+
:text (str "Error: " (ex-message e) "\n\nCheck ECA stderr for more details.")})
1333+
(finish-chat-prompt! :idle (dissoc chat-ctx :on-finished-side-effect)))))
13281334

13291335
(defn ^:private prompt*
13301336
[{:keys [model]}

src/eca/features/commands.clj

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
[eca.features.skills :as f.skills]
1313
[eca.features.tools.mcp :as f.mcp]
1414
[eca.llm-api :as llm-api]
15+
[eca.logger :as logger]
1516
[eca.messenger :as messenger]
1617
[eca.secrets :as secrets]
1718
[eca.shared :as shared :refer [multi-str update-some]])
@@ -93,8 +94,13 @@
9394
:arguments [{:name "provider-id"}]}
9495
{:name "skills"
9596
:type :native
96-
:description "List available skills."
97+
:description "List available skills"
9798
:arguments []}
99+
{:name "skill-create"
100+
:type :native
101+
:description "Create a skill considering a user request"
102+
:arguments [{:name "name" :description "The skill name"}
103+
{:name "prompt" :description "What to consider as this skill content"}]}
98104
{:name "costs"
99105
:type :native
100106
:description "Total costs of the current chat session."
@@ -303,6 +309,10 @@
303309
skills)]
304310
{:type :chat-messages
305311
:chats {chat-id {:messages [{:role "system" :content [{:type :text :text msg}]}]}}})
312+
"skill-create" (let [skill-name (first args)
313+
user-prompt (second args)]
314+
{:type :send-prompt
315+
:prompt (f.prompt/skill-create-prompt skill-name user-prompt all-tools behavior db config)})
306316
"config" {:type :chat-messages
307317
:chats {chat-id {:messages [{:role "system" :content [{:type :text :text (with-out-str (pprint/pprint config))}]}]}}}
308318
"doctor" {:type :chat-messages

src/eca/features/prompt.clj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[babashka.fs :as fs]
44
[clojure.java.io :as io]
55
[clojure.string :as string]
6+
[eca.features.skills :as f.skills]
67
[eca.features.tools.mcp :as f.mcp]
78
[eca.logger :as logger]
89
[eca.shared :refer [multi-str] :as shared]
@@ -172,6 +173,15 @@
172173
(get-config-prompt :init behavior config)
173174
(->base-selmer-ctx all-tools db)))
174175

176+
(defn skill-create-prompt [skill-name user-prompt all-tools behavior db config]
177+
(selmer/render
178+
(get-config-prompt :skillCreate behavior config)
179+
(merge
180+
(->base-selmer-ctx all-tools db)
181+
{:skillFilePath (str (fs/file (f.skills/global-skills-dir) skill-name "SKILL.md"))
182+
:skillName skill-name
183+
:userPrompt user-prompt})))
184+
175185
(defn chat-title-prompt [behavior config]
176186
(get-config-prompt :chatTitle behavior config))
177187

src/eca/features/skills.clj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@
5252
:body body
5353
:dir (str (fs/canonicalize (fs/parent skill-file)))})))
5454

55-
(defn ^:private global-skills []
55+
(defn global-skills-dir []
5656
(let [xdg-config-home (or (config/get-env "XDG_CONFIG_HOME")
57-
(io/file (config/get-property "user.home") ".config"))
58-
skills-dir (io/file xdg-config-home "eca" "skills")]
57+
(io/file (config/get-property "user.home") ".config"))]
58+
(io/file xdg-config-home "eca" "skills")))
59+
60+
(defn ^:private global-skills []
61+
(let [skills-dir (global-skills-dir)]
5962
(when (fs/exists? skills-dir)
6063
(keep skill-file->skill
6164
(fs/glob skills-dir "**/SKILL.md" {:follow-links true})))))

0 commit comments

Comments
 (0)