Clime
Declarative command framework for Emacs Lisp. Define commands once — run them as CLI scripts or interactively via clime-invoke. Parsing, types, help, env vars, and dispatch included.
Install / Use
/learn @cosmicz/ClimeREADME
#+title: clime #+options: toc:nil num:nil
clime is a declarative command framework for Emacs Lisp. Define your commands, options, and arguments once with a structured DSL — then surface them however you want: as a CLI script, interactively from Emacs via =clime-invoke=, or (soon) over a network via =clime-serve=.
clime handles the plumbing: two-pass argument parsing with type coercion, env var resolution, and validation, auto-generated help, structured output formats, a polyglot shebang for direct shell execution, and a bundler that produces single-file distributions usable as both standalone CLIs and Emacs libraries.
Here's clime's own build tool, ~clime-make~ (built with clime):
[[file:.github/assets/invoke-menu.png]]
And from the command line — same definition, two surfaces:
[[file:.github/assets/cli-help.png]]
- Getting Started
Write an Elisp file using the clime DSL:
#+begin_src emacs-lisp ;;; greet.el --- A greeting app -- lexical-binding: t; --
;;; Code:
(require 'clime)
(clime-app greet :version "1.0.0" :help "A friendly greeter."
(clime-opt shout ("--shout" "-s") :bool :help "SHOUT THE GREETING")
(clime-command hello :help "Say hello to someone" (clime-arg name :help "Person to greet") (clime-handler (ctx) (clime-let ctx (name shout) (let ((msg (format "Hello, %s!" name))) (if shout (upcase msg) msg)))))
(clime-command goodbye :help "Say goodbye" (clime-arg name :help "Person to bid farewell") (clime-handler (ctx) (clime-let ctx (name) (format "Goodbye, %s!" name)))))
(provide 'greet) ;;; greet.el ends here #+end_src
Now surface it. From the shell (run ~quickstart~ once to add the shebang):
#+begin_src bash path/to/clime-make.el quickstart greet.el
./greet.el hello world # => Hello, world! ./greet.el --shout hello world # => HELLO, WORLD! ./greet.el --help ./greet.el hello --help #+end_src
Or interactively from Emacs:
#+begin_src emacs-lisp (require 'clime-invoke) (clime-invoke greet) #+end_src
- Installation
** Git clone / submodule
Pull the code locally.
#+begin_src bash
Clone directly
git clone https://github.com/cosmicz/clime lib/clime
Or as a submodule
git submodule add https://github.com/cosmicz/clime lib/clime #+end_src
Then set up your app file.
#+begin_src bash lib/clime/clime-make.el quickstart myapp.el #+end_src
This inserts the entrypoint boilerplate, prepends a polyglot shebang, and makes the file executable.
** Download release (single file)
Download ~clime.el~ from the [[https://github.com/cosmicz/clime/releases/latest][latest release]]. The dist is self-executable --- it includes ~clime-make~:
#+begin_src bash curl -Lo clime.el https://github.com/cosmicz/clime/releases/latest/download/clime.el chmod +x clime.el ./clime.el quickstart myapp.el #+end_src
Or use it as a library from Emacs:
#+begin_src emacs-lisp (require 'clime) #+end_src
** package-vc (Emacs 29+)
#+begin_src emacs-lisp (package-vc-install "https://github.com/cosmicz/clime") #+end_src
** Vendoring (build from source)
Build a single-file bundle from a clone:
#+begin_src bash cd clime/ make dist cp dist/clime.el /path/to/myapp/ #+end_src
- Concepts
** App tree
A clime app is a tree of nodes. The root is a ~clime-app~. Interior nodes are ~clime-group~ (containing subcommands or nested groups). Leaves are ~clime-command~ (with a handler). Options and args attach to any node.
#+begin_example clime-app (root) +-- clime-opt (--verbose) app-level option +-- clime-command (install) leaf command | +-- clime-arg (package) | +-- clime-handler +-- clime-group (repo) branch node +-- clime-opt (--registry) group-level option +-- clime-command (add) +-- clime-command (remove) #+end_example
- Surfaces
A clime app is surface-agnostic --- the same definition drives CLI parsing, interactive menus, and (soon) network servers. Each surface translates user input into the values map, validates it through the same pipeline, and calls the handler.
** CLI (~clime-run-batch~)
The default surface. ~clime-run-batch~ parses argv, runs the two-pass pipeline, and calls the handler. See [[*Getting Started][Getting Started]] and [[*Bundling & Distribution][Bundling & Distribution]] for setup.
** Interactive (~clime-invoke~)
Any ~clime-app~ can be invoked interactively from Emacs via ~clime-invoke~, which auto-generates a lightweight ~read-key~ menu from the app tree. No external dependencies.
#+begin_src emacs-lisp (require 'clime-invoke) (clime-invoke my-app) #+end_src
Groups become nested menus (press a key to drill down, ~DEL~ to go back). Options are cycled with ~-X~ (prefix dash + key) or set directly with ~=X~ (completing-read for choices, read-string for counts). Leaf commands show a "Run" action (~RET~) that collects values, calls ~clime-run~, and displays output.
Inline validation highlights invalid parameters as you set them. Prefix keys (~-~ / ~=~) re-render the buffer with dimmed non-option keys for visual feedback.
Use ~:key~ to override auto-assigned keys:
#+begin_src emacs-lisp (clime-command deploy :key "d" :help "Deploy to production" (clime-opt target ("--target" "-t") :key "t" :required :choices '("staging" "production")) (clime-handler (ctx) ...)) #+end_src
~clime-invoke~ returns a structured plist for programmatic use:
#+begin_src emacs-lisp (let ((result (clime-invoke my-app))) (plist-get result :params) ;; final parameter values (plist-get result :exit) ;; handler exit code (nil if user quit) (plist-get result :output)) ;; handler output string (nil if user quit) #+end_src
*** Hot-reload (~clime-reload~)
~clime-reload~ force-reloads all clime modules in dependency order, including ~clime.el~ itself and optional modules (~clime-invoke~, ~clime-make~) if already loaded. Clears and reapplies face specs so ~defface~ changes take effect. Useful during development to pick up changes without restarting Emacs.
- Features
** Commands & Groups
*** Subcommands
Define subcommands with ~clime-command~:
#+begin_src emacs-lisp (clime-command install :help "Install a package" (clime-arg name :help "Package name") (clime-handler (ctx) (clime-let ctx (name) (format "Installing %s" name)))) #+end_src
*** Aliases
Commands can have aliases:
#+begin_src emacs-lisp (clime-command install :help "Install a package" :aliases (i) ...) #+end_src
Now both ~myapp install foo~ and ~myapp i foo~ work. Aliases are symbols or strings.
*** Command aliases for nested paths
Use ~clime-alias-for~ to expose a nested command at a higher level without duplicating its definition:
#+begin_src emacs-lisp (clime-group agents :help "Manage agents" (clime-command start :help "Start an agent" (clime-arg name :help "Agent name") (clime-opt prompt ("--prompt") :help "Initial prompt") (clime-handler (ctx) (do-start ctx))))
(clime-group shortcuts :inline :category "Shortcuts" (clime-alias-for start (agents start) :help "Start an agent (shortcut)")) #+end_src
Now ~myapp start foo~ works identically to ~myapp agents start foo~. The alias copies args, options, and handler from the target at init time. You can override ~:help~, ~:aliases~, ~:hidden~, and ~:category~ on the alias. When ~:help~ is omitted, it inherits from the target.
Use ~:defaults~ to preset option values (user can still override):
#+begin_src emacs-lisp (clime-alias-for show-csv (report show) :help "Show report as CSV" :defaults '((format . "csv"))) #+end_src
Use ~:vals~ to lock option values (removed from CLI and help):
#+begin_src emacs-lisp (clime-alias-for deploy-ci (deploy run) :help "Deploy to CI" :vals '((target . "ci"))) #+end_src
*** Nested groups
Organize related commands under a group:
#+begin_src emacs-lisp (clime-group repo :help "Manage repositories"
(clime-command add :help "Add a repository" (clime-arg name :help "Repository name") (clime-arg url :help "Repository URL") (clime-handler (ctx) (clime-let ctx (name url) (format "Added %s → %s" name url))))
(clime-command remove :help "Remove a repository" :aliases (rm) (clime-arg name :help "Repository name") (clime-handler (ctx) (clime-let ctx (name) (format "Removed %s" name))))) #+end_src
*** Inline groups
Mark a group with ~:inline~ to promote its children to the parent level. Both the group prefix and the short form work:
#+begin_src emacs-lisp (clime-group repo :inline :help "Manage repositories"
(clime-command add :help "Add a repository" (clime-arg name :help "Repository name") (clime-handler (ctx) (clime-let ctx (name) (format "Added %s" name))))
(clime-command remove :help "Remove a repository" (clime-arg name :help "Repository name") (clime-handler (ctx) (clime-let ctx (name) (format "Removed %s" name))))) #+end_src
#+begin_src bash ./myapp.el add foo # works (promoted) ./myapp.el repo add foo # also works (direct) #+end_src
In ~--help~, inline group children appear at the parent level. The group name itself is hidden from the command listing. If a parent has its own child with the same name, the parent's child takes priority.
*** Invocable groups
A group can have its own ~:handler~ that runs when no subcommand is given:
#+begin_src emacs-lisp (clime-group config :help "View or modify configuration"
(clime-handler (_ctx) "Configuration:\n registry = default\n timeout = 30s")
(clime-command set :help "Set a config value" (clime-arg key :help "Config key") (clime-arg value :help "New value") (clime-handler (ctx) (clime-let ctx (key value) (format "Set %s = %s" key value))))) #+end_src
~myapp config~ shows the overview; ~myapp config set key val~ sets a value.
*** Hidden commands and options
Mark a command or option as ~:hidden~ to omit it from ~--help~ while
Related Skills
node-connect
351.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
