Skip to content

Instantly share code, notes, and snippets.

@maxweber
Created May 27, 2026 12:41
Show Gist options
  • Select an option

  • Save maxweber/4b03dff8b3ab3fed7f42891e60faada6 to your computer and use it in GitHub Desktop.

Select an option

Save maxweber/4b03dff8b3ab3fed7f42891e60faada6 to your computer and use it in GitHub Desktop.
Evaluate Clojure code in the running app (written by Claude)
#!/usr/bin/env bb
;; =============================================================================
;; eval-app - Evaluate Clojure code in the running app
;; =============================================================================
;;
;; This tool connects to the nREPL of the app running evaluates Clojure code.
;; Useful for inspecting app state, querying the database, or debugging issues
;; in the running system.
;;
;; USAGE:
;; cat <<'EOF' | agent-tools/eval-app
;; (your-code-here)
;; EOF
;;
;; cat code.clj | agent-tools/eval-app
;;
;; OPTIONS:
;; --host HOST nREPL host (default: localhost)
;; --port PORT nREPL port (default: 4000)
;; --timeout MS Timeout in milliseconds (default: 30000)
;; --help, -h Show help
;;
;; EXAMPLES:
;;
;; # Get the current database value
;; cat <<'EOF' | agent-tools/eval-app
;; (dev/db)
;; EOF
;;
;; # Query an entity
;; cat <<'EOF' | agent-tools/eval-app
;; (dev/show-entity 12345)
;; EOF
;;
;; # Check system state
;; cat <<'EOF' | agent-tools/eval-app
;; (keys @app.system/state)
;; EOF
;;
;; # Multi-line code
;; cat <<'EOF' | agent-tools/eval-app
;; (d/q '[:find ?e :where [?e :user/email]] (dev/db))
;; EOF
;;
;; OUTPUT:
;; Returns the result of evaluating the Clojure code, pretty-printed.
;; Errors are printed to stderr.
;;
;; =============================================================================
(require '[babashka.cli :as cli]
'[clojure.string :as str]
'[bencode.core :as bencode])
(import '[java.net Socket]
'[java.io PushbackInputStream])
(def cli-spec
{:host {:desc "nREPL host"
:default "localhost"}
:port {:desc "nREPL port"
:default 4000
:coerce :int}
:timeout {:desc "Timeout in milliseconds"
:default 30000
:coerce :int}
:help {:desc "Show help"
:alias :h}})
(defn print-help []
(println "eval-app - Evaluate Clojure code in the running app")
(println)
(println "Usage: cat <<'EOF' | agent-tools/eval-app [OPTIONS]")
(println " (your-code-here)")
(println " EOF")
(println)
(println " cat code.clj | agent-tools/eval-app [OPTIONS]")
(println)
(println "Options:")
(println " --host HOST nREPL host (default: localhost)")
(println " --port PORT nREPL port (default: 4000)")
(println " --timeout MS Timeout in milliseconds (default: 30000)")
(println " --help, -h Show this help")
(println)
(println "Examples:")
(println " cat <<'EOF' | agent-tools/eval-app")
(println " (dev/db)")
(println " EOF"))
(defn bytes->str [x]
(if (bytes? x)
(String. ^bytes x "UTF-8")
x))
(defn parse-response [response]
(into {}
(map (fn [[k v]]
[(keyword (bytes->str k))
(if (sequential? v)
(mapv bytes->str v)
(bytes->str v))])
response)))
(defn nrepl-eval [{:keys [host port timeout]} code]
(let [socket (Socket. ^String host ^int port)
_ (.setSoTimeout socket timeout)
out (.getOutputStream socket)
in (PushbackInputStream. (.getInputStream socket))
session-id (str (random-uuid))
msg {"op" "eval"
"code" code
"id" session-id}]
(try
(bencode/write-bencode out msg)
(loop [has-error false]
(let [response (parse-response (bencode/read-bencode in))]
(when-let [out-str (:out response)]
(print out-str)
(flush))
(when-let [err-str (:err response)]
(binding [*out* *err*]
(print err-str)
(flush)))
(when-let [value (:value response)]
(println value))
(when-let [ex (:ex response)]
(binding [*out* *err*]
(println "Exception:" ex)))
(let [status (set (:status response))
has-error (or has-error
(contains? status "error")
(contains? status "eval-error"))]
(if (contains? status "done")
(if has-error 1 0)
(recur has-error)))))
(finally
(.close socket)))))
(defn eval-code [opts code]
(try
(nrepl-eval opts code)
(catch java.net.ConnectException e
(binding [*out* *err*]
(println "Error: Cannot connect to nREPL at" (str (:host opts) ":" (:port opts)))
(println "Make sure the app is running (bin/dev up)"))
1)
(catch java.net.SocketTimeoutException e
(binding [*out* *err*]
(println "Error: nREPL request timed out"))
1)
(catch Exception e
(binding [*out* *err*]
(println "Error:" (.getMessage e)))
1)))
(let [{:keys [opts]} (cli/parse-args *command-line-args* {:spec cli-spec})
opts-with-defaults (merge {:host "localhost" :port 4000 :timeout 30000} opts)]
(if (:help opts)
(print-help)
(let [code (slurp *in*)]
(if (str/blank? code)
(do (binding [*out* *err*]
(println "Error: No code provided on stdin")
(println "Usage: cat <<'EOF' | agent-tools/eval-app")
(println " (dev/db)")
(println " EOF")
(println "Run with --help for more information"))
(System/exit 1))
(System/exit (eval-code opts-with-defaults code))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment