A terminal REPL for Clojure (and friends) built on nREPL. It's meant as a spiritual successor to REPLy, with a proper line editor, the core of CIDER's tooling and a CLI that works just as well for scripts and coding agents as it does for people.
It's early days: there's a REPL, jack-in and a handful of command line tools. PLAN.md has the details on where this is going.
bbin installs the latest release as a
neorepl command, which runs on babashka 1.13.227
or newer:
bbin install io.github.nrepl/neoreplFrom a checkout, bbin install . installs that instead.
neorepl connects to the nREPL server whose .nrepl-port file is in the
current directory or one of its parents (neorepl ports lists the ones it
can see) and starts a REPL. Enter evaluates the input once its forms are
complete, and before that starts a line indented for the form it's in,
Ctrl-C interrupts an evaluation and Ctrl-D exits. Code gets
highlighted, along with the delimiter that pairs with the one at the cursor,
values get pretty printed and the history is kept per project. What gets
printed after an evaluation's done shows up above the prompt, and that
includes what the server's own threads print, like a web server's
handlers, with nREPL 1.5 or later and babashka. So do the values sent to
tap> in Clojure, with cider-nrepl 0.64 or later, and ,tap opens the
last one in the inspector.
TAB completes, and the arglists of the function you're calling show up
under the input, with the parameter the argument at the cursor goes into in
bold, and a macro's, like defn's, matched by their shape. C-x C-d shows
the documentation of what's at the cursor, C-x C-s its source and C-x C-a
the vars whose names have it in them. C-x C-e opens what you've typed in
your $EDITOR, as it does in bash, and brings back what you save, and C-x
Enter evaluates only the form at the cursor, or the one right before it,
then brings back what you'd typed. After an evaluation, the prompt shows
at its right whether it worked, ✓ or ✗, and how long it took, until you
press Enter.
Commands start with a comma, the CIDER way: ,doc, ,source, ,apropos,
,macroexpand, ,pst for the last error's stack trace, ,stacktrace to
browse it, ,reload, ,autoreload to reload what changed before each
evaluation, ,test, ,inspect, ,trace, ,profile, ,edit to open a
definition in your $EDITOR, a copy of it for one in a jar, or with
cider-nrepl, the namespace's file without a name, ,ns to switch
namespaces, loading the one you name if it isn't yet, or without a name, to
browse them, their vars and their docs, ,spec to browse the specs, from
one to those its form names, ,sessions to step into another session, like
an agent's, and ,help for the rest.
Without a terminal, say in a pipe or Emacs' M-x shell, it works like
clojure.main's REPL instead.
The line editor, JLine, takes the readline settings it knows from
~/.inputrc, or the file $INPUTRC names, so set editing-mode vi there
gets you vi's keys, and what's for neorepl alone goes under $if neorepl.
Input longer than 1000 characters, like a big pasted form, shows up plain,
with no arglists, no matching delimiter and no undo, since JLine stops doing
all that past its features-max-buffer-size, which the inputrc can raise:
$if neorepl
set features-max-buffer-size 10000
$endif
,set lists the REPL's settings and turns them on or off: eldoc, the
arglists under the input, color, pretty, for values pretty printed as
wide as the terminal, server-output, what the server's System/out and
System/err get, and taps, for what shows up above the prompt, quiet,
which leaves all of that out for a while, outcome, for how the last
evaluation went at the right of the prompt, and autoreload. print-length
and print-level take a number, how many items of a collection a value
shows and how deep it goes, or off, which leaves that to the session's
*print-length* and *print-level*. Pretty printing counts a map's
entries as a level, as clojure.pprint does, so at level 1 {:a 1} is
{#}. C-x C-h toggles eldoc, C-x C-p
pretty printing and C-x C-q quiet, and every setting that's on or off,
but autoreload, has a widget the inputrc can bind a key to, named
neorepl-toggle- and the setting, like "\C-xt": neorepl-toggle-taps.
What they start as goes in ~/.config/neorepl/config.edn, or under
$XDG_CONFIG_HOME if that's set, and NO_COLOR turns colors off whatever
it says:
{:eldoc false
:print-length 100
:autoreload true}nbb and Basilisp print values their own way, not pretty and without the
limits. babashka takes the limits only when it pretty prints, and ignores
the session's own *print-length* and *print-level*.
With no server to connect to, neorepl starts one for the project, the way
CIDER's jack-in does, and stops it when the REPL ends. The project's build
tool runs it, with cider-nrepl: Leiningen for a project.clj, the Clojure
CLI for a deps.edn, babashka, which has CIDER's essentials built in, for a
bb.edn and shadow-cljs for a shadow-cljs.edn. --tool picks another one
and --aliases adds deps.edn aliases or Leiningen profiles. In a Leiningen
project, :init-ns and :init in :repl-options work, :prompt and
:welcome don't, being for lein repl's own client, and a cider-nrepl in
its :plugins or in ~/.lein/profiles.clj takes the place of the one
jack-in adds, which the REPL says when it starts. neorepl server starts
one that stays up until you stop it, for editors, coding agents and
neorepl eval to share:
neorepl --aliases :dev:test
neorepl server --port 7888ClojureScript REPLs run inside a Clojure session on the server, through shadow-cljs or piggieback, which jack-in adds to deps.edn and Leiningen projects. The REPL follows the session into whichever runtime you start:
;; shadow-cljs: the :app build's runtime, or a fresh Node one
(shadow/repl :app)
(shadow/node-repl)
;; figwheel-main: builds dev and connects to its runtime
(figwheel.main.api/start "dev")
;; ClojureScript alone: a Node runtime
(require 'cljs.repl.node)
(cider.piggieback/cljs-repl (cljs.repl.node/repl-env))From then on evaluations are ClojureScript's, and so are completion, eldoc,
,doc, ,where and ,macroexpand when the server has cider-nrepl.
:cljs/quit switches back. Ctrl-C can't stop ClojureScript code that's
still running. With shadow-cljs a second Ctrl-C stops waiting for it, and
piggieback stops waiting on the first Ctrl-C, though the code keeps the
runtime busy.
neorepl eval evaluates code on the same server:
neorepl eval '(+ 1 2)'
neorepl eval --format json -f script.clj
neorepl eval --ns my.app '(start)'
neorepl eval --reload '(my.app/handler {})'
echo '(range 3)' | neorepl eval ---ns evaluates in a namespace, loading it first unless it's there, and
leaves the session in its own. -f FILE --line N:COL evaluates only the
top-level form at that spot of FILE, in the file's namespace, which is how an
editor without a plugin evaluates the form at the cursor:
doc/editors.md has setups for Neovim, Emacs and Helix.
--reload first reloads the namespaces whose files changed, and the ones
that use them, so code edited anywhere is what runs, keeping what defonce
defined. That takes cider-nrepl, whose clj-reload notes what changes from
the first reload on, which neorepl does as soon as a server it starts is up.
Evaluations share a session, so *ns*, *1 and *e carry over from one
call to the next. --session NAME keeps separate ones apart, neorepl sessions lists them and neorepl sessions close NAME starts one over.
In the REPL, ,sessions NAME steps into one, say to see what an agent left
in *1 and *e, and ,sessions - steps back out.
Servers whose sessions end with the connection, like Basilisp's and jank's,
start each call afresh. neorepl eval --help has the rest.
neorepl load loads a file the way load-file does, which is how you get
your changes into the running program after editing it:
neorepl load src/my/app.cljneorepl doc, neorepl source, neorepl apropos and neorepl pst do what
their clojure.repl namesakes do, neorepl macroexpand expands macros,
neorepl complete lists completions, neorepl where says where something's
defined, neorepl refs which functions use it, or with --deps, which it
uses, and neorepl reload reloads code, all using cider-nrepl when the
server has it, which refs takes. In the REPL, refs and refs --deps
are ,refs and ,deps. --session NAME looks things up as that session
sees them: in its language, ClojureScript included, and in its namespace,
the Clojure one in ClojureScript, which --ns can name otherwise:
neorepl doc map
neorepl source --ns my.app my.app/start
neorepl pst --format jsonneorepl test runs tests, in the namespaces you name, the tests you name or,
without either, the whole project's, and prints what didn't pass and where,
with the stack trace of each error down to the test that hit it. With
cider-nrepl 0.63 or later it says which namespace it's testing as it goes.
neorepl retest reruns the ones that didn't. They're ,test and ,retest
in the REPL. cider-nrepl or babashka on the server runs them, and so does
plain clojure.test, given the namespaces:
neorepl test
neorepl test my.app-test my.app.db-test
neorepl test --format json my.app-test/start-testIn a session in ClojureScript they run the ClojureScript tests, which takes cider-nrepl and piggieback for now: on shadow-cljs cider-nrepl can't run them yet.
,inspect shows a value, or the last one without code, in cider-nrepl's
inspector, full-screen: the arrow keys (or j and k) move between the
values in it, Enter or → digs into one, Backspace or ← goes back, [ and
] page through big collections, a screenful at a time, v shows the
object's fields, P turns pretty printing on and off, g shows the value
again, for one that changes, like an atom's, d defines what's being
inspected as a var in the REPL's namespace, under the name you type, and
q takes you back to the REPL. neorepl inspect pushes, pops, pages and
defines the same way for scripts and agents, one step at a time, since the
inspector stays in the session. Both take
cider-nrepl or babashka on the server:
neorepl inspect '(System/getProperties)'
neorepl inspect --push 3
neorepl inspect --pop
neorepl inspect --def props,stacktrace shows the last error full-screen the same way: Enter on a
frame opens its source in your $EDITOR at that line, coming back to the
view after, and a brings back the frames ,pst leaves out, the ones of the REPL's own machinery and the
repeats of the same function. With cider-nrepl, i inspects the cause the
frame's in and d its ex-data, and q brings you back. After a ,test
with errors it shows those, each under the test it's from, until an
evaluation fails.
,trace traces functions, or every function in a namespace, so each call
shows its arguments and what it returns, nested in the calls it's in.
,trace without a name lists what's traced and ,untrace stops, for the
functions named or everything. In the REPL, calls on other threads, like an
HTTP handler's, show up above the prompt too. neorepl trace and neorepl untrace do the same for scripts and agents, the calls showing up in the
output of the neorepl eval that makes them. Tracing takes cider-nrepl on
the server:
neorepl trace my.app/handler my.app.db
neorepl eval '(my.app/handler {:uri "/"})'
neorepl untrace,profile times every call of a function, or of every function in a
namespace, and without a name shows what it measured: how many calls each
function had, and how long they took, on average, in all, at the shortest
and the longest. ,unprofile stops and ,profile-clear forgets what was
measured, for the next try. neorepl profile does the same from the shell,
with --clear, and it takes cider-nrepl on the server too:
neorepl profile my.app.db
neorepl eval '(my.app/import "data.csv")'
neorepl profileneorepl check finds unbalanced parens, brackets and braces, the most
common way for an edit (or a coding agent) to break a Clojure file. It
doesn't need a server:
$ neorepl check src/my/app.clj
src/my/app.clj:12:1: Unclosed (--fix repairs them the way parinfer's indent mode would, trusting the
indentation over the delimiters: a line closes the forms opened at its
column or right of it. It only touches the top-level forms that don't
balance, writes the files back, and leaves alone the ones it can't tell how
to repair, like a string that never ends:
$ neorepl check --fix src/my/app.clj
src/my/app.clj:12:1: Unclosed (, repaired from the indentationAgents can drive the CLI like anyone else: every command takes --format json, and named sessions keep *ns*, *1 and *e from one call to the
next. skills/neorepl/SKILL.md is a skill that
teaches them the workflow: check an edited file's parens, load it, run its
tests, look at the stack trace when something fails. Agents that read
skills load it when they work on Clojure code.
doc/agents.md says where to put it for Claude Code,
Codex, Gemini CLI, Cursor and VS Code, and how to set up neorepl mcp with
each.
For an agent without skills, a few lines in the project's AGENTS.md (or
CLAUDE.md, GEMINI.md) cover the essentials:
This project has an nREPL server you can reach with `neorepl`. After
editing a Clojure file, run `neorepl check --fix FILE`, then
`neorepl load -s agent FILE`, then `neorepl test -s agent NS`. Try code
with `neorepl eval -s agent 'CODE'` and read errors with
`neorepl pst -s agent`. `neorepl COMMAND --help` has the details.For agents that speak MCP, neorepl mcp serves the same features as
MCP tools over standard input and output, through the server it finds the
way the other commands do. With Claude Code:
claude mcp add neorepl -- neorepl mcpIts tools are eval, load, doc, source, where, refs, apropos,
macroexpand, reload, test, retest, pst, interrupt, check and
ports, with check taking a fix argument for --fix and refs a
deps one for --deps, and they share the CLI's sessions, the one called
agent unless a call names another, so neorepl eval --session agent and
the eval tool see the same state. That's fewer tools
than the CLI has commands on purpose: an agent pays for every tool's
description in its context on every turn, whether it uses it or not, while a
command costs nothing until it runs. So completion, which is for people
typing, and the inspector, which pages through a value a round trip at a
time, are left out, as is managing sessions and servers, which is the job of
whatever starts neorepl mcp. Evaluations get cut at 10,000 characters and
interrupted after 60 seconds, unless --max-output and --timeout say
otherwise, so a stray (range) or an endless loop can't take over the
agent's context or the session.
neorepl isn't the only terminal REPL that talks nREPL these days. The
Clojure team's clojure-cli.repl
and babashka's bb repl --connect are both nREPL clients too, but they're
after different things. neorepl's goals are a CIDER-like REPL in the
terminal, a CLI that serves coding agents as well as people, and working
with any nREPL server out there. Here's how the three stack up in practice
(as of November 2026):
| neorepl | clojure-cli.repl | bb repl --connect |
|
|---|---|---|---|
| Runs on | JVM or babashka | JVM | babashka |
| Servers | any nREPL server, tested with nREPL, babashka, shadow-cljs, piggieback, nbb and Basilisp | its own; other servers only evaluate and complete | any nREPL server |
| Starts a server for you | Leiningen, deps.edn, bb and shadow-cljs projects, with cider-nrepl | deps.edn projects | no |
| Completion, arglists as you type | yes | completion | yes |
| Docs | yes, and source, apropos, macroexpansion | yes, with Java docs | yes |
| Eval at the cursor | yes | yes | no |
| Structural editing | no | yes | no |
| Inspector | yes | yes | no |
| Test runner | yes | no | no |
| Browsing namespaces and specs, cross-references | yes | no | no |
| Reloading, tracing, profiling | yes, and reloading what changed before each evaluation | no | no |
| Beside an editor | evaluates from Neovim, Emacs and Helix without a plugin | shows what the editor evaluates | no |
| ClojureScript | shadow-cljs, piggieback, figwheel-main | no | evaluation only |
| CLI for scripts and agents | yes: eval, docs, tests and more, with JSON output | no | no |
In short, clojure-cli.repl is the one to try if you live in deps.edn and want
the fanciest line editing, and bb repl --connect is a lightweight way to
poke at a running server. neorepl follows CIDER: it sends the ops CIDER
sends, so it gets the most out of a server with cider-nrepl (or babashka's
take on it) and falls back to nREPL's built-in ops and plain evaluation
without one. It doesn't add middleware of its own.
neorepl runs from the same source on the JVM (Java 17+, Clojure 1.10+) and on babashka (1.13.227+). The tests run on both:
bb test # on babashka
bb test:jvm # on the JVM, with the default Clojure version
bb test:jvm :1.10 # ... or with an older one (:1.10, :1.11)Copyright © 2026 Bozhidar Batsov and contributors.
Distributed under the Eclipse Public License 1.0, the same as Clojure.