# Command Routes Source: https://runegraft.codesft.dev/concepts/command-routes Define CLI routes with typed arguments, friendly errors, and clean parsing. Runegraft uses **route-style strings** to define commands, similar to Flask or FastAPI but for CLIs. Routes stay readable, and they double as your help text. ## Defining routes Use `@cli.command("")` with typed parameters that match the placeholders in your pattern: ```python theme={null} from runegraft import CLI, opt cli = CLI("demo") @cli.command("install ") def install(url: str, force: bool = opt("--force", "-f", default=False, help="Overwrite existing install")): print(f"Installing from {url} (force={force})") @cli.command("config set ") def set_config(key: str, value: str): print(f"Saved {key}={value}") @cli.command("config get ") def get_config(key: str): print(f"Fetching {key}...") ``` The `` placeholders describe both the argument type and the name passed into your function. Missing or malformed args produce clear, typed errors automatically. ## Typing and validation * Built-in converters include `str`, `int`, `bool`, `float`, `url`, `uuid`, and `json`. * Convert user input before your function runs, so you only handle validated Python types. * Custom converters are easy—see [Type Converters](/concepts/types). ## Root vs. subcommands * `@cli.root` handles `runegraft` with no args (perfect for launching the shell). * Routes can nest to mimic subcommands: `config set` and `config get` share the `config` prefix but map to different functions. ## Helpful output for free * Help text is generated from your route strings and docstrings. * Missing/extra argument errors are formatted automatically and show expected types and options. * Combine with [Options & Flags](/concepts/options) to offer defaults, choices, and aliases without hand-writing parsers. # Options & Flags Source: https://runegraft.codesft.dev/concepts/options Add polished flags with defaults, choices, and help text without writing a parser. Options in Runegraft are declared with the `opt` helper. They stay close to your function signatures, so type hints and defaults map directly to your CLI help text. ## Adding options ```python theme={null} from runegraft import CLI, opt cli = CLI("demo") @cli.command("deploy ") def deploy( env: str, dry_run: bool = opt("--dry-run", "-n", default=False, help="Print actions without executing"), region: str = opt("--region", "-r", default="us-east-1", help="Deployment region"), retries: int = opt("--retries", default=2, help="Retry failed steps"), ): ... ``` * Short and long flags are defined together: `opt("--dry-run", "-n", default=False, ...)`. * Defaults show up in generated help output automatically. * Type hints (`bool`, `str`, `int`, etc.) are enforced and reflected in the usage string. ## Choices and validation Provide a set of allowed values to restrict input: ```python theme={null} @cli.command("publish") def publish( channel: str = opt("--channel", choices=["alpha", "beta", "stable"], default="beta", help="Release channel"), notes: str = opt("--notes", help="Optional release notes"), ): ... ``` When a user passes an invalid value, Runegraft prints a clear error and shows the valid choices. ## Pairing with routes Options layer cleanly on top of your route patterns. Keep positional arguments in the route (e.g., ``) and reserve options for optional behavior. Combine options with [Type Converters](/concepts/types) to accept richer input like URLs or JSON blobs. # Interactive Shell Source: https://runegraft.codesft.dev/concepts/shell Runegraft ships with a polished REPL for your CLI—completion, history, aliases, and scripts included. Running `runegraft` with no args drops you into a shell that feels like a real tool, not a demo prompt. ## Why use the shell? * **Tab + fuzzy completion** for commands, subcommands, and options. * **Persistent history** so your shortcuts stick between sessions. * **Built-ins** you expect: `help`, `exit`, `history`, `alias`, `source`, `cd`, `pwd`, and `!` for running system commands. * **Scripts via `source`** to automate repetitive workflows. ## Typical session ```bash interactive theme={null} $ runegraft runegraft> help runegraft> install https://example.com/pkg.zip -f 3 runegraft> alias set i "install -f 3" runegraft> i https://example.com/pkg.zip runegraft> history show runegraft> exit ``` Aliases are great for long option sets. Pair them with `source ./team-setup.rg` to script team defaults. ## Making your shell the default If you want `runegraft` to open the shell when no arguments are provided, wire your root handler to return `cli.shell()`: ```python theme={null} from runegraft import CLI cli = CLI("runegraft") @cli.root def _root(): return cli.shell() ``` ## Troubleshooting commands Use `history show` to replay recent commands, and tap `!` to run system commands without leaving the REPL. The shell echoes validation errors with the same helpful formatting as one-shot mode, so you can iterate quickly. # Type Converters Source: https://runegraft.codesft.dev/concepts/types Validate and convert arguments before your command logic runs. Runegraft converts user input into typed Python objects automatically. Route placeholders and option annotations describe what the user should pass and ensure the CLI fails fast with helpful errors. ## Built-in converters You can reference these in route patterns (e.g., ``) or via type hints: * `str`, `int`, `float`, `bool` * `url` for HTTP/HTTPS URLs * `uuid` for UUID strings * `json` for JSON objects/arrays (parsed into Python types) ## Custom converters Define your own converter when you need stricter rules or domain-specific validation: ```python theme={null} from runegraft import CLI, opt, ConverterError cli = CLI("demo") def port(value: str) -> int: try: parsed = int(value) except ValueError as exc: raise ConverterError("Port must be an integer") from exc if not (1 <= parsed <= 65535): raise ConverterError("Port must be between 1 and 65535") return parsed @cli.command("serve ") def serve( port: int, host: str = opt("--host", default="127.0.0.1"), ): print(f"Serving on {host}:{port}") ``` Attach your converter to a placeholder (``) and annotate the parameter to keep type checkers happy. ## Validation experience * Errors surface before your function runs, with messages that include the expected type. * Help text shows converter names so users know what each argument expects. * Combine converters with [Options & Flags](/concepts/options) to accept structured input everywhere. # Runegraft Source: https://runegraft.codesft.dev/index The most customizable, beautiful, simple CLI framework. Build CLIs the way you *wish* every CLI was built: clean command routes, strong typing, an interactive shell out of the box, and quality-of-life features that make your tool feel like a real product. Run your tool with **no args** to drop into Runegraft’s interactive shell. You still get the normal “one-shot CLI” behavior when you pass a command. Install, create your first CLI, and ship a polished entrypoint. Flask-like routes for CLIs: install \, types, and defaults. Completion, history, aliases, source scripts, and more. Typed options with nice help text, short/long forms, and defaults. Built-ins like int, bool, url, json, uuid, and custom converters. Real-world patterns: installers, git-like subcommands, and task runners. *** ## Install in one command ```bash theme={null} pip install runegraft ``` Prefer editable installs while developing your CLI? Clone the repo and run pip install -e .. ## Sneak peek ```bash normal-cli theme={null} $ runegraft install https://example.com/pkg.zip --optional-flag 3 Installing from https://example.com/pkg.zip (optional_flag=3) ``` ```bash interactive-shell theme={null} $ runegraft runegraft> help runegraft> install https://example.com/pkg.zip -f 3 runegraft> alias set i "install -f 3" runegraft> i https://example.com/pkg.zip runegraft> history show runegraft> exit ``` ## Where to start Install Runegraft and ship your first command in minutes in the Quickstart. See how Flask-like routes power typed arguments in Command Routes. Explore completion, history, and scripts in Interactive Shell. Add flags, converters, and real-world patterns in Options & Flags and Type Converters. Grab ready-to-use snippets in Recipes. # Quickstart Source: https://runegraft.codesft.dev/quickstart Install Runegraft and build your first command in minutes. ## Install Runegraft Runegraft ships as a normal Python package. Pick the install path that fits your workflow. ```bash pip theme={null} pip install runegraft ``` ```bash from-source theme={null} git clone https://github.com/YOUR_ORG/runegraft cd runegraft pip install -e . ``` Run python -m runegraft with no args to drop into the interactive shell immediately. ## Build a CLI in 60 seconds Drop this into `cli.py` (or any module you prefer) to see the basics: ```python theme={null} from runegraft import CLI, opt cli = CLI("runegraft") @cli.root def _root(): # Running with no args opens the shell. return cli.shell() @cli.command("install ") def install( url: str, optional_flag: int = opt("--optional-flag", "-f", default=0, help="Example integer option"), ): print(f"Installing from {url} (optional_flag={optional_flag})") @cli.command("echo ") def echo(text: str, upper: bool = opt("--upper", "-u", default=False, help="Uppercase the text")): print(text.upper() if upper else text) def main(): raise SystemExit(cli.run()) if __name__ == "__main__": main() ``` Run it however you like: ```bash interactive-shell theme={null} python -m runegraft ``` ```bash one-shot theme={null} python -m runegraft install https://example.com/pkg.zip -f 3 python -m runegraft echo "hi there" --upper ``` ## What you just got * Flask-like command routes: `install ` and typed args. * A polished shell: completion, history, aliases, `source` scripts, and system command passthrough. * Friendly errors and help text generated from your function signatures. ## Next steps * Learn how route patterns and types work in [Command Routes](/concepts/command-routes). * Explore completions, history, and scripts in [Interactive Shell](/concepts/shell). * Add polished flags and defaults in [Options & Flags](/concepts/options). * See the built-in converters and how to add your own in [Type Converters](/concepts/types). # Recipes Source: https://runegraft.codesft.dev/recipes Practical patterns for real-world CLIs built with Runegraft. Use these snippets as starting points. Copy, adjust, and ship. ## Installer-style command ```python theme={null} from runegraft import CLI, opt cli = CLI("installr") @cli.command("install ") def install( url: str, force: bool = opt("--force", "-f", default=False, help="Overwrite existing installs"), target: str = opt("--target", "-t", default="~/.installr", help="Install directory"), ): print(f"Installing {url} into {target} (force={force})") ``` * The `` converter validates the input before running. * `--force` + `--target` make the command discoverable via generated help. ## Git-style subcommands ```python theme={null} @cli.command("task add ") def task_add(name: str): print(f"Added task '{name}'") @cli.command("task list") def task_list(): print("Listing tasks...") ``` Route prefixes (`task add`, `task list`) keep related commands grouped in the shell and in help output. ## Script repeatable workflows Drop frequently used commands into a `.rg` file and run it with `source` inside the shell: ```text theme={null} # team-setup.rg install https://example.com/pkg.zip -f alias set deploy-prod "deploy production --region us-east-1" history save ``` In the shell: ```bash theme={null} runegraft> source ./team-setup.rg ``` Everyone on your team can share the same scripts to bootstrap their environment in one command. # CLI API Source: https://runegraft.codesft.dev/reference/cli-api Reference for the `CLI` object, decorators, and helper utilities exposed by Runegraft. Runegraft centers on a single `CLI` instance defined in `src/runegraft/cli.py`. Use it to register commands, options, and root behavior; then hand off execution to `CLI.run()`. ## Constructing a CLI ```python theme={null} from runegraft import cli app = cli.CLI(name="forge", description="Package builder") ``` Parameters: * `name`: controls how usage is rendered and becomes the default shell prompt (`forge>`). * `description`: printed at the top of `help` output. The object also exposes `ui.console`, a Rich console configured with pretty tracebacks (see `formatting.make_ui`). ## Registering commands ```python theme={null} @app.command("install [target:path]") def install(url: str, target: Path | None = None): """Download and stage a package.""" ... ``` * Route strings are parsed by `parse_route` and enforce positional types; an error is raised at startup if a reserved shell command name is reused. * The first line of the docstring becomes the summary column in `help`. * All function annotations are available during parsing, so you can combine route tokens and type hints (e.g., type hints drive option coercion). Use `@app.root` to specify what runs when `python -m runegraft` is invoked with no arguments. Returning `app.shell()` matches the demo behavior, but you could launch a default command or print a welcome message. ## Options and flags Options are inferred from function parameters that are **not** present in the route: ```python theme={null} @app.command("ship ") def ship(pkg: str, dry_run: bool = False, retries: int = 1): ... ``` * `dry_run` automatically becomes `--dry-run` and, because it is typed as `bool`, behaves like a flag (`--dry-run` sets the value to `True`). * Non-bool parameters expect a value: `--retries 3`. * Defaults come from the function signature; use `runegraft.cli.option` to override the long/short flag names or add help text. ```python theme={null} from runegraft.cli import option @app.command("echo ") def echo( text: str, upper: bool = option("--upper", "-U", default=False, help="Uppercase output", is_flag=True), ): ... ``` ## Custom converters Route tokens (``) map to converters defined in `types.BUILTIN_TYPES`. Register your own via `CLI.type`: ```python theme={null} @app.type("semver") def parse_semver(raw: str) -> tuple[int, int, int]: ... @app.command("bump ") def bump(version): ... ``` If validation fails, raise any exception and it will be surfaced as a `CLIError` with a nice message both in the shell and non-interactive mode. ## Execution helpers * `CLI.run(argv: Optional[List[str]])`: dispatch arguments (or `sys.argv[1:]` by default). Automatically handles `--help` and empty input. * `CLI.invoke(tokens: List[str])`: run one command when you already parsed the leading command name. * `CLI.shell()`: construct and run the interactive loop from `shell.py`. * `CLI.print_help()`: render the Rich table shown in the shell. The parser raises `CLIError` for user-facing issues; let them bubble up so Rich can colorize the message. Only catch them for custom error handling or to translate into exit codes. # Routing & Types Source: https://runegraft.codesft.dev/reference/routing-and-types Details about Runegraft route syntax, positional argument parsing, and built-in type converters. Runegraft borrows the idea of declarative "routes" from HTTP frameworks: a single string describes the command name and positional arguments. The parser for these strings lives in `parser.py` (`parse_route`, `ArgSpec`, `Route`). ## Route syntax ``` "command [optional:type]" ``` * Tokens wrapped in `<>` are required; `[]` makes the argument optional. * If no type is provided (`` or `[name]`), the parser defaults to `str`. * Whitespace separates tokens; put multi-word help text in docstrings instead. Examples: ```python theme={null} @cli.command("add ") @cli.command("spinner [seconds:float]") @cli.command("fetch [retries:int]") ``` ## Type resolution pipeline When a command runs, Runegraft follows this order: 1. **Custom route types** registered via `CLI.type("name")` take precedence. 2. **Built-in converters** from `types.BUILTIN_TYPES` handle known tokens such as `int`, `url`, or `json`. 3. If no route token is found, the function's **type hints** decide how to coerce option values. For example, an option annotated as `Path` uses `type_name_for_py()` to map to the `path` converter. 4. When none of the above apply, the raw string value is passed through. Any `ValueError` or `TypeErrorValue` raised during conversion is surfaced to the user as a friendly error message. ## Built-in converters | Token | Converts to | Notes | | ----------------------- | ------------------- | -------------------------------------------------- | | `str`, `string`, `text` | `str` | No transformation beyond trimming quotes. | | `int` | `int` | Raises if the input is not numeric. | | `float` | `float` | Accepts decimal input. | | `bool` | `bool` | Handles values like `true/false`, `yes/no`, `1/0`. | | `path` | `pathlib.Path` | Expands `~` on load. | | `url` | `str` | Requires a scheme; enforces host unless `file://`. | | `json` | `Any` | Parses JSON strings into Python objects. | | `uuid` | `uuid.UUID` | Validates canonical UUID text. | | `date` | `datetime.date` | ISO `YYYY-MM-DD`. | | `datetime` | `datetime.datetime` | ISO 8601 timestamp. | Because these live in `runegraft.types`, you can import `BUILTIN_TYPES` and extend or override entries before wiring CLI commands if necessary. ## Usage strings and help The `usage_for_route` helper composes readable usage strings (`install [target:path] [--force]`). These strings end up in the help table and are also used by the shell validator to offer precise error messages while the user types. If you add new option converters or custom route tokens, the `type_name_for_py` helper ensures that help text reflects the human-friendly token name rather than the internal Python class. # Shell API Source: https://runegraft.codesft.dev/reference/shell-api Deep dive into the prompt_toolkit-based shell implementation and customization points. The Runegraft shell (`src/runegraft/shell.py`) wraps prompt\_toolkit to provide completion, validation, aliases, and history. This reference documents the available classes so you can extend or embed the shell in other programs. ## `ShellConfig` ```python theme={null} @dataclass class ShellConfig: history_path: Path prompt: str ``` * `history_path`: file storing the persistent command history (`~/.runegraft/history/.txt` by default). The parent directory is created automatically. * `prompt`: string displayed before each line (e.g., `runegraft> `). `CLI.shell()` constructs this config automatically, but you can pass a custom one if you instantiate `Shell` yourself. ## `Shell` ```python theme={null} shell = Shell(cli, ShellConfig(history_path=..., prompt="forge> ")) shell.run() ``` Key responsibilities: * Builds a nested fuzzy completer from registered commands and shell built-ins (`help`, `history`, `alias`, etc.). * Validates input before execution by calling `CLI.parse_for_invoke`. * Dispatches reserved words (`exit`, `help`, `history`, `alias`, `clear`, `!` system escape). * Maintains an `AliasStore` for simple token substitutions. `Shell.run()` prints a header, then loops forever until the user exits via Ctrl+D, Ctrl+C, `exit`, or `quit`. It returns `0`, and any errors during completion/validation are displayed using the CLI's Rich console. ### Customizing behavior Subclass `Shell` when you need to adjust bindings or completions: ```python theme={null} from runegraft.shell import Shell class MyShell(Shell): def _build_completer(self): completer = super()._build_completer() # mutate completer.tree or wrap with another prompt_toolkit completer return completer ``` * Override `_validate_line` to change how input is pre-validated. * Patch the key bindings (e.g., add custom shortcuts) by overriding `run()` and modifying the `KeyBindings` object before calling `PromptSession`. ## Alias helpers `runegraft.builtins` exposes utilities used by the shell: * `AliasStore`: simple dataclass containing a mapping of alias names to replacement text. * `expand_alias(store, line)`: replaces the first token when it matches an alias. * `clear_screen()`: cross-platform clear helper triggered by `Ctrl+L` or the `clear` command. You can reuse these utilities outside the shell—for example, to apply alias logic in non-interactive contexts. ## Built-in commands The shell reserves several names and prevents you from registering conflicting commands: * `help`, `?` * `exit`, `quit`, `q` * `history` * `clear` * `alias` * `!` (system escape) If you try to decorate a command with one of these names, `CLI.command` raises `CLIError`. Use aliases if you need shortcuts that mimic these behaviors. ## Error handling All exceptions raised by command functions or parsing are caught and printed using `cli.ui.console.print("[red]Error:[/red] …")`. When you throw `CLIError` from custom validation code, the message surfaces directly in the shell and in non-interactive mode, so keep them concise and user-friendly.