babashka 2026-01-28

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?

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)

šŸ™ 1

@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&amp;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! šŸ™‚

šŸŽ‰ 1

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

šŸ‘ 1
🫔 1

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.

šŸ’Æ 1

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.

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 šŸŖ„

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.

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

šŸ‘ 1

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.

šŸ‘ 1

Would need to do something smart about layout changes though

šŸ‘ 1

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

šŸ‘ 1

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

šŸ‘ 1

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:201

you 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

šŸ‘ 1

no I think preventing lag is good

šŸ‘ 1

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 instant

Yup, 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)

šŸ‘ 1

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

šŸ‘ 1

codeberg has a ci feature that i have to look into... if it is not compelling i'll switch to github

Maybe I can e-mail you a patch?

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 commit

so I think just copy paste the code and apply it yourself :)

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

you can open prs there

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.

šŸ‘ 1

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

šŸ‘ 1

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

šŸ‘€ 1

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

šŸ™ 1

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.

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]})

āœ… 1

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 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.

šŸš€ 1