SkillAgentSearch skills...

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/Clime
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

#+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

View on GitHub
GitHub Stars28
CategoryDevelopment
Updated2h ago
Forks0

Languages

Emacs Lisp

Security Score

90/100

Audited on Apr 8, 2026

No findings