ExNounVerbCli

A generator-first, dispatch-agnostic noun-verb CLI core for Elixir. Ports the useful parts of the Rust clap-noun-verb framework's ergonomics — compile-time-registered noun/verb dispatch, a declarative arg schema, JSON-by-default output, command chaining/stdin extraction — without the linkme/proc-macro machinery: registration is a generated explicit registry module by default (a real BEAM reflection-based fallback exists too, see the matrix below).

Full design charter: docs/superpowers/specs/2026-09-11-ex-noun-verb-cli-design.md.

Installation

def deps do
[
{:ex_noun_verb_cli, "~> 26.9"}
]
end

Quick Start

The toy calc CLI under examples/calc/ proves the core end-to-end, mirroring clap-noun-verb's own README quickstart (calc add/calc multiply). A verb is a noun/verb pair bound to a module/function plus an Igniter.Mix.Task.Info-shaped arg schema:

defmodule ExNounVerbCli.Examples.Calc.Registry do
use ExNounVerbCli.Registry.Generated,
verbs: [
[
noun: "calc",
verb: "add",
module: ExNounVerbCli.Examples.Calc,
function: :add,
info: %{schema: [x: :integer, y: :integer], required: [:x, :y]},
doc: "Adds --x and --y."
],
[
noun: "calc",
verb: "multiply",
module: ExNounVerbCli.Examples.Calc,
function: :multiply,
info: %{schema: [x: :integer, y: :integer], required: [:x, :y]},
doc: "Multiplies --x and --y."
]
]
end

Dispatching runs the pipeline directly, in-process, with no adapter needed — this is exactly what ExNounVerbCli.Escript.run/2 does under the hood:

iex> ExNounVerbCli.Dispatcher.dispatch(
...> ExNounVerbCli.Examples.Calc.Registry,
...> ["calc", "add", "--x", "2", "--y", "3"]
...> )
{:ok, 5}
iex> ExNounVerbCli.JsonOutput.encode_string(
...> ExNounVerbCli.JsonOutput.encode(:ok, 5)
...> )
"{\"status\":\"ok\",\"result\":5}"

As a single-binary escript

# config/config.exs (or wherever the escript reads config from)
config :ex_noun_verb_cli, registry: ExNounVerbCli.Examples.Calc.Registry
$ mix escript.build
$ ./ex_noun_verb_cli calc add --x 2 --y 3
{"status":"ok","result":5}
$ echo $?
0

Command chaining

Multiple noun/verb groups separated by ++ run in sequence; the JSON result is an array, and the overall exit status is :error if any group errored:

$ ./ex_noun_verb_cli calc add --x 2 --y 3 ++ calc multiply --x 4 --y 5
[{"status":"ok","result":5},{"status":"ok","result":20}]

As an Igniter.Mix.Task

Requires :igniter as a dependency of the consuming application (the adapter module is compiled only when Igniter is present):

{:igniter, "~> 0.5"}
$ mix ex_noun_verb_cli calc add --x 2 --y 3

folds the same dispatch result into the Igniter.t() being built, since an Igniter task composes rather than prints-and-exits — see lib/mix/tasks/ex_noun_verb_cli.ex.

Feature matrix

What's implemented in this v1 proof-of-concept vs. deferred per the design spec's Section 6 non-goals:

CapabilityStatus
Noun/verb dispatch (Dispatcher.dispatch/2)Implemented
Declarative arg schema (Igniter.Mix.Task.Info-shaped)Implemented
JSON-by-default output (JsonOutput)Implemented
Command chaining (++)Implemented
Cross-group value threading (@{N.path} into a prior group's envelope)Implemented, v26.9.15
--introspect LLM self-description (machine-readable verb manifest)Implemented, v26.9.15
Stdin/file extraction (@- / @-::json.path)Implemented
Generated explicit registry (Registry.Generated)Implemented, v1-primary
Reflection-based registry (Registry.Reflection)Implemented, v2 opt-in convenience
Escript adapterImplemented
Igniter.Mix.Task adapterImplemented
Capability standing / RDF graph ops (Capability, GraphProvider behaviour)Implemented (decoupled — no oxigraph/ggen_igniter dependency in this library; a consumer supplies the GraphProvider implementation)
Toy calc example CLI (examples/calc/)Implemented
Real ggen-marketplace consumer (marketplace_cli.catalog/validate)Implemented consumer-side — ggen-marketplace's packages/marketplace-cli (Milestone 2)
Dynamic shell completions (--completions bash|zsh|fish)Implemented, v26.9.16
Human help (--help [<noun>]) as a manifest projectionImplemented, v26.9.16
Inline @{N.path} splicing inside larger tokensImplemented, v26.9.16
Full clap-noun-verb parity (semantic composition, economic simulation, quantum-ready crypto-agility, etc.)Out of scope for this port's charter
Real Hex.pm publishv26.9.15 — first release published to Hex.pm (mix hex.publish --dry-run verified)

Testing

Chicago-style throughout: real collaborators (a genuine, simple Registry fixture — not a mock; a real in-memory GraphProvider fixture; a real System.cmd subprocess for the escript adapter; Igniter.Test for the Igniter adapter), state-based assertions. No Mox/Mimic/:meck/Patch anywhere in this repo.

mix test

Documentation

Diataxis doc set for v26.9.16 (the same documentation convention the ggen-marketplace ecosystem uses):

Also: CHANGELOG.md, the design spec (with its v26.9.15 release amendment), and the full API on HexDocs.