|
38 | 38 |
|
39 | 39 | (defn ^:private prefixed-command-name |
40 | 40 | "Builds the user-invocation name for a plugin-sourced command. |
41 | | - Returns just the plugin name when it equals the command name (dedup), |
| 41 | + Returns just the plugin name when it equals the command name, |
42 | 42 | otherwise 'plugin:command'." |
43 | 43 | [plugin-name command-name] |
44 | 44 | (if (= plugin-name command-name) |
45 | 45 | plugin-name |
46 | 46 | (str plugin-name ":" command-name))) |
47 | 47 |
|
| 48 | +(defn ^:private markdown-file? [file] |
| 49 | + (and (not (fs/directory? file)) |
| 50 | + (string/ends-with? (string/lower-case (str file)) ".md"))) |
| 51 | + |
| 52 | +(defn ^:private configured-command-files [path] |
| 53 | + (cond |
| 54 | + (not (fs/exists? path)) [] |
| 55 | + (fs/directory? path) (filter markdown-file? (fs/glob path "**" {:follow-links true})) |
| 56 | + :else [path])) |
| 57 | + |
| 58 | +(defn ^:private command-file->command [type file opts] |
| 59 | + (let [base (normalize-command-name file)] |
| 60 | + (cond-> {:name (if-let [plugin (:plugin opts)] |
| 61 | + (prefixed-command-name plugin base) |
| 62 | + base) |
| 63 | + :path (str (fs/canonicalize file)) |
| 64 | + :type type |
| 65 | + :content (slurp (str file))} |
| 66 | + (:plugin opts) (assoc :plugin (:plugin opts))))) |
| 67 | + |
48 | 68 | (defn ^:private global-file-commands [] |
49 | 69 | (let [xdg-config-home (or (config/get-env "XDG_CONFIG_HOME") |
50 | 70 | (io/file (config/get-property "user.home") ".config")) |
51 | 71 | commands-dir (io/file xdg-config-home "eca" "commands")] |
52 | | - (when (fs/exists? commands-dir) |
53 | | - (keep (fn [file] |
54 | | - (when-not (fs/directory? file) |
55 | | - {:name (normalize-command-name file) |
56 | | - :path (str (fs/canonicalize file)) |
57 | | - :type :user-global-file |
58 | | - :content (slurp (fs/file file))})) |
59 | | - (fs/glob commands-dir "**" {:follow-links true}))))) |
| 72 | + (map #(command-file->command :user-global-file % {}) |
| 73 | + (configured-command-files commands-dir)))) |
60 | 74 |
|
61 | 75 | (defn ^:private local-file-commands [roots] |
62 | 76 | (->> roots |
63 | 77 | (mapcat (fn [{:keys [uri]}] |
64 | | - (let [commands-dir (fs/file (shared/uri->filename uri) ".eca" "commands")] |
65 | | - (when (fs/exists? commands-dir) |
66 | | - (fs/glob commands-dir "**" {:follow-links true}))))) |
67 | | - (keep (fn [file] |
68 | | - (when-not (fs/directory? file) |
69 | | - {:name (normalize-command-name file) |
70 | | - :path (str (fs/canonicalize file)) |
71 | | - :type :user-local-file |
72 | | - :content (slurp (fs/file file))}))))) |
| 78 | + (configured-command-files (fs/file (shared/uri->filename uri) ".eca" "commands")))) |
| 79 | + (map #(command-file->command :user-local-file % {})))) |
73 | 80 |
|
74 | 81 | (defn ^:private config-commands [config roots] |
75 | 82 | (->> (get config :commands) |
76 | | - (map |
| 83 | + (mapcat |
77 | 84 | (fn [{:keys [path plugin]}] |
78 | 85 | (let [path (str (fs/expand-home path)) |
79 | | - effective-name (fn [file] |
80 | | - (let [base (normalize-command-name file)] |
81 | | - (if plugin (prefixed-command-name plugin base) base)))] |
82 | | - (if (fs/absolute? path) |
83 | | - (when (fs/exists? path) |
84 | | - (cond-> {:name (effective-name path) |
85 | | - :path path |
86 | | - :type :user-config |
87 | | - :content (slurp path)} |
88 | | - plugin (assoc :plugin plugin))) |
89 | | - (keep (fn [{:keys [uri]}] |
90 | | - (let [f (fs/file (shared/uri->filename uri) path)] |
91 | | - (when (fs/exists? f) |
92 | | - (cond-> {:name (effective-name f) |
93 | | - :path (str (fs/canonicalize f)) |
94 | | - :type :user-config |
95 | | - :content (slurp f)} |
96 | | - plugin (assoc :plugin plugin))))) |
97 | | - roots))))) |
98 | | - (flatten) |
99 | | - (remove nil?))) |
| 86 | + opts (cond-> {} |
| 87 | + plugin (assoc :plugin plugin))] |
| 88 | + (->> (if (fs/absolute? path) |
| 89 | + (configured-command-files path) |
| 90 | + (mapcat (fn [{:keys [uri]}] |
| 91 | + (configured-command-files (fs/file (shared/uri->filename uri) path))) |
| 92 | + roots)) |
| 93 | + (map #(command-file->command :user-config % opts)))))))) |
100 | 94 |
|
101 | 95 | (defn ^:private custom-commands [config roots] |
102 | 96 | (concat (config-commands config roots) |
|
394 | 388 | {:type :new-chat-status |
395 | 389 | :status :login}) |
396 | 390 | "model" (let [selected-model (first args) |
397 | | - current-model (or (get-in db [:chats chat-id :model]) |
398 | | - full-model |
399 | | - (llm-api/default-model db config)) |
400 | | - available-models (sort (keys (:models db))) |
401 | | - chat-message (fn [text] |
402 | | - {:type :chat-messages |
403 | | - :chats {chat-id {:messages [{:role "system" |
404 | | - :content [{:type :text |
405 | | - :text text}]}]}}})] |
406 | | - (cond |
407 | | - (string/blank? selected-model) |
408 | | - (if (seq available-models) |
409 | | - (chat-message |
410 | | - (multi-str (str "Current model: `" current-model "`") |
411 | | - "" |
412 | | - "Available models:" |
413 | | - (string/join "\n" (map #(str "- `" % "`") available-models)) |
414 | | - "" |
415 | | - "Run `/model <provider/model>` to switch chat model.")) |
416 | | - (chat-message |
417 | | - (multi-str "No models available." |
418 | | - "" |
419 | | - "Sync models or login first, for example `/login anthropic`."))) |
| 391 | + current-model (or (get-in db [:chats chat-id :model]) |
| 392 | + full-model |
| 393 | + (llm-api/default-model db config)) |
| 394 | + available-models (sort (keys (:models db))) |
| 395 | + chat-message (fn [text] |
| 396 | + {:type :chat-messages |
| 397 | + :chats {chat-id {:messages [{:role "system" |
| 398 | + :content [{:type :text |
| 399 | + :text text}]}]}}})] |
| 400 | + (cond |
| 401 | + (string/blank? selected-model) |
| 402 | + (if (seq available-models) |
| 403 | + (chat-message |
| 404 | + (multi-str (str "Current model: `" current-model "`") |
| 405 | + "" |
| 406 | + "Available models:" |
| 407 | + (string/join "\n" (map #(str "- `" % "`") available-models)) |
| 408 | + "" |
| 409 | + "Run `/model <provider/model>` to switch chat model.")) |
| 410 | + (chat-message |
| 411 | + (multi-str "No models available." |
| 412 | + "" |
| 413 | + "Sync models or login first, for example `/login anthropic`."))) |
420 | 414 |
|
421 | | - (not (contains? (:models db) selected-model)) |
422 | | - (chat-message |
423 | | - (multi-str (str "Unknown model: `" selected-model "`") |
424 | | - "" |
425 | | - (when (seq available-models) |
426 | | - (str "Available models:\n" |
427 | | - (string/join "\n" (map #(str "- `" % "`") available-models)))))) |
| 415 | + (not (contains? (:models db) selected-model)) |
| 416 | + (chat-message |
| 417 | + (multi-str (str "Unknown model: `" selected-model "`") |
| 418 | + "" |
| 419 | + (when (seq available-models) |
| 420 | + (str "Available models:\n" |
| 421 | + (string/join "\n" (map #(str "- `" % "`") available-models)))))) |
428 | 422 |
|
429 | | - :else |
430 | | - (do |
431 | | - (swap! db* update-in [:chats chat-id] assoc :model selected-model :variant nil) |
432 | | - (config/notify-fields-changed-only! |
433 | | - {:chat {:select-model selected-model |
434 | | - :variants [] |
435 | | - :select-variant nil}} |
436 | | - messenger |
437 | | - db*) |
438 | | - (chat-message |
439 | | - (multi-str (str "Selected model: `" selected-model "`") |
440 | | - "Using model defaults."))))) |
| 423 | + :else |
| 424 | + (do |
| 425 | + (swap! db* update-in [:chats chat-id] assoc :model selected-model :variant nil) |
| 426 | + (config/notify-fields-changed-only! |
| 427 | + {:chat {:select-model selected-model |
| 428 | + :variants [] |
| 429 | + :select-variant nil}} |
| 430 | + messenger |
| 431 | + db*) |
| 432 | + (chat-message |
| 433 | + (multi-str (str "Selected model: `" selected-model "`") |
| 434 | + "Using model defaults."))))) |
441 | 435 | "fork" (let [chat (get-in db [:chats chat-id]) |
442 | | - new-id (str (random-uuid)) |
443 | | - now (System/currentTimeMillis) |
444 | | - new-title (fork-title (:title chat)) |
445 | | - new-chat {:id new-id |
446 | | - :title new-title |
447 | | - :status :idle |
448 | | - :created-at now |
449 | | - :updated-at now |
450 | | - :model (:model chat) |
451 | | - :last-api (:last-api chat) |
452 | | - :messages (vec (:messages chat)) |
453 | | - :prompt-finished? true}] |
454 | | - (swap! db* assoc-in [:chats new-id] new-chat) |
455 | | - (db/update-workspaces-cache! @db* metrics) |
456 | | - (messenger/chat-opened messenger {:chat-id new-id :title new-title}) |
457 | | - {:type :chat-messages |
458 | | - :chats {new-id {:messages (:messages chat) |
459 | | - :title new-title} |
460 | | - chat-id {:messages [{:role "system" |
461 | | - :content [{:type :text |
462 | | - :text (str "Chat forked to: " new-title)}]}]}}}) |
| 436 | + new-id (str (random-uuid)) |
| 437 | + now (System/currentTimeMillis) |
| 438 | + new-title (fork-title (:title chat)) |
| 439 | + new-chat {:id new-id |
| 440 | + :title new-title |
| 441 | + :status :idle |
| 442 | + :created-at now |
| 443 | + :updated-at now |
| 444 | + :model (:model chat) |
| 445 | + :last-api (:last-api chat) |
| 446 | + :messages (vec (:messages chat)) |
| 447 | + :prompt-finished? true}] |
| 448 | + (swap! db* assoc-in [:chats new-id] new-chat) |
| 449 | + (db/update-workspaces-cache! @db* metrics) |
| 450 | + (messenger/chat-opened messenger {:chat-id new-id :title new-title}) |
| 451 | + {:type :chat-messages |
| 452 | + :chats {new-id {:messages (:messages chat) |
| 453 | + :title new-title} |
| 454 | + chat-id {:messages [{:role "system" |
| 455 | + :content [{:type :text |
| 456 | + :text (str "Chat forked to: " new-title)}]}]}}}) |
463 | 457 | "resume" (let [chats (into {} |
464 | 458 | (filter #(and (not= chat-id (first %)) |
465 | 459 | (not (:subagent (second %))))) |
|
652 | 646 | (if-let [skill (first (filter #(= command (:name %)) skills))] |
653 | 647 | {:type :send-prompt |
654 | 648 | :prompt (if (seq args) |
655 | | - (substitute-args (:body skill) args) |
656 | | - (str "Load skill: " (:name skill)))} |
| 649 | + (substitute-args (:body skill) args) |
| 650 | + (str "Load skill: " (:name skill)))} |
657 | 651 | {:type :text |
658 | 652 | :text (str "Unknown command: " command)}))))) |
0 commit comments