Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
10d1a5d
Add proof of concept for Json Schema types and JSON input format for
dillonkearns Mar 12, 2026
8466793
Separate out typed schema API to separate module to prevent breaking
dillonkearns Mar 12, 2026
8f6689e
Fix Ci.
dillonkearns Mar 12, 2026
611ebf0
Migrate to two separate modules for elm-ts-json decoder API vs. standard
dillonkearns Mar 13, 2026
d663957
Ignore docs.json.
dillonkearns Mar 13, 2026
43b3c29
Use structured $cli object format for JSON schema and input.
dillonkearns Mar 13, 2026
797e292
Add contains constraint for expectFlags.
dillonkearns Mar 13, 2026
389ef52
Change format of flags to be booleans to be more intuitive for agents to
dillonkearns Mar 13, 2026
b3ee66d
Add usage synopsis to JSON schema description field.
dillonkearns Mar 13, 2026
ad688aa
Add string type to anyOf schema.
dillonkearns Mar 13, 2026
a93706c
Put all values under $cli to make it more unified in JSON schema.
dillonkearns Mar 13, 2026
dcda7b3
Nest keyword args and subcommand inside $cli object.
dillonkearns Mar 13, 2026
f9fa208
Extract common code between options APIs, and remove redundant
dillonkearns Mar 13, 2026
71cbd58
Improve docs.
dillonkearns Mar 14, 2026
d5f0c01
Use flat JSON structure to simplify JSON invocation. Add x-cli-kind
dillonkearns Mar 14, 2026
ff06933
Formatting.
dillonkearns Mar 14, 2026
eab2fae
Update elm-ts-json.
dillonkearns Mar 14, 2026
5347ea7
Improve docs.
dillonkearns Mar 14, 2026
d1d8da1
Update readme.
dillonkearns Mar 14, 2026
9fc5dec
Add typed options example, update changelog and readme.
dillonkearns Mar 14, 2026
4a82754
Export all key parts of API for Typed varation so you only need to im…
dillonkearns Mar 14, 2026
c9ba9d2
Update docs.
dillonkearns Mar 14, 2026
81eaa0e
Update docs.
dillonkearns Mar 14, 2026
a831c7a
Update docs.
dillonkearns Mar 14, 2026
7927803
Rename function.
dillonkearns Mar 14, 2026
216d7c7
Remove the bool CLI optiosn decoder, it isn't necessary because we have
dillonkearns Mar 14, 2026
786e094
Update docs.
dillonkearns Mar 14, 2026
d11faeb
Pull in relevant docs from Cli.Option for Typed version.
dillonkearns Mar 14, 2026
d4a2105
Mark positional property as required in json schema.
dillonkearns Mar 14, 2026
fca70b6
Add additionalProperties: false in schema to make it clear that
dillonkearns Mar 14, 2026
f3e2c78
Reject unknown fields in JSON mode to give clear error feedback (mirrors
dillonkearns Mar 14, 2026
5323cf3
Show decoding errors at the initial failure level for clarity (like
dillonkearns Mar 14, 2026
d301386
Use draft 07 json schema.
dillonkearns Mar 14, 2026
4328d2f
Move some internal details. Results in major change because a type ch…
dillonkearns Mar 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Distribution
build/
documentation.json
docs.json
*/elm.js
**/*.elm.js
*graphqelm-metadata.json
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- **`Cli.Option.Typed` module** — new option constructors that take a `CliDecoder`
for typed CLI parsing and JSON schema generation. Includes `string`, `int`,
`float`, and `customDecoder` for custom types.
- **`Program.toJsonSchema`** — generates a [JSON Schema](https://json-schema.org/)
from your CLI configuration, suitable for
[MCP tool](https://modelcontextprotocol.io/specification/draft/server/tools)
`inputSchema` definitions and
[elm-pages script](https://elm-pages.com/docs/elm-pages-scripts) introspection.
- **JSON input mode** — parsers accept structured JSON in addition to traditional
CLI arguments, enabling LLM agents to invoke tools programmatically. The `$cli`
object serves as the sentinel, containing positional arguments and subcommand.
- `x-cli-kind` annotations in JSON schema output (`"keyword"`, `"flag"`,
`"keyword-list"`) describing how each option maps to CLI invocation.
- Schema `description` field includes usage synopsis and invocation instructions.
- `Option.withDisplayName` for custom metavar display (e.g., `--output-dir <PATH>`).
- `TypedGreet` example demonstrating the typed options API.

### Changed

- New dependency on `dillonkearns/elm-ts-json` (>= 2.1.2).
- Improved help text formatting: uppercase metavar names, 80-character line
wrapping, description indentation.

## [4.0.0]

See the [V4 Upgrade Guide](V4-UPGRADE-GUIDE.md) for migration instructions.
Expand Down
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Elm CLI Options Parser

`elm-cli-options-parser` allows you to build command-line options parsers in Elm.
It uses a syntax similar to `Json.Decode.Pipeline`.
It uses a syntax similar to `Json.Decode.Pipeline`, with automatic help text
generation, validation, and [JSON Schema](https://json-schema.org/) output
for [MCP tool](https://modelcontextprotocol.io/specification/draft/server/tools)
definitions and [elm-pages script](https://elm-pages.com/docs/elm-pages-scripts)
introspection.

You can
play around with `elm-cli-options-parser` in a [live terminal simulation in Ellie here](https://ellie-app.com/8b8QWfcxx4Ca1)!
Expand Down Expand Up @@ -95,6 +99,68 @@ git log [--author <author>] [--max-count <max-count>] [--stat] [<revision range>

Note: the `--help` option is a built-in command, so no need to write a `OptionsParser` for that.

## Typed Options & JSON Schema

The [`Cli.Option.Typed`](https://package.elm-lang.org/packages/dillonkearns/elm-cli-options-parser/4.0.0/Cli-Option-Typed/)
module lets you specify the type of each option (string, int, float, etc.) via a
`CliDecoder`. This gives you:

- **JSON Schema generation** via `Program.toJsonSchema` — for MCP tool definitions,
elm-pages script introspection, or any tooling that needs a machine-readable
description of your CLI's inputs
- **Typed JSON input** — the same parser handles both traditional CLI args and
structured JSON, so LLM agents can invoke your tool programmatically
- **CLI validation** — typed decoders like `int` and `float` automatically reject
malformed input

```elm
import Cli.Option.Typed as Option
import Cli.OptionsParser as OptionsParser exposing (with)
import Cli.Program as Program

type alias Options =
{ name : String
, count : Int
, verbose : Bool
}

programConfig : Program.Config Options
programConfig =
Program.config
|> Program.add
(OptionsParser.build Options
|> with (Option.requiredKeywordArg "name" Option.string)
|> with (Option.requiredKeywordArg "count" Option.int)
|> with (Option.flag "verbose")
)
```

This parser works with traditional CLI args:

```console
$ mytool --name hello --count 3 --verbose
```

And also accepts JSON input (for tool-calling agents):

```json
{ "name": "hello", "count": 3, "verbose": true, "$cli": {} }
```

`Program.toJsonSchema "mytool" programConfig` generates a JSON Schema with
proper types (`"type": "string"`, `"type": "integer"`, etc.) and `x-cli-kind`
annotations that describe how each option maps to CLI flags.

### When to use `Cli.Option` vs `Cli.Option.Typed`

Use **`Cli.Option.Typed`** when you want JSON schema generation or JSON input support.

Use **`Cli.Option`** (the original API shown in the example above) when you only need
traditional CLI argument parsing. It's simpler — no decoder argument needed — and
treats all values as strings, which you then transform with `validateMap`, `map`, etc.

Both modules produce the same `Option` type and work with the same `OptionsParser.with` pipeline.

## Color Support

The library automatically adds ANSI color codes to help text and error messages when enabled. To enable colors, pass `colorMode: true` in your flags from JavaScript:
Expand Down
1 change: 0 additions & 1 deletion docs.json

This file was deleted.

7 changes: 5 additions & 2 deletions elm.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
{
"type": "package",
"name": "dillonkearns/elm-cli-options-parser",
"summary": "Type-safe command line options parsing.",
"summary": "Type-safe command line options parsing with JSON schema generation.",
"license": "BSD-3-Clause",
"version": "4.0.0",
"exposed-modules": [
"Cli.Program",
"Cli.Option",
"Cli.OptionsParser",
"Cli.Validate",
"Cli.OptionsParser.BuilderState"
"Cli.OptionsParser.BuilderState",
"Cli.Option.Typed"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"dillonkearns/elm-ts-json": "2.1.2 <= v < 3.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/json": "1.1.3 <= v < 2.0.0",
"elm/regex": "1.0.0 <= v < 2.0.0",
"elmcraft/core-extra": "2.2.0 <= v < 3.0.0",
"wolfadex/elm-ansi": "3.0.0 <= v < 4.0.0"
Expand Down
8 changes: 5 additions & 3 deletions examples/elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"dillonkearns/elm-ts-json": "2.1.2",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.1",
"elm/http": "1.0.0",
"elm/json": "1.1.4",
"elm/regex": "1.0.0",
"elm-community/list-extra": "8.3.1",
"elmcraft/core-extra": "2.3.0",
"wolfadex/elm-ansi": "3.0.1"
},
"indirect": {
"avh4/elm-color": "1.0.0",
"elm/json": "1.1.4",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.5"
"elm/virtual-dom": "1.0.5",
"elm-community/dict-extra": "2.4.0"
}
},
"test-dependencies": {
Expand Down
69 changes: 69 additions & 0 deletions examples/src/TypedGreet.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module TypedGreet exposing (main)

{-| A simple example using `Cli.Option.Typed` for typed options with JSON schema support.

This is the typed equivalent of the `Simple.elm` example. The key difference is
that each option specifies its type via a `CliDecoder`, enabling JSON schema
generation via `Program.toJsonSchema`.

Try it:

node -e "require('./create-cli')('TypedGreet')" -- --name "World"

node -e "require('./create-cli')('TypedGreet')" -- --name "World" --greeting "Hi" --times 3

Or with JSON input:

node -e "require('./create-cli')('TypedGreet')" -- '{"name": "World", "times": 3, "$cli": {}}'

-}

import Cli.Option.Typed as Option
import Cli.OptionsParser as OptionsParser
import Cli.Program as Program
import Ports


type alias GreetOptions =
{ name : String
, greeting : String
, times : Int
}


programConfig : Program.Config GreetOptions
programConfig =
Program.config
|> Program.add
(OptionsParser.build GreetOptions
|> OptionsParser.with (Option.requiredKeywordArg "name" Option.string)
|> OptionsParser.with
(Option.optionalKeywordArg "greeting" Option.string
|> Option.withDefault "Hello"
)
|> OptionsParser.with
(Option.optionalKeywordArg "times" Option.int
|> Option.withDefault 1
)
)


init : Flags -> GreetOptions -> Cmd Never
init flags { name, greeting, times } =
List.repeat times (greeting ++ " " ++ name ++ "!")
|> String.join "\n"
|> Ports.print


type alias Flags =
Program.FlagsIncludingArgv {}


main : Program.StatelessProgram Never {}
main =
Program.stateless
{ printAndExitFailure = Ports.printAndExitFailure
, printAndExitSuccess = Ports.printAndExitSuccess
, init = init
, config = programConfig
}
3 changes: 3 additions & 0 deletions snapshot-tests/elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"direct": {
"dillonkearns/elm-pages": "10.3.0",
"dillonkearns/elm-snapshot": "1.0.0",
"dillonkearns/elm-ts-json": "2.1.1",
"elm/core": "1.0.5",
"elm/json": "1.1.4",
"elm/regex": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-explorations/test": "2.2.1",
"kraklin/elm-debug-parser": "2.0.0",
"lue-bird/elm-syntax-format": "1.1.14",
"miniBill/elm-diff": "1.1.0",
Expand All @@ -38,6 +40,7 @@
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.5",
"elm-community/basics-extra": "4.1.0",
"elm-community/dict-extra": "2.4.0",
"elm-community/maybe-extra": "5.3.0",
"elmcraft/core-extra": "2.2.0",
"fredcy/elm-parseint": "2.0.1",
Expand Down
Loading
Loading