here is a pomodoro timer: https://codeberg.org/timokramer/charm.clj/raw/branch/master/docs/examples/src/examples/pomodoro.clj
excellent. I downloaded the file, prepended it with:
#_:clj-kondo/ignore
(babashka.deps/add-deps '{:deps {org.codeberg.timokramer/charm.clj {:git/sha "0db03d34885267620d51c1c1f68644106f278acd"}}})
and added (-main) at the end and then I could run it even without any other files :)I tried doing this^^ and got this:
Error building classpath. Failed to infer git url for: org.codeberg.timokramer/charm.clj
----- Error --------------------------------------------------------------------
Type: clojure.lang.ExceptionInfo
Message:
Data: {:proc #object[java.lang.ProcessImpl 0x550675cf "Process[pid=46934, exitValue=1]"], :exit 1, :in #object[java.lang.ProcessBuilder$NullOutputStream 0x6e386f72 "java.lang.ProcessBuilder$NullOutputStream@6e386f72"], :out #object[java.lang.ProcessImpl$ProcessPipeInputStream 0x4e5d3945 "java.lang.ProcessImpl$ProcessPipeInputStream@4e5d3945"], :err #object[java.lang.ProcessBuilder$NullInputStream 0x2b7fc968 "java.lang.ProcessBuilder$NullInputStream@2b7fc968"], :prev nil, :cmd ["/usr/bin/java" "-XX:-OmitStackTraceInFastThrow" "-classpath" "/Users/oliver/.deps.clj/1.12.4.1582/ClojureTools/clojure-tools-1.12.4.1582.jar" "clojure.main" "-m" "clojure.tools.deps.script.make-classpath2" "--config-user" "" "--config-project" "__babashka_no_deps_file__.edn" "--basis-file" "/Users/oliver/.clojure/.cpcache/71A0AB41F908CB45811FF9A516C5A6F2.basis" "--cp-file" "/Users/oliver/.clojure/.cpcache/71A0AB41F908CB45811FF9A516C5A6F2.cp" "--jvm-file" "/Users/oliver/.clojure/.cpcache/71A0AB41F908CB45811FF9A516C5A6F2.jvm" "--main-file" "/Users/oliver/.clojure/.cpcache/71A0AB41F908CB45811FF9A516C5A6F2.main" "--manifest-file" "/Users/oliver/.clojure/.cpcache/71A0AB41F908CB45811FF9A516C5A6F2.manifest" "--config-data" "{:deps {org.codeberg.timokramer/charm.clj {:git/sha \"0db03d34885267620d51c1c1f68644106f278acd\"}}, :deps-root \"\", :aliases {:org.babashka/defaults {:replace-paths [], :classpath-overrides {org.clojure/clojure \"\", org.clojure/spec.alpha \"\", org.clojure/core.specs.alpha \"\"}}}}" "-A::org.babashka/defaults"], :type :babashka.process/error}
Location: /Users/oliver/Desktop/./pomodoro.bb:2:1
----- Context ------------------------------------------------------------------
1: #_:clj-kondo/ignore
2: (babashka.deps/add-deps '{:deps {org.codeberg.timokramer/charm.clj {:git/sha "0db03d34885267620d51c1c1f68644106f278acd"}}})
^---
3:
4: (ns examples.pomodoro
5: "Pomodoro CLI timer with work/break cycles.
6:
7: Usage:
----- Stack trace --------------------------------------------------------------
babashka.process/check - <built-in>
babashka.process/shell - <built-in>
babashka.impl.deps/add-deps/fn--29015 - <built-in>
borkdude.deps/-main - <built-in>
clojure.core/apply - <built-in>
... (run with --debug to see elided elements)
clojure.core/apply - <built-in>
clojure.core/with-bindings* - <built-in>
babashka.impl.deps/add-deps/fn--29022 - <built-in>
babashka.impl.deps/add-deps - <built-in>
user - /Users/oliver/Desktop/./pomodoro.bb:2:1
Have I got the wrong version of babashka, or am I doing something else dumb?
https://clojurians.slack.com/archives/CLX41ASCS/p1769601100306739?thread_ts=1769521188.320639&cid=CLX41ASCS Sorry for not replying in the proper thread
Aha! I need the dev version of bb, that makes sense! Thanks @timok
That is so cool! Thx again! (I am going to watch out for a stable release for bb with jline3 so that I can use this day2day)
@maleghast please test the dev version if you have time. you can install it alongside the stable one: https://clojurians.slack.com/archives/CLX41ASCS/p1769601100306739?thread_ts=1769521188.320639&cid=CLX41ASCS
I have built a dev-build of bb in a folder as per those instructions and then run the pomodoro timer with the additions that you mentioned and it works beautifully! š
I am not going to have time to but the dev build through its paces more widely right now, I am afraid, but I am happy to wait for a stable release with jline3 as and when you are completely ready / satisfied that the update is stable and ready to be rolled out š
Thx again š
thanks for testing it, this was already helpful
Awesome @timok š. I've been myself working on a TUI app using Babashka, polishing up for release somewhere this week probably: http://prstack.dev I've made a small incomplete TUI library with a react like API, this is how the code looks: https://github.com/yannvanhalewyn/prstack/blob/main/src/prstack/tui/app.clj#L57 I was actually planning on making a TUI library myself from the code in src/bb_tui. But maybe Charm would be sufficient / we could join forces on something.
awesome. I hear a bb conf proposal coming
@borkdude FYI I was planning on making a proposal about how to leverage Babashka to make great dev tools and showing off these things, bit of inspiration and open discussion. Will make a submission after releasing PRStack
you still have a few weeks :)
I know, just FYI š
@timok cool I've tested charm. I couldn't get it to run with Babashka because of JLine dependency. On my Intel Mac machine using native-image as in the examples README complained, I'm not often using native-image and might not have things set up properly.
Caused by: com.oracle.svm.core.util.UserError$UserException: Class initialization of clojure.pprint.dispatch__init failed. Use the option
'--initialize-at-run-time=clojure.pprint.dispatch__init'
to explicitly request initialization of this class at run time. Exception thrown by the class initializer:
java.lang.IllegalStateException: Attempting to call unbound fn: #'clojure.pprint/set-pprint-dispatch
at clojure.lang.Var$Unbound.throwArity(Var.java:45)
at clojure.lang.AFn.invoke(AFn.java:32)
at clojure.pprint.dispatch__init.load(Unknown Source)
at clojure.pprint.dispatch__init.(Unknown Source)
at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized0(Native Method)
at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized(Unsafe.java:1169)
(internal stack frames of the image generator are omitted)
clj -M commands work well! Some examples don't return to the old terminal state correctly. The Pomodoro example exits properly.@yannvahalewyn https://clojurians.slack.com/archives/CLX41ASCS/p1769601100306739?thread_ts=1769521188.320639&cid=CLX41ASCS
what I mean with that link: it points you to the dev build which should now work with charm.clj
(in case it wasn't clear)
I've installed a local dev build of bb and it gets through the JLine error. However the examples turn up empty on my machine:
it's because you need to invoke the main function
Ah yes you mentioned that earlier silly me
Yep, works like a charm šŖ
woohoo :)
Should a babshka library for TUIs be implemented without JLine? It seemed to grow the bb binary. I did have an implementation before to prompt without it and now prompts in PRStack have been replaced with https://github.com/lispyclouds/bblgum which works great. Is JLine something useful to add to babashka in general?
it's already merged. adds 3.5mb to the binary. TUI stuff has been asked for for long time and shelling out to binaries is ok-ish but this is much more convenient
Ok cool. Nice job š. Also RIP the time I spent working around it š
every time I've seen people try or ask for TUI stuff contributed to pulling the trigger on this. You've been doing your thing, lately someone else was asking about it and ended up using the lanterna pod, other people shelled out to gum, yet other people shelled out to some other tool which I don't remember the name of (but it's documented in some blog somewhere). I was reluctant to use jline3 because of the '3" which to me signals: 4 is coming which breaks everything, but the maintainer ensured me that not much is breaking at all. Since GraalVM 25 native-image supports FFM and jline now also has an FFM terminal, so there's nothing workaround-ish happening anymore with JNI and copying .so libraries at startup etc. So jline3 is fine in bb now. I'm surprised it only adds 3.5mb since I had to activate the FFM stuff in GraalVM now which is probably 2mb or so
Thanks for the insight š. Yeah I get it. Btw my comment above was a little joke, I also learned a lot messing around and such.
(here's an example of a recent question: https://clojurians.slack.com/archives/CLX41ASCS/p1768583461449979)
oh sure :)
It makes sense to want to use Babashka for TUI apps. Great to see you've pulled the trigger on JLine. TUI apps are making a bit of a comeback it seems and who wouldn't want to make such programs using Clojure - seriously! So I'd be very excited to see some kind of well maintained library for making TUI apps using BB. It's likely still some work to get the right design and to get it working correctly everywhere. I'd like to either build or help maintain such a thing.
I've been using my thing for a bit under a year now and seems stable on Mac, yet feature incomplete and 0 time spent on performance, it just re-renders everything every state change.
have you seen my snake game? https://gist.github.com/borkdude/21f8e8996d5d20044f3f1c265ab7cc48 I ported it from reagami to TUI (I let an LLM do it) This is the same game in reagami: https://squint-cljs.github.io/squint/?src=gzip%3AH4sIAAAAAAAAE41WW4%2BjNhR%2Bz684y6iS2YoJTLsvZGenlbqqql2pUi9PiFYOPiSeGJu1HRI6yn%2BvbJNAZpnVECngw%2BfP534g0kBDuVwAkFzjlz3XCEW0tbY1%2BXKJprk126VGuqENjyCnBoZFGceLBWFYg%2BH%2FIdylMaxWsNGcBQG5S493aRwgFQoBWYAgrbZBcA9Z2h4Di5IVgrHUolOFWtXAU24k3SEUxTvI0hKKH8PtB3crYQVccsupgABbK9Yv4HzljGsoMkgdsFEdlxvQfLO1E0itFIMiC%2BwjnRNPUFTwDh%2FA6j2ezjZL0FQy1SSBolwAFMSJEi6ttz%2BGZ%2BvysrVRHSaDaW4nMQfavrkYD0BqCcVTvsPeQBGAzhp%2FVtCm9JEw9lQOihJeA5HKDu%2Fji%2F5mNJgItFBskTIgHVZQkO%2BBSLsFUnNtbHBjDGkcpO7INB6Z5q95jmzCkcXO9pmt1OIDkHvwGjnjZlESD4OzvNKkUtKELc5mzzG8Xu%2BtoBcd4nnVj0ExT5DOIvoJIptFbLlNDlSIByBKA3kPR%2B%2B099C%2FQHnlsA%2F3cDynyId76MPziwcZFPUDEKMahJuzt75z6YXGjt6J4%2FI5g3MV%2B5p2oj01RlVg7CXJayoMzqgyUeTVe0J4R3iI0hjOUH5kUkhzTshRGPwGTRyuxWK1gr94gxqEUu2CPJqlQfubtKg7KqY1d5emAf4J%2B7WimkGlpNVKmAW5pYx97FDaz9xYlKjh0SwPXDJ1gGiHPVMHGS2GCsXg8VBWO%2ByB3CbuhpdQTCsb9i1zN9eYXs4RT8y4%2FiqY14GlBmGH%2FbdzLfpZa3X4u41Cd7j31VikkJXx%2BTHJ5kvzOckvzu5nNMmE55U0n7G21zSuQQ8siX9%2BDc0fro9f8yQTolfwMK7PeRN6sptHicbKQlEcoS%2BhUkL5KBS5Fz%2FlRyBv4eiRMeS9W%2FVhNTkrP3Bmh%2FGWb9Ep6hdTSM2FCPyncShsaINJSMcwFHxWXQ%2BB6QA4wU8%2BrUKiFDnjXTijyE23gadBEfI2TONB60GjK%2BmVp3Jje4HwlK%2BVZqghytojGCU4g7Wg1S6a9Wu%2BptVuo9VeMohuEOvodBqAq1Xox%2BeKqJWGoqV6aNNjnv%2FjbQVirAb3Pj5BMUbF74g2GlFGl%2BCuVtNZPUF7R0UaWVSOUOdhUB1q%2FyfouXrIYYtyfngWucVjiP271MU8S1PXt6RNho%2BeIZpRcM4Jol%2FdKb93qN9EZVyOH0laKTeJ%2FcB4NEumqn2D0t5%2B2aPu%2F0SBlVUaohvatlFQgDBl1TW40kgtfhToVhAx3kXxpdWgfQMJZxCNFHC93X0h3bYaW5RsmvwapYt1yLvh8245CJ3eUIzZGSyijCUHaqvt0NvyPMDPrfFf8L%2FS0Tl5OGx4%2Fh8hQm1YdAoAAA%3D%3D Reagami is a simple library that prevents re-renders when possible (similar to React, but less sophisticated).
the TUI snake game re-renders everything
No will check it out!
but it could work more like reagami
I've seen flickering on Windows with this game, but not on mac
I mean, the current TUI version
Cool! Note to self, always talk to you before building something. I've been re-inventing some wheeels /s
well, reagami just compares virtual nodes to the real dom nodes. perhaps the same idea would work with terminals? I don't know :)
Yes it probably would! I was pushing back implementing this but super cool to see this already exists. I'll play around with it sometime
The bb snake game works great on my mac machine indeed
With reagami, can we get the diff and execute rendering ourselves somehow?
no, reagami is hardcoded agains to the DOM to yield ultra small JS. you can compile this snake game to 5kb gzipped for example
Ok. Maybe extract some kind of diffing library then or what would you suggest?
I don't recommend to use reagami's code, just the simple idea of a vdom probably
I'll use it as inspiration then
forget about reagami, I just mentioned it because I ported the snake game.
yeah I misread the intention, I heard a re-usable virtual dom diffing library haha
and you could use a vdom for performance in TUI, maybe. perhaps charm.clj does that
gotta run now
The way I understand charm is it's pretty imperative, write this to the UI now. I would try to go a bit more declarative route
Cya and thanks!
we should write reagent or reagami for TUI, that's basically the idea I wanted to convey (but I did poorly :P)
for just CRUD TUIs just re-rendering the whole screen is fine probably
for something like claude code, something more optimal is probably nice (to prevent flickering)
Np I got it. Maybe Iām poorly communicating that that is exactly what Iād like to see and have a working prototype for in PRStack
You are right about it being enough to re-render everything for CRUD TUIs. I have been using my tool for a while now and haven't run into any flickering issues, computers are fine rendering a few lines. But once you start having more dynamic elements like spinners I'd assume it's worth not rendering everything for every animation frame
Spinners are quite easy to control in a more performant manner
Considering these points maybe v-dom is a bit overkill for this environment. Maybe declaring components that each take responsibility of their own little screen space, a bit like Flutter, would also be an option. Along with some way to subscribe to changes in global state.
Would need to do something smart about layout changes though
I guess you could just te render on layout change
Something like that yes
I think just creating enough bb TUIs with the bare minimum helper functions will tell over time which patterns are useful
I'll probably just release bb-tui as an option with the current reagent-like API with full re-rendering. And put on the engineering hat if people run into issues
Talking about performance, @timok on my machine the file_browser example is rather slow. j/k takes about 1-2 seconds. How is it for others?
is it the same with clojure -M -m example.file-browser?
I think I identified some slowness in the bb version compared to clj. here bb re-renders the file thing in 166ms vs 6ms. I'll have to inspect that code to see if something can be done.
;; Regular message
:else
(let [[new-state cmd] (update @state m)]
(reset! state new-state)
(execute-cmd! cmd msg-chan)
(time (render/render! renderer (view new-state))))render-diff! seems to be a pretty expensive function
perhaps re-rendering the whole thing is more performant in bb? worth a shot
Indeed, the Clojure version is smooth. BB version feels laggy
For me every render seems slow, 1.5s on first render as well
This is the snippet that's taking 1.5s in BB:
(if (pos? width)
(mapv #(scr/truncate-line % width) new-lines)
new-lines)
Found at render/core.clj:201you are on intel mac right?
Yes 2,3Ghz 8-core Intel i9
let's solve it by using some more of those cores /s
GraalVM will support this architecture one more year, FWIW
Thanks good to know. Wanting to upgrade to apple silicon machines once my bank account supports this feature
I'll support it with bb as long as GraalVM supports it
or I'll have to compile with older versions after that, might work for a while
ok, let's see the slow bit
that part takes 100ms on my machine and it also seems laggy since 100ms isn't instant. but 1.5s is quite a difference
Are they dropping support as in it won't work anymore, or just not maintaining it further? If it's the latter it could still work for a little bit longer
Yeah big difference
Probably not worth digging deeper if it's only on my old machine with decaying support
ok, now I found string-width to be the slow bit
no I think preventing lag is good
Enjoy the hunt! š¦
I think I know what's going on, it's doing interop on character by character and this can get very slow in bb
graphemes is the "guilty" one
Nice find! š
yeah when I change:
(defn truncate-line
"Truncate a line to fit within terminal width."
[line width]
line #_(if (or (<= width 0) (<= (w/string-width line) width))
line
(w/truncate line width :tail "")))
(see commented out, I just return line)
then it becomes instantYup, instant on my machine as well
I'm jealous of @timok's AI, his string width calculation code is so much more sophisticated than what my AI did š
On one hand mine added simple regex based ansi stripping, and on another it just added :no-color? true to UI rendering functions and calculates strings twice, once with colors for rendering and once without to calculate widths haha.
We could optimize this for when no character is a special char
The kind of stuff I need to clean up now for release
> We could optimize this for when no character is a special char Sounds good. I'll steal whatever code comes out of this š
ah one quick win is calling this only once:
(Character/getType cp)yeah I'll throw some claude at it too
I gotta run now cya š
nice, I have a faster version now without sacrificing functionality
@timok I'll send a PR :)
or maybe I should first release bb so we can also use it in CI. how does codeberg use CI... does it provide anything?
anyway, I'll send the PR
codeberg has a ci feature that i have to look into... if it is not compelling i'll switch to github
I get a 500 when creating a PR :-s https://codeberg.org/timokramer/charm.clj/compare/master...borkdude/charm.clj:feat/no-spi-with-ffm
Maybe I can e-mail you a patch?
of course
fucking hell, I'm in the middle of some rebase to create a single patch, I don't even remember how this work. can I just copy/paste the changes here?
bb.edn:
{:paths ["src" "test"]
:tasks {test:bb
{:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:task cognitect.test-runner/-main}}}ansi/width.clj:
after these changes, smooth file browser
I had the branch merged with master, but I didn't remember how to create a patch file from this situation:
abcd merge commit
defg my actual commitso I think just copy paste the code and apply it yourself :)
sure lms...
microsoft style engineering haha
github in 2010 :)
can you just quickly tell me what these changes are supposed to do? tried to understand what you talked about above but it's not entirely clear
ah i see the comment for fastpath for babashka
Yes. Iterating over each character and doing interop on each character is slow in bb. This can be avoided by checking if all the characters are normal ASCII
The or is replaced with a set operation which is also faster in bb
And some interop expressions were done multiple times, it is now one let binding
yes, great thanks
Haha the verbal pull request
@timok would you consider adding the bb.edn too?
I'm trying to change some stuff here to optimize string-width with a function from jline which would be much faster if I added it to bb
This is the new implementation:
(if (or (nil? s) (empty? s))
0
(.columnLength (AttributedString/fromAnsi s)))all the other crap can be gone
yes, of course. I pushed to github: https://github.com/TimoKramer/charm.clj
you can open prs there
ah ok
BOOM. Got rid of dozens of lines of code by using 1 Jline class which I just added to bb. https://github.com/TimoKramer/charm.clj/pull/1/changes
This should make TUI in bb with charm.clj even more smooth
can confirm, smooth as hell now that file browser
crazy good. tell me... was this now human intelligence?
š
I suspected that Jline would have something for this
and claude confirmed my suspicion
that's experience. thank you!
Awesome @borkdude!! PRStack's width code is also not perfect and I was unaware of AttributedString/fromAnsi.
When BB gets a release I can also use this to improve PRStack.
Thanks for the great work! š
@timok about Charm, the next challenge I was facing with my TUI library are scroll views and layouts. Did you have any ideas brewing on this topic?
I'm currently considering having some kind of light flexboxy implementation where you can set up either fixed size components or letting things grow to fill the remainder of the screen. But I haven't designed anything specific yet.
Hey, i will have to look into that. Did not yet think about it any further but that's a valid feature.
Allright. The app I'm making needs this so I'll be thinking about this some more.
I'm wondering if you had an intention to the architecture of Charm? I'm making a Reagent-like API. IMO, if you are heading the same route it may be a good idea to put our work together, just let me know. If you want to go another route we'd both make our own thing and offer options to the community.
so i was basically advising claude to copy over williamagh/tui4j or in that regard charmbracelet/bubbletea to clojure. that's in simple terms what i told it to do. in bubbletea it says that it is based on the elm architecture, so probably not exactly reagent-like then.
Ah ok, Iām not super familiar with Elm but looking at Bubbletea it does seem to head in a similar direction
At first glance both ideas seem similar when it comes to declaring the UI as a function of the state. Where a Reagent-like API would not really care how you handle your state as long as it can react to something (r/atom), Bubbletea / Elm seem to also be batteries included for a redux like dispatching system
was my initial impression as well
My personal preference is to follow the Reagent model and focus on a reactive UI only. Offer a simple example to dispatch yourself using multimethods or allow for custom state management libraries a-la re-frame for more complicated apps
For the layout feature, I think we can get some inspiration from https://github.com/charmbracelet/lipgloss
Are there frameworks or libraries to write CLI applications in bb? For example, in Go there are https://github.com/rwxrob/bonzai and https://github.com/spf13/cobra etc
Do you mean command line parsing?
clojure.tools.cli and babashka.cli are built-in
For Bonzai, I have this little personal babaska program that does something similar. You can copy it and make it your own: https://github.com/yannvanhalewyn/cmd/blob/main/src/cmd/commands.clj#L82
It does this
Hmm... But there are not meta-frameworks? It's not only for parsing. But, thinking of it, that's a big part. For example,
var HelpCmd = &bonzai.Cmd{
Name: `help`,
Alias: `h|?`,
Short: `display help information`,
Do: func(x *bonzai.Cmd, args ...string) error {
caller := x.Caller()
if caller != nil && caller != x {
return showHelp(caller)
}
// Fallback: show help for self
return showHelp(x)
},
}
This makes such you call yourscript help will call this "Do" function. What bonzai role would be to organize, what is metadata, help info, man related info etc. in Long tag you could entire sections to compose a man page.
And it also will support automatically the bash-native auto-completion tool (see in screenshot). And some other things too.
Recently, I PR'ed a support for native MCP on Bonzai. You add metadata to these bonzai.Cmd; build binary; and automatically they will be picked up by LLMs as a tool in the mcp server.Wow, supper cool
babashka.cli has support for subcommands
you can hook up a function to one subcommand
I don't think there is a framework that does all the things, might be mistaken. Typically it's pretty easy to make all those things happen by using a few tools together and writing a bit of code yourself, like clojure.tools.cli and babashka.cli. It's a bit more work but it does keep you flexible. I just copy over boilerplate code from project to project, let me see if I can make a gist for you
Thank you! But, it's just curiosity. I often create CLIs. Currently, do Go Bonzai, mostly. Just brainstorming if I should turn more to babashka
there is an open issue for bash completions in babashka.cli, but I didn't even get around to help output for subcommands since I haven't reached a one-size fits all vision on how to do it
Don't lose time with me haha @yannvahalewyn (regarding the gist)
ok well I've found a project where most of these things are implemented, but I'd need to clean it up a bit for a gist. Should you ever want to have a closer look let me know I'll share it
Claude created a mutiplexed-designed bb-mcp for my project. So, I mean. It's already powerful enough, anyways! I have been using it daily.
Spawn a father JVM. And next bb-mcp connections pass commands to the heavyweight. It works as a charm.
But, nice to hear from the horse's mouth about the tool itself.
For sure!
https://github.com/hive-agi/bb-mcp for reference
there is an open issue for bash completions in babashka.cli, but I didn't even get around to help output for subcommands since I haven't reached a one-size fits all vision on how to do it@borkdude I've been doing it using datastructures to declare commands, subcommands, and the flags etc, have some glue code find the correct command, parse the attrs according to those flags and either run the command or generate help from it. Something like:
;; commands/app.clj
(def test
{:command/key "test"
:command/description "Compiles and runs the test builds"
:command/options [[nil "--watch" "Runs a CLJS test watcher in the browser"]]
:command/handler
(fn [options]
(if (:watch options)
(p/check (build/browser-test))
(do (p/check (build/test))
(p/check (p/process! ["node" "target/test/index.js"]
{:inherit true :dir (project/dir :app)})))))})
(def command
{:command/key "app"
:command/description "Build or manage the App"
:command/sub-commands [install dev run debug test build]})
;; commands.clj
(def root-command
{:command/description "A single CLI to run any task for all Pilloxa projects"
:command/options [["-h" "--help" "Show this help message"]]
:command/sub-commands [app/command
ci/command
code/command
console/command]})yeah, babashka.cli has a similar structure, but you can just nest subcommands and the corresponding options
Would run like command app test --watch
This is already close to Bonzai š. Clojure and it's magic
this is what it looks like in neil which has nested subcommands: https://github.com/babashka/neil/blob/8d5ccdbd8185fb2e03bd7de04b3d190e90331709/src/babashka/neil.clj#L886-L946
> This is already close to Bonzai š. Clojure and it's magic The glue code is really 10 lines of code, generating help a bit more but still. This is why I love Clojure, most things you need are just a few expressive lines of code away.
And hence some libraries just, like not existing š
The nice thing about keeping a nested tree is that you can generate help at any layer. command app -h would show the description of the app command, the list of sub commands and their descriptions. Makes it more discoverable, better dev-ux.