Claude-skill-registry clojure-babashka-cli
Turn Clojure functions into CLIs with babashka.cli. Use when working with command-line argument parsing, building CLIs, subcommand dispatching, option validation, or creating tools with babashka/clojure.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clojure-babashka-cli" ~/.claude/skills/majiayu000-claude-skill-registry-clojure-babashka-cli && rm -rf "$T"
manifest:
skills/data/clojure-babashka-cli/SKILL.mdsource content
babashka.cli
Turn Clojure functions into CLIs with minimal effort. Supports both
:foo value and --foo value style arguments, automatic type coercion, validation, subcommands, and help generation.
Setup
deps.edn:
org.babashka/cli {:mvn/version "0.8.60"}
Leiningen:
[org.babashka/cli "0.8.60"]
Built into babashka - no dependency needed in bb.edn.
See https://clojars.org/org.babashka/cli for the latest version.
Quick Start
#!/usr/bin/env bb (require '[babashka.cli :as cli]) (defn greet [{:keys [name verbose]}] (when verbose (println "Greeting...")) (println "Hello" name)) ;; Simple parsing (cli/parse-opts ["--name" "Alice" "--verbose"] {:coerce {:verbose :boolean}}) ;;=> {:name "Alice", :verbose true} ;; Call function directly with parsed args (greet (cli/parse-opts *command-line-args* {:coerce {:verbose :boolean}}))
Core Patterns
Parse Options
parse-opts - returns a map, leaves extra args in metadata:
(cli/parse-opts ["--port" "8080" "--host" "0.0.0.0"] {:coerce {:port :long}}) ;;=> {:port 8080, :host "0.0.0.0"}
parse-args - separates options from positional arguments:
(cli/parse-args ["--force" "file.txt"] {:coerce {:force :boolean}}) ;;=> {:opts {:force true}, :args ["file.txt"]}
Type Coercion
;; Keyword coercion types {:coerce {:port :long ; parse-long :ratio :double ; parse-double :flag :boolean ; true/false :name :string ; stays string :kw :keyword ; keyword :sym :symbol}} ; symbol ;; Auto-coercion (enabled by default since v0.3.35) (cli/parse-opts ["--port" "8080"]) ;;=> {:port 8080} (cli/parse-opts ["--flag"]) ;;=> {:flag true} (cli/parse-opts ["--key" ":value"]) ;;=> {:key :value} ;; Collection coercion {:coerce {:paths []}} ; collect into vector (cli/parse-opts ["--paths" "src" "--paths" "test"] {:coerce {:paths []}}) ;;=> {:paths ["src" "test"]} {:coerce {:tags [:keyword]}} ; vector of keywords (cli/parse-opts ["--tags" "foo" "bar"] {:coerce {:tags [:keyword]}}) ;;=> {:tags [:foo :bar]}
Aliases (Short Options)
(cli/parse-opts ["-p" "8080" "-v"] {:alias {:p :port, :v :verbose} :coerce {:port :long, :verbose :boolean}}) ;;=> {:port 8080, :verbose true} ;; Combined short flags (since v0.7.51) (cli/parse-opts ["-abc"]) ;;=> {:a true, :b true, :c true}
Positional Arguments
Use
:args->opts to map positional arguments to option names:
;; Fixed number of args (cli/parse-opts ["alice" "30"] {:args->opts [:name :age] :coerce {:age :long}}) ;;=> {:name "alice", :age 30} ;; Variable args - first goes to :user, rest to :files (cli/parse-opts ["alice" "a.txt" "b.txt"] {:args->opts (cons :user (repeat :files)) :coerce {:files []}}) ;;=> {:user "alice", :files ["a.txt" "b.txt"]}
Validation and Requirements
(def spec {:port {:coerce :long :validate pos? :require true :desc "Server port"} :dir {:validate #(fs/directory? %) :desc "Working directory"}}) ;; Validate with custom message {:host {:validate {:pred #(re-matches #"[a-z0-9.-]+" %) :ex-msg (fn [m] (str "Invalid host: " (:value m)))}}} ;; Require specific options (cli/parse-opts [] {:require [:port]}) ;;=> Throws: Required option: :port ;; Restrict to known options only (cli/parse-opts ["--port" "8080" "--unknown" "x"] {:spec {:port {}} :restrict true}) ;;=> Throws: Unknown option: :unknown
Default Values
(cli/parse-opts ["--name" "Alice"] {:exec-args {:port 8080, :host "localhost"}}) ;;=> {:name "Alice", :port 8080, :host "localhost"} ;; Command-line args override defaults (cli/parse-opts ["--port" "9000"] {:exec-args {:port 8080}}) ;;=> {:port 9000}
Subcommands
Use
dispatch for subcommand routing:
(defn copy-cmd [{:keys [opts]}] (println "Copying" (:file opts))) (defn delete-cmd [{:keys [opts]}] (println "Deleting" (:file opts) "depth:" (:depth opts))) (def table [{:cmds ["copy"] :fn copy-cmd :args->opts [:file]} {:cmds ["delete"] :fn delete-cmd :args->opts [:file] :coerce {:depth :long}} {:cmds [] :fn (fn [_] (println "Unknown command"))}]) (cli/dispatch table ["copy" "foo.txt"]) ;;=> Copying foo.txt (cli/dispatch table ["delete" "bar.txt" "--depth" "3"]) ;;=> Deleting bar.txt depth: 3
Shared options across subcommands (since v0.8.54):
(def table [{:cmds [] :spec {:verbose {:coerce :boolean}}} ; global option {:cmds ["build"] :fn build-cmd :spec {:target {:coerce :keyword}}} {:cmds ["test"] :fn test-cmd}]) (cli/dispatch table ["--verbose" "build" "--target" "prod"]) ;;=> {:dispatch ["build"], :opts {:verbose true, :target :prod}}
Help Generation
Use
format-opts to generate help text:
(def spec {:port {:desc "Server port" :ref "<port>" :default 8080 :coerce :long :alias :p} :host {:desc "Server host" :ref "<host>" :default-desc "localhost" :alias :h} :verbose {:desc "Enable verbose logging" :alias :v}}) (println (cli/format-opts {:spec spec :order [:port :host :verbose]})) ;; Prints: ;; -p, --port <port> 8080 Server port ;; -h, --host <host> localhost Server host ;; -v, --verbose Enable verbose logging
Error Handling
Custom error handler:
(cli/parse-opts [] {:spec {:port {:require true :desc "Server port"}} :error-fn (fn [{:keys [type cause msg option]}] (when (= :org.babashka/cli type) (case cause :require (println "Missing required:" option) :validate (println "Invalid value:" msg) :coerce (println "Cannot parse:" msg) :restrict (println "Unknown option:" option)) (System/exit 1)))})
Common CLI Patterns
Complete CLI with Help
(def cli-spec {:spec {:port {:coerce :long :desc "Server port" :default 8080 :alias :p} :verbose {:coerce :boolean :desc "Verbose output" :alias :v} :help {:coerce :boolean :desc "Show help" :alias :h}}}) (defn show-help [] (println "Usage: server [options]") (println (cli/format-opts cli-spec))) (defn -main [& args] (let [opts (cli/parse-opts args cli-spec)] (if (:help opts) (show-help) (start-server opts))))
Building Tasks for Babashka
In bb.edn:
{:tasks {build {:doc "Build the project" :task (let [opts (cli/parse-opts *command-line-args* {:coerce {:clean :boolean}})] (when (:clean opts) (clean)) (compile-project opts))}}}
Usage:
bb build --clean
Clojure CLI Integration
Call any function as a CLI without wrapper code. In deps.edn:
{:aliases {:exec {:extra-deps {org.babashka/cli {:mvn/version "0.8.60"}} :main-opts ["-m" "babashka.cli.exec"]}}}
Usage:
;; Call any function with map arg clojure -M:exec my-ns/my-fn --port 8080 --verbose ;; Add metadata to function for coercion (defn my-fn {:org.babashka/cli {:coerce {:port :long}}} [{:keys [port verbose]}] ...)
Key Gotchas
-
Options are open by default - extra options don't cause errors. Use
to enforce known options only.:restrict true -
Without
info, ambiguous args default to strings::coerce
(cli/parse-args ["--port" "8080"]) ;;=> {:port "8080"} (string!)
- Boolean flags need no value - just their presence:
(cli/parse-opts ["--verbose"]) ;;=> {:verbose true}
- Negative flags work automatically (since v0.7.51):
(cli/parse-opts ["--no-colors"]) ;;=> {:colors false}
- Use
to separate options from arguments when ambiguous:--
(cli/parse-args ["--paths" "src" "test" "--" "file.txt"] {:coerce {:paths []}}) ;;=> {:opts {:paths ["src" "test"]}, :args ["file.txt"]}
- The library is still evolving - check CHANGELOG for breaking changes before upgrading.
References
- Full API Reference - Complete function signatures and documentation
- GitHub README - Comprehensive examples and guides
- Babashka Book CLI Chapter - Integration with babashka tasks