{"id":13763386,"url":"https://github.com/thatstoasty/prism","last_synced_at":"2026-05-31T01:04:07.796Z","repository":{"id":216732964,"uuid":"739969568","full_name":"thatstoasty/prism","owner":"thatstoasty","description":"Mojo CLI Library.","archived":false,"fork":false,"pushed_at":"2026-02-18T23:59:50.000Z","size":6259,"stargazers_count":67,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-19T05:11:25.022Z","etag":null,"topics":["cli","mojo","terminal"],"latest_commit_sha":null,"homepage":"","language":"Mojo","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thatstoasty.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-01-07T05:17:04.000Z","updated_at":"2026-02-18T23:59:52.000Z","dependencies_parsed_at":"2024-01-12T11:03:08.095Z","dependency_job_id":"dfd7ac10-2507-4d65-af59-a39dc707eec9","html_url":"https://github.com/thatstoasty/prism","commit_stats":null,"previous_names":["thatstoasty/prism"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thatstoasty/prism","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatstoasty%2Fprism","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatstoasty%2Fprism/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatstoasty%2Fprism/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatstoasty%2Fprism/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thatstoasty","download_url":"https://codeload.github.com/thatstoasty/prism/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatstoasty%2Fprism/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29718671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T15:10:41.462Z","status":"ssl_error","status_checked_at":"2026-02-22T15:10:04.636Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","mojo","terminal"],"created_at":"2024-08-03T15:00:43.708Z","updated_at":"2026-05-31T01:04:07.782Z","avatar_url":"https://github.com/thatstoasty.png","language":"Mojo","funding_links":[],"categories":["Command Line Interpreter","🗂️ Libraries\u003ca id='libraries'\u003e\u003c/a\u003e"],"sub_categories":["CLI"],"readme":"# Prism\n\nA Budding CLI Library!\n\nPrism is a Mojo library designed to help you build command-line interfaces (CLI) with ease. It provides a simple and intuitive way to define commands, subcommands, flags, and hooks, making it easier to create powerful CLI applications. This is primarily a pet project of mine, so expect it to be a bit rough around the edges. I plan to add more features and polish it up as I go along!\n\nInspired by: `Cobra` and `urfave/cli`!\n\n![Mojo Version](https://img.shields.io/badge/Mojo%F0%9F%94%A5-1.0.0b1-orange)\n![Build Status](https://github.com/thatstoasty/prism/actions/workflows/build.yml/badge.svg)\n![Test Status](https://github.com/thatstoasty/prism/actions/workflows/test.yml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Adding the `prism` package to your project\n\nFirst, you'll need to enable the `pixi-build` preview by adding this to the `workspace` section of your `pixi.toml` file.\n\n```bash\npreview = [\"pixi-build\"]\n```\n\n### Building it from source\n\nThere's two ways to build `prism` from source: directly from the Git repository or by cloning the repository locally.\n\n#### Building from source: Git\n\nRun the following commands in your terminal:\n\n```bash\npixi add -g \"https://github.com/thatstoasty/prism.git\" --tag v0.3.0 \u0026\u0026 pixi install\n```\n\n#### Building from source: Local\n\n```bash\n# Clone the repository to your local machine\ngit clone https://github.com/thatstoasty/prism.git\n\n# Add the package to your project from the local path\npixi add -s ./path/to/prism \u0026\u0026 pixi install\n```\n\n## Basic Command and Subcommand\n\nHere's an example of a basic command and subcommand!\n\n```mojo\nfrom prism import Command, FlagSet, read_args\n\ndef test(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Pass chromeria as a subcommand!\")\n\ndef hello(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Hello from Chromeria!\")\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        description=\"This is a dummy command!\",\n        run=test,\n        children=[\n            Command(\n                name=\"chromeria\",\n                description=\"This is a dummy command!\",\n                run=hello\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n![Chromeria](https://github.com/thatstoasty/prism/blob/main/doc/tapes/hello-chromeria.gif)\n\n## Why are subcommands wrapped with `ArcPointer`?\n\nDue to the nature of self-referential structs, we need to use a smart pointer to reference the subcommand. The child command is owned by the `ArcPointer`, and that pointer is then shared across the program execution.\n\n## Accessing arguments\n\n`prism` provides the parsed cli arguments as command function arguments.\n\n```mojo\nfrom prism import FlagSet\n\ndef printer(args: List[String], flags: FlagSet) raises -\u003e None:\n    if len(args) == 0:\n        raise Error(\"No args provided.\")\n\n    for arg in args:\n        print(arg)\n```\n\n## Command Aliases\n\nCommands can also be aliased to enable different ways to call the same command. You can change the command underneath the alias and maintain the same behavior.\n\n```mojo\nfrom prism import Command, read_args\n\ndef main():\n    var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"]\n    )\n    cli.execute(read_args())\n```\n\n![Aliases](https://github.com/thatstoasty/prism/blob/main/doc/tapes/aliases.gif)\n\n## Pre and Post Run Hooks\n\nCommands can be configured to run pre-hook and post-hook functions before and after the command's main run function.\n\n```mojo\nfrom prism import Command, FlagSet, read_args\n\ndef pre_hook(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Pre-hook executed!\")\n\ndef post_hook(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Post-hook executed!\")\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"printer\",\n        description=\"Base command.\",\n        run=printer,\n        pre_run=pre_hook,\n        post_run=post_hook,\n    )\n    cli.execute(read_args())\n```\n\n![Printer](https://github.com/thatstoasty/prism/blob/main/doc/tapes/printer.gif)\n\n## Flags\n\nCommands can have typed flags added to them to enable different behaviors.\n\n```mojo\nfrom prism import Command, Flag, read_args\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"logger\",\n        description=\"Base command.\",\n        run=handler,\n        flags=[\n            Flag.string(\n                name=\"type\",\n                shorthand=\"t\",\n                usage=\"Formatting type: [json, custom]\",\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n![Logging](https://github.com/thatstoasty/prism/blob/main/doc/tapes/logging.gif)\n\n### Default flag values from environment variables\n\nFlag values can also be retrieved from environment variables, if a value is not provided as an argument.\n\n```mojo\nfrom prism import Command, Flag, FlagSet, read_args\n\ndef test(args: List[String], flags: FlagSet) raises -\u003e None:\n    if name := flags.get_string(\"name\"):\n        print(\"Hello \", name[])\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"greet\",\n        usage=\"Greet a user!\",\n        run=test,\n        flags=[\n            Flag.string(\n                name=\"name\",\n                shorthand=\"n\",\n                usage=\"The name of the person to greet.\",\n                environment_variable=\"NAME\",\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n### Default flag values from files\n\nLikewise, flag values can also be retrieved from a file as well, if a value is not provided as an argument.\n\n```mojo\nfrom prism import Command, Flag, FlagSet, read_args\nimport prism\n\ndef test(args: List[String], flags: FlagSet) raises -\u003e None:\n    if name := flags.get_string(\"name\"):\n        print(\"Hello \", name[])\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"greet\",\n        usage=\"Greet a user!\",\n        run=test,\n        flags=[\n            Flag.string(\n                name=\"name\",\n                shorthand=\"n\",\n                usage=\"The name of the person to greet.\",\n                file_path=\"~/.myapp/config\",\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n### Flag Precedence\n\nThe precedence for flag value sources is as follows (highest to lowest):\n\n1. Command line flag value from user\n2. Environment variable (if specified)\n3. Configuration file (if specified)\n4. Default defined on the flag\n\n### Persistent Flags and Hooks\n\nFlags and hooks can also be inherited by children commands! This can be useful for setting global flags or hooks that should be applied to all child commands.\n\n```mojo\nfrom prism import Command, Flag, read_args\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"nested\",\n        description=\"Base command.\",\n        run=base,\n        children=[\n            Command(\n                name=\"get\",\n                description=\"Base command for getting some data.\",\n                run=print_information,\n                persistent_pre_run=pre_hook,\n                persistent_post_run=post_hook,\n            )\n        ],\n        flags=[\n            Flag.bool(\n                name=\"lover\",\n                shorthand=\"l\",\n                usage=\"Are you an animal lover?\",\n                persistent=True,\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n![Persistent](https://github.com/thatstoasty/prism/blob/main/doc/tapes/persistent.gif)\n\n### Required flags\n\nFlags can be grouped together to enable relationships between them. This can be used to enable different behaviors based on the flags that are passed.\n\nBy default flags are considered optional. If you want your command to report an error when a flag has not been set, mark it as required:\n\n```mojo\nfrom prism import Command, Flag, FlagSet, read_args\n\ndef main():\n    var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"],\n        flags=[\n            Flag.bool(\n                name=\"required\",\n                shorthand=\"r\",\n                usage=\"Always required.\",\n                required=True,\n            )\n        ],\n    )\n    cli.execute(read_args())\n```\n\n### Flag Groups\n\nIf you have different flags that must be provided together (e.g. if they provide the `--color` flag they MUST provide the `--formatting` flag as well) then Prism can enforce that requirement:\n\n```mojo\nfrom prism import Command, Flag, read_args\n\ndef main():\n    var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"],\n        flags=[\n            Flag.uint32(\n                name=\"color\",\n                shorthand=\"c\",\n                usage=\"Text color\",\n                default=0x3464eb,\n            ),\n            Flag.string(\n                name=\"formatting\",\n                shorthand=\"f\",\n                usage=\"Text formatting\",\n            ),\n        ],\n        flags_required_together=[\"color\", \"formatting\"],\n    )\n    cli.execute(read_args())\n```\n\nYou can also prevent different flags from being provided together if they represent mutually exclusive options such as specifying an output format as either `--color` or `--hue` but never both:\n\n```mojo\nfrom prism import Command, Flag, read_args\n\ndef main():\n   var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"],\n        flags=[\n            Flag.uint32(\n                name=\"color\",\n                shorthand=\"c\",\n                usage=\"Text color\",\n                default=0x3464eb,\n            ),\n            Flag.uint32(\n                name=\"hue\",\n                shorthand=\"x\",\n                usage=\"Text color\",\n                default=0x3464eb,\n            ),\n        ],\n        mutually_exclusive_flags=[\"color\", \"hue\"],\n    )\n    cli.execute(read_args())\n```\n\nIf you want to require at least one flag from a group to be present, you can use `mark_flags_one_required`. This can be combined with `mark_flags_mutually_exclusive` to enforce exactly one flag from a given group:\n\n```mojo\nfrom prism import Command, Flag, read_args\n\ndef main():\n   var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"],\n        flags=[\n            Flag.uint32(\n                name=\"color\",\n                shorthand=\"c\",\n                usage=\"Text color\",\n                default=0x3464eb,\n            ),\n            Flag.string(\n                name=\"formatting\",\n                shorthand=\"f\",\n                usage=\"Text formatting\",\n            ),\n        ],\n        one_required_flags=[\"color\", \"formatting\"],\n        mutually_exclusive_flags=[\"color\", \"formatting\"],\n    )\n    cli.execute(read_args())\n```\n\nIn these cases:\n\n- The group is only enforced on commands where every flag is defined.\n- A flag may appear in multiple groups.\n- A group may contain any number of flags.\n\n![Flag Groups](https://github.com/thatstoasty/prism/blob/main/doc/tapes/flag_groups.gif)\n\n### Suggesting alternative flags\n\nIf a flag is not provided, you can suggest an alternative flag to the user. This can be useful for providing hints to the user about what they may have meant to type.\n\n```mojo\nfrom prism import Command, Flag, read_args\nimport prism\n\ndef main():\n    var cli = Command(\n        name=\"tool\",\n        description=\"This is a dummy command!\",\n        run=tool_func,\n        aliases=[\"object\", \"thing\"],\n        flags=[\n            Flag.string(\n                name=\"color\",\n                shorthand=\"c\",\n                usage=\"Text color\",\n                default=0x3464eb,\n            ),\n            Flag.string(\n                name=\"formatting\",\n                shorthand=\"f\",\n                usage=\"Text formatting\",\n            ),\n        ],\n        suggest=True,\n    )\n    cli.execute(read_args())\n```\n\nIf you run the command with an invalid flag, it will suggest the closest match to the flag you provided.\n\n```bash\nmojo cli.mojo --volor\n```\n\nwill suggest:\n\n```txt\nUnknown flag: volor\nDid you mean: --color\n```\n\n## Positional and Custom Arguments\n\nValidation of positional arguments can be specified using the `arg_validator` field of `Command`. The following validators are built in:\n\n- Number of arguments:\n  - `no_args` - report an error if there are any positional args.\n  - `arbitrary_args` - accept any number of args.\n  - `minimum_n_args[Int]` - report an error if less than N positional args are provided.\n  - `maximum_n_args[Int]` - report an error if more than N positional args are provided.\n  - `exact_args[Int]` - report an error if there are not exactly N positional args.\n  - `range_args[min, max]` - report an error if the number of args is not between min and max.\n- Content of the arguments:\n  - `valid_args` - report an error if there are any positional args not specified in the `valid_args` field of `Command`, which can optionally be set to a list of valid values for positional args.\n- Composition of validators:\n  - `match_all` - pass a list of validators to ensure all of them pass.\n\nIf `arg_validator` is undefined, it defaults to `arbitrary_args`.\n\n![Arg Validators](https://github.com/thatstoasty/prism/blob/main/doc/tapes/arg_validators.gif)\n\n## Common Flags\n\n### Help\n\nCommands are configured to accept a `--help` and `-h` flag by default. This will print the output of a default help function. You can also configure a custom help function to be run when the `--help` flag is passed. You can use the `help` argument of the `Command` constructor to configure the help function, and the help flag itself.\n\n```mojo\nfrom prism import Command, FlagSet, Flag, Help, read_args\n\ndef help_func(args: List[String], flags: FlagSet) -\u003e String:\n    return \"My help function.\"\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        description=\"This is a dummy command!\",\n        run=test,\n        help=Help(\n            flag=Flag.bool(name=\"custom-help\", shorthand=\"ch\", usage=\"My Cool Help Flag.\"),\n            action=help_func,\n        ),\n    )\n    cli.execute(read_args())\n```\n\n![Help](https://github.com/thatstoasty/prism/blob/main/doc/tapes/help.gif)\n\n### Version\n\nCommands can be configured to accept `--version` and `-v` flag to run a version function. This will print the result of the version function using the output writer that's configured for the command. You can also configure the flag and function to run when the version flag is passed by using the `version` argument of the `Command` constructor.\n\n```mojo\nfrom prism import Command, FlagSet, Version, Flag, read_args\n\ndef test(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Pass -v to see the version!\")\n\ndef version(version: String) -\u003e String:\n    return \"MyCLI version: \" + version\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        usage=\"This is a dummy command!\",\n        run=test,\n        version=Version(\n            \"0.1.0\",\n            flag=Flag.bool(name=\"custom-version\", shorthand=\"cv\", usage=\"My Cool Version Flag.\"),\n            action=version\n        ),\n    )\n    cli.execute(read_args())\n```\n\n## Output Redirection\n\nThe standard output and error output behavior can be customized by providing writer functions. By default, the writer is set to `print` to stdout and stderr, but you can provide custom writer functions that satisfy the expected function signatures.\n\n```mojo\nfrom prism import Command, FlagSet, read_args\nfrom sys import stderr\n\ndef my_output_writer(arg: String):\n    print(arg)\n\ndef my_error_writer(arg: String):\n    print(arg, file=stderr)\n\ndef test(args: List[String], flags: FlagSet) -\u003e None:\n    print(\"Pass -v to see the version!\")\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        usage=\"This is a dummy command!\",\n        run=test,\n        version=version,\n        output_writer=my_output_writer,\n        error_writer=my_error_writer,\n    )\n    cli.execute(read_args())\n```\n\n## Reading arguments in from stdin\n\nCommands can additionally read arguments in from `stdin`. Set `read_from_stdin` to `True` and `stdin` will also be read and parsed for arguments. This should only be set on the root command.\n\n```mojo\nfrom prism import Command, FlagSet, read_args\n\ndef test(args: List[String], flags: FlagSet) -\u003e None:\n    for arg in args:\n        print(\"Received:\", arg)\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        usage=\"This is a dummy command!\",\n        run=test,\n        read_from_stdin=True\n    )\n    cli.execute()\n```\n\n## Exiting the program\n\nBy default, `prism` will exit with a status code of `1` if any `Errors` are raised during the execution of the program. However, the exit behavior can be customized by providing an exit function to the `Command` struct. It's a bit manual with error handling now, but it will be improved in the future.\n\n```mojo\nfrom prism import Command, FlagSet, read_args\nfrom sys import exit\n\n\ndef test(args: List[String], flags: FlagSet) raises -\u003e None:\n    raise Error(\"Error: Exit Code 2\")\n\n\ndef my_exit(e: Error) -\u003e None:\n    if e.as_string_slice() == \"Error: Exit Code 2\":\n        exit(2)\n    else:\n        exit(1)\n\n\ndef main() -\u003e None:\n    var cli = Command(\n        name=\"hello\",\n        usage=\"This is a dummy command!\",\n        run=test,\n        exit=my_exit,\n    )\n    cli.execute(read_args())\n```\n\n## Notes\n\n- Flags can have values passed by using the `=` operator. Like `--count=5` OR like `--count 5`.\n\n## TODO\n\nShould error and output writers even be supported for commands? It seems like unneccessary complexity to have them for every command, when they can be set at the top level. Perhaps we can make it so that the top level command has a default writer, and child commands can override it if needed.\n\n### Features\n\n- Add support for configurable delimiter (default: `--`) to indicate the end of flags.\n- Add persistent flag mutually exclusive and required together checks back in. Right now, the subcommands are created before\nthe parent command, so they can't inherit the persistent flags at construction.\n- Typed arguments.\n- Once the stdlib supports reading from stdin (currently only supports `readline` and `read_until_delimiter`), reading args from stdin will be updated to support newlines.\n\n### Improvements\n\n- Tree traversal improvements.\n- Once we have trait objects, use actual typed flags instead of converting values to and from strings.\n- Commands without children can be created at compile time, but those with them cannot. Perhaps I can find a way to make this work.\n\n## Bugs\n\n- The `CLI.help` is temporarily no longer optional due to a bug in Mojo. It should be optional in order to disable the help flag, but the optional argument in the constructor with a default value leads to an issue where the pointer to the help function is null.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthatstoasty%2Fprism","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthatstoasty%2Fprism","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthatstoasty%2Fprism/lists"}