From “parse args” to a real command surface (Repl.Core only)
This first demo is intentionally small and dependency-light. It uses Repl.Core only (no DI, no hosting extras) to show the essentials:
- one command graph shared by CLI and REPL,
- help and discovery with zero extra authoring,
- typed parameters and route constraints,
- sensible default rendering (tables vs text),
- and agent-friendly discovery via
--help.
Think of this as the Hello, World of Repl Toolkit: a tiny contacts app that already behaves like a serious tool.
One-shot CLI
$ myapp add "Carla Roy" carla@example.com --json
{ "id": 1, "name": "Carla Roy", "email": "carla@example.com" }
$ myapp show 1 --json
{ "id": 1, "name": "Carla Roy", "email": "carla@example.com" }
Discovery comes for free
$ myapp --help
Commands:
list
add {name} {email}
show {id}
count
Same commands, interactive
$ myapp
Try: list, add Alice alice@test.com, show 1, count
> add "Carla Roy" carla@example.com
Contact 'Carla Roy' added.
> list
Name Email
Carla Roy carla@example.com
> exit
No separate “CLI mode”. No separate “REPL mode”.
Same routes. Same handlers. Same behavior.
This is the same command surface, just executed in different modes.
This sample validates the core contract of Repl Toolkit:
- One command graph authored from delegates
- Dual mode: one-shot CLI and interactive REPL use the same routes
- Route constraints like
{id:int}and{email:email} - Metadata from attributes:
[Description]on handlers → command descriptions in help[Description]on parameters → argument descriptions in help[Browsable(false)]→ hide commands from discovery
- Help and discovery without writing any help text manually
- Reasonable defaults:
- collections render as tables,
- strings render as-is,
- structured data can be emitted as JSON via
--json
This is Repl Toolkit at its smallest: just Repl.Core, no DI, no hosting, no extras.
myapp
├── list
├── add {name} {email}
├── show {id:int}
└── count
- There is no
helpnode in the graph. - There is no
--helpcommand you define. - Help is a framework behavior:
--helpin CLI intercepts execution and renders help.helpin REPL is an ambient command provided by the framework.
- Both render the same model.
using System.ComponentModel;
using Repl;
using Repl.Parameters;
var store = new ContactStore();
var commands = new ContactCommands(store);
var app = CoreReplApp.Create()
.WithDescription("Core basics sample: minimal contacts REPL without DI dependencies.")
.WithBanner("""
Try: list, add Alice alice@test.com, show 1, count
Also: error (exception handling), debug reset
""");
app.Map("list", commands.List);
app.Map("add {name} {email:email}", commands.Add);
app.Map("show {id:int}", commands.Show);
app.Map("count", commands.Count);
app.Map("report period", commands.ReportPeriod);
app.Map("error", ErrorCommand);
app.Map("debug reset", commands.Reset);
return app.Run(args);- No command classes. Routes are mapped directly to delegates.
- Routes define the shape:
add {name} {email},show {id:int}. - Types matter:
{id:int}enforces an integer at parse time (also inferred from parameter type){email}can use anemailconstraint if you enable it.
- Attributes are reused:
[Description]feeds help output[Browsable(false)]hides a command from discovery.
- Return values are semantic:
IEnumerable<Contact>→ tableContact→ structured output (or JSON with--json)string→ plain text.
An automated client can do this:
$ myapp --help
Commands:
list
add {name} {email}
show {id}
count
$ myapp add --help
Usage: add {name} {email}
Arguments:
name Full name
email Email address
From this alone, an agent can:
- discover the available commands,
- understand required arguments,
- and call them with
--jsonto get structured results.
No screen scraping. No custom schema. No extra endpoints.
This sample also demonstrates two advanced parameter features:
- reusable options groups via
[ReplOptionsGroup] - date-only temporal ranges via
ReplDateRange
Try these commands:
myapp list --format json
myapp show 1 --no-verbose
myapp report period --period 2024-01-15..2024-02-15
myapp report period --period 2024-01-15@30d
Expected behavior:
--formatand--no-verboseare provided by a shared options-group object.report periodacceptsstart..endandstart@duration.ReplDateRangeaccepts whole-day durations only.
Validation example:
$ myapp report period --period 2024-01-15@8h
Validation: '2024-01-15@8h' is not a valid date range literal. Use start..end or start@duration with whole days.
- This sample uses an in-memory store.
Each CLI invocation is a new process, so sequences in the CLI examples are a narrative shortcut. - The
debug resetcommand exists for demos/tests but is hidden from help via[Browsable(false)]. - This demo does not use:
- dynamic scoping,
- completion providers,
- middleware,
- named options,
- or custom output formats.
Those come next.
Now that you’ve seen:
- routes,
- constraints,
- help/discovery,
- and dual CLI/REPL execution,
the next demo introduces scopes and navigation:
👉 02 — Scoped Contacts: enter contexts, navigate with .., and see how the command graph becomes stateful without turning into a shell you have to invent yourself.