diff --git a/CHANGELOG.md b/CHANGELOG.md index 559e2203..4861d11d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add `:config-file` cli option to pass in config. + ## 0.72.2 - Run `preToolCall` hook before user approval if any. #170 diff --git a/src/eca/config.clj b/src/eca/config.clj index 5a96bee2..7a5dd5b1 100644 --- a/src/eca/config.clj +++ b/src/eca/config.clj @@ -4,7 +4,9 @@ 1. base: fixed config var `eca.config/initial-config`. 2. env var: searching for a `ECA_CONFIG` env var which should contains a valid json config. 3. local config-file: searching from a local `.eca/config.json` file. - 4. `initializatonOptions` sent in `initialize` request." + 4. `initializatonOptions` sent in `initialize` request. + + When `:config-file` from cli option is passed, it uses that instead of searching default locations." (:require [camel-snake-kebab.core :as csk] [cheshire.core :as json] @@ -23,11 +25,14 @@ (def ^:private logger-tag "[CONFIG]") (def ^:dynamic *env-var-config-error* false) +(def ^:dynamic *custom-config-error* false) (def ^:dynamic *global-config-error* false) (def ^:dynamic *local-config-error* false) (def ^:private listen-idle-ms 3000) +(def custom-config-file-path* (atom nil)) + (def initial-config {:providers {"openai" {:api "openai-responses" :url "https://api.openai.com" @@ -148,6 +153,14 @@ (def ^:private config-from-envvar (memoize config-from-envvar*)) +(defn ^:private config-from-custom* [] + (when-some [path @custom-config-file-path*] + (let [config-file (io/file path)] + (when (.exists config-file) + (safe-read-json-string (slurp config-file) (var *custom-config-error*)))))) + +(def ^:private config-from-custom (memoize config-from-custom*)) + (defn global-config-dir ^File [] (let [xdg-config-home (or (get-env "XDG_CONFIG_HOME") (io/file (get-property "user.home") ".config"))] @@ -262,6 +275,13 @@ [:behavior :ANY :toolCall :approval :deny :ANY :argsMatchers] [:otlp]]}) +(defn ^:private config-from-custom-or-default-location [pure-config? db] + (if-some [config-from-custom (config-from-custom)] + (when-not pure-config? config-from-custom) + (deep-merge + (when-not pure-config? (config-from-global-file)) + (when-not pure-config? (config-from-local-file (:workspace-folders db)))))) + (defn all [db] (let [initialization-config @initialization-config* pure-config? (:pureConfig initialization-config)] @@ -270,8 +290,7 @@ eca-config-normalization-rules (deep-merge initialization-config (when-not pure-config? (config-from-envvar)) - (when-not pure-config? (config-from-global-file)) - (when-not pure-config? (config-from-local-file (:workspace-folders db)))))))) + (config-from-custom-or-default-location pure-config? db)))))) (defn validation-error [] (cond diff --git a/src/eca/main.clj b/src/eca/main.clj index c2c13201..a429b9b3 100644 --- a/src/eca/main.clj +++ b/src/eca/main.clj @@ -42,7 +42,7 @@ (def log-levels #{"error" "warn" "info" "debug"}) (def cli-spec - {:order [:help :version :verbose] + {:order [:help :version :verbose :config-file] :spec {:help {:alias :h :desc "Print the available commands and its options"} :version {:desc "Print eca version"} @@ -52,6 +52,10 @@ :validate {:pred log-levels :ex-msg (fn [{:keys [_option _value]}] (format "Must be in %s" log-levels))}} + :config-file {:ref "" + :desc "Path to a JSON config to use instead of searching default locations" + :coerce :string + :default nil} :verbose {:desc "Use stdout for eca logs instead of default log settings"}}}) (defn ^:private parse-opts @@ -92,6 +96,8 @@ [action options] (proxy/load!) (when (= "server" action) + (when-some [cfg-file (:config-file options)] + (reset! config/custom-config-file-path* cfg-file)) (alter-var-root #'logger/*level* (constantly (keyword (:log-level options)))) (let [finished @(server/run-io-server! (:verbose options))] {:result-code (if (= :done finished) 0 1)}))) diff --git a/test/eca/main_test.clj b/test/eca/main_test.clj index b0e1a627..ac3c1611 100644 --- a/test/eca/main_test.clj +++ b/test/eca/main_test.clj @@ -22,6 +22,10 @@ [] {:options {:verbose m/absent}} ["--verbose"] {:options {:verbose true}} ["-v"] {:options {:verbose m/absent}} + ;; config-file + [] {:options {:config-file m/absent}} + ["--config-file"] {:options {:config-file m/absent}} + ["--config-file" "/dev/config.json"] {:options {:config-file "/dev/config.json"}} #_())) (deftest parse