Skip to content

Instantly share code, notes, and snippets.

@realgenekim
Created August 14, 2025 17:36
Show Gist options
  • Select an option

  • Save realgenekim/42eae555debe4584afa40a99e239a1b2 to your computer and use it in GitHub Desktop.

Select an option

Save realgenekim/42eae555debe4584afa40a99e239a1b2 to your computer and use it in GitHub Desktop.
giving Claude Code power of nrepl access

nREPL Client Script (nrepl.clj)

A fast Babashka script for interacting with the running nREPL server. Located at server/nrepl.clj.

Usage

The script automatically reads the port from .nrepl-port file.

Command line argument (fastest for one-off commands):

./nrepl.clj '(+ 1 2 3)'
./nrepl.clj '(require '\''[podcast.db :as db]) (count (db/get-recent-episodes 10))'
./nrepl.clj '(require '\''[clojure.repl :as repl]) (repl/source podcast.server/validate-progress-snapshot)'

Piped input:

echo '(+ 1 2 3)' | ./nrepl.clj
cat some-code.clj | ./nrepl.clj

Interactive mode (if no args or piped input):

./nrepl.clj
# Starts interactive REPL prompt

When to use nrepl.clj

  • Quick code evaluation: Test functions or run queries without restarting REPL
  • Inspect running state: Check var values, function sources, database connections
  • Database queries: Run quick DB queries to verify data
  • Function testing: Test individual functions with different inputs
  • Source inspection: Use clojure.repl/source to view function implementations
  • Namespace reloading: Reload namespaces after code changes

Examples for common tasks

Database Queries (Very Useful!)

# Check queue status
./nrepl.clj '(require '\''[podcast.db :as db]) (println "Queue count:" (count (db/get-user-queue "dev-user")))'

# View queue items with titles
./nrepl.clj '(require '\''[podcast.db :as db]) (let [queue (take 3 (db/get-user-queue "dev-user"))] (doseq [item queue] (println "Title:" (:user_play_queue/title item))))'

# Check subscriptions
./nrepl.clj '(require '\''[podcast.db :as db]) (let [subs (db/get-user-subscriptions "dev-user")] (println "Subscriptions:" (count subs)) (doseq [s (take 3 subs)] (println "-" (:subscriptions/title s))))'

# View highlights
./nrepl.clj '(require '\''[podcast.db :as db]) (let [highlights (db/get-user-highlights "dev-user")] (println "Total highlights:" (count highlights)) (when (pos? (count highlights)) (let [h (first highlights)] (println "Latest:" (:highlights/note h) "at" (:highlights/timestamp h) "seconds"))))'

# Get recent episodes
./nrepl.clj '(require '\''[podcast.db :as db]) (println "Recent episodes:" (count (db/get-recent-episodes 10)))'

Code Inspection

# View function source
./nrepl.clj '(require '\''[clojure.repl]) (clojure.repl/source podcast.server/validate-progress-snapshot)'

# Test a handler function
./nrepl.clj '(require '\''[podcast.server :as s]) (s/health-check-handler {})'

# Check server state
./nrepl.clj '(require '\''[podcast.server]) @podcast.server/server'

Why use this instead of regular clojure command?

  • Much faster: Babashka starts instantly vs ~2-3 seconds for Clojure JVM
  • Connected to running server: Access live server state and loaded namespaces
  • No startup overhead: Reuses existing REPL connection
#!/usr/bin/env bb
;; -*- mode: clojure -*-
;; deps
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {babashka/nrepl-client {:git/url "https://github.com/babashka/nrepl-client"
:git/sha "19fbef2525e47d80b9278c49a545de58f48ee7cf"}}})
(require '[babashka.nrepl-client :as nrepl]
'[clojure.string :as str])
(def port-file ".nrepl-port")
(defn read-port []
(try
(-> port-file slurp str/trim parse-long)
(catch Exception _
(println "Error: Could not read .nrepl-port file")
(System/exit 1))))
(defn eval-and-print [port expr]
(try
(let [result (nrepl/eval-expr {:port port :expr expr})]
;; Print any output
(doseq [response (:responses result)]
(when-let [out (:out response)]
(print out))
(when-let [err (:err response)]
(binding [*out* *err*]
(print err))))
;; Print return values (only if not nil and no output was printed)
(when-let [vals (:vals result)]
(when (and (= 1 (count vals))
(not= "nil" (first vals))
(empty? (filter :out (:responses result))))
(println (first vals))))
;; Check for errors
(when-let [ex (:ex result)]
(binding [*out* *err*]
(println "Error:" ex)))
(System/exit 0))
(catch Exception e
(binding [*out* *err*]
(println "Connection error:" (.getMessage e)))
(System/exit 1))))
(defn -main [& args]
(let [port (read-port)]
(cond
;; If arguments provided, eval them and exit
(seq args)
(eval-and-print port (str/join " " args))
;; If stdin is available (piped input), read and eval
(not (.ready *in*))
(if-let [input (slurp *in*)]
(eval-and-print port input)
(System/exit 0))
;; Otherwise, interactive mode
:else
(do
(println (str "Connected to nREPL on port " port))
(println "Enter Clojure expressions (Ctrl+D to exit):")
(loop []
(print "nrepl> ")
(flush)
(when-let [line (read-line)]
(when (not (str/blank? line))
(eval-and-print port line))
(recur)))))))
(apply -main *command-line-args*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment