GitHub - evilmarty/ilc: A simple way to create a command-line utility · GitHub
Skip to content

evilmarty/ilc

Repository files navigation

ilc

The simple way to create a command-line utility

CI GitHub Release GitHub License

Create an easy to use interactive CLI to simplify your workflow with a single YAML file.

demo

Installation

Homebrew

To install via Homebrew just run the following command:

brew install evilmarty/ilc/ilc

Golang

Ensure you have Go installed then run the follow:

go install github.com/evilmarty/ilc

Manual

Binaries are available to download. Get the latest release binary for your platform.

Usage

The usage is as followed:

ilc [flags] CONFIG [COMMAND ...] [INPUT ...]

CONFIG is the path to your config file.

COMMAND is one or a cascade of subcommands defined in the config file.

INPUT is one or many inputs inherited by the command.

Flags

  • -version / --version: Displays the version information and exits.
  • -debug / --debug: Prints debugging logs to standard error.
  • -non-interactive / --non-interactive: Disables interactive terminal prompts. If any required inputs are missing, the tool immediately exits with an error listing the missing inputs. Useful for CI/CD or automated scripts.
  • -validate / --validate: Validates the syntax, structure, and schema of the configuration file without executing any commands.

The best way to use ilc is to include it in the shebang of your config, like so:

#!/usr/bin/env ilc

Commands

If the configuration has defined commands they can either be passed as arguments or an interactive prompt will allow you to choose a command. If the command specified in the arguments has itself subcommands the interactive prompt will appear to complete the selection process.

Inputs

After a command is specified or selected, if any inputs are missing, an interactive TUI prompt will collect them before execution. Inputs are rendered natively according to their type:

  • boolean: Rendered as interactive selection toggles (Yes/No).
  • number: Can be incremented or decremented using arrow keys, enforcing min and max constraints.
  • string (with options): Rendered as an interactive select list.
  • string (with pattern): Validated live against the regex pattern as the user types.

Inputs can also be pre-filled via command-line arguments or environment variables prefixed with ILC_INPUT_. Inputs provided via these methods are not prompted interactively.

All resolved input values are accessible within the script environment via variables prefixed with ILC_INPUT_.

Example of passing inputs as arguments

ilc examples/ilc.yml calendar -month February

Example of passing inputs via environment variables

export ILC_INPUT_month=February
ilc examples/ilc.yml calendar

Replay & History

ilc logs the arguments of successfully executed commands in a history file.

  • Replay Prefix (!): You can replay a previous execution by prefixing the command name or argument with !. For example, ilc examples/ilc.yml !calendar or ilc examples/ilc.yml ! to replay the last run of that configuration.
  • History File Location: History is written to ~/.ilc_history by default. This path can be overridden by setting the ILC_HISTFILE environment variable.

Config

description

The overall description of what is the config's purpose. Is optional.

env

Optionally set environment variables for the command. Cascades to descending commands and subcommands. Expressions can be used in values.

shell

The shell to run the command in. Must be in JSON array format. Defaults to ["/bin/sh"].

run

Runs command-line programs using the specified shell. If commands is also defined then run cannot be invoked directly and becomes a template accessible to all nested commands. See Templating for more information.

pure

Setting pure to true to not pass through environment variables and only use environment variables that have been specified or inherited. Subcommands do not inherit this option and must be set for each command.

inputs

Optionally specify inputs to be used in run and env values. Inputs can be passed as arguments or will be asked when invoking a command. Nested commands inherit inputs and cascade down.

inputs.<input_name>

The key input_name is a string and its value is a map of the input's configuration. The name can be used as an argument in the form -<input_name> or --<input_name> followed by a value. The value can be omitted for boolean types.

inputs.<input_name>.type

The type of input. Defaults to string but can also be boolean and number.

inputs.<input_name>.description

Optionally describe the input's purpose or outcome.

inputs.<input_name>.options

Limit the value to a set of acceptable choices. Options can be defined as either a list or a map, with behavior varying slightly based on the input type:

1. Using Lists (Arrays)

  • String inputs: The list items are presented directly as selectable options.
  • Boolean inputs: The list must contain exactly 2 items; the item at index 0 represents false and the item at index 1 represents true.
List Examples

String options list:

inputs:
  month:
    options:
      - January
      - February
      - March
      - April
      - May
      - June
      - July
      - August
      - September
      - October
      - November
      - December

Boolean options list:

inputs:
  confirm:
    type: boolean
    options:
      - No way      # index 0 (false)
      - Absolutely  # index 1 (true)

2. Using Maps

  • Standard Behavior: In general (and for string inputs), keys are presented as the user-facing labels, and the corresponding values are the resulting values.
  • Boolean inputs: Follows the standard Label: Value format for consistency, but also supports the shorthand Value: Label format for convenience.
Map Examples

String options map:

inputs:
  city:
    options:
      Brisbane: bne
      Melbourne: mlb
      Sydney: syd

Boolean options map (Consistent style):

inputs:
  confirm:
    type: boolean
    options:
      Absolutely: true
      No way: false

Boolean options map (Shorthand style):

inputs:
  confirm:
    type: boolean
    options:
      true: Absolutely
      false: No way

inputs.<input_name>.pattern

A regex pattern to validate the input's value. Default is to allow any input. Applies to string types only.

Example setting an input pattern

inputs:
  year:
    pattern: "(19|20)[0-9]{2}"

inputs.<input_name>.default

Set the default value for the input. It is overwritten when a value is given as an argument or changed when prompted. If a default value is not defined then a value is required.

inputs.<input_name>.min

The minimum value the input can be. Applies to number types only.

inputs.<input_name>.max

The maximum value the input can be. Applies to number types only.

commands

The commands defined are then available to be invoked from the command line either by passing arguments or by interactive selection. Must define at least one command.

commands.<command_name>

Use commands.<command_name> to give your command a unique name. The key command_name is a string and its value is a map of the command's configuration data. A string value can be used as a shorthand for the run attribute.

Example defining an inline command

commands:
  calendar: cal

commands.<command_name>.description

Optionally describe the command's purpose or outcome.

commands.<command_name>.run

See run for more information.

commands.<command_name>.commands

Define sub-commands in the same structure as commands. All inputs or env defined cascade to all sub-commands. Cannot be used in conjunction with run.

commands.<command_name>.env

Optionally set environment variables for the command. Cascades to descending commands and subcommands. See Templating for more information.

Example of templating an environment variable

commands:
  greet:
    env:
      NAME: "{{ .Input.name }}"
      GREETING: Hello

commands.<command_name>.pure

Setting pure to true to not pass through environment variables and only use environment variables that have been specified or inherited. Subcommands do not inherit this option and must be set for each command.

commands.<command_name>.inputs

Optionally specify inputs to be used in run and env values. Inputs can be passed as arguments or will be asked when invoking a command. Nested commands inherit inputs and cascade down. See inputs for more information.

commands.<command_name>.aliases

Optionally include additional aliases to reference the command. Aliases must be unique within the same parent.

Example of defining aliases

commands:
  files:
    commands:
      list:
        aliases:
          - ls
        run: ls -lf
  directories:
    aliases:
      - dir
    commands:
      list:
        aliases:
          - ls
        run: ls -ld

Templating

Go's template language is available for run and env values to construct complex entries. Templates are evaluated after inputs are collected but before script execution. Along with inputs, templates can access environment variables that are present and regardless whether pure is enabled or not.

Nested commands can include the run scripts from their parent commands. ie. {{template "<command_name>"}}

.Input.<input_name>

The expression to reference an input value. ie. '{{ .Input.my_input }}'

.Env.<variable_name>

The expression to reference an environment variable. ie. '{{ .Env.HOME }}'

input "input_name"

A function to retrieve the input by its name. ie. '{{input "my_input"}}'

env "variable_name"

A function to retrieve the environment variable by its name. ie. '{{env "HOME"}}'

Example config with single command

description: Display a calendar for the month
inputs:
  month:
    options:
      - January
      - February
      - March
      - April
      - May
      - June
      - July
      - August
      - September
      - October
      - November
      - December
run: cal -m {{ .Input.month }}

Example config with commands

description: My awesome CLI
commands:
  weather:
    description: Show the current weather forecast
    run: curl wttr.in?0QF
  calendar:
    description: Display a calendar for the month
    inputs:
      month:
        options:
          - January
          - February
          - March
          - April
          - May
          - June
          - July
          - August
          - September
          - October
          - November
          - December
    run: cal -m {{ .Input.month }}
  greet:
    description: Give a greeting
    inputs:
      name:
        default: World
      greeting:
        options:
          - Hello
          - Hi
          - G'day
    run: echo $GREETING $NAME
    env:
      NAME: "{{ .Input.name }}"
      GREETING: "{{ .Input.greeting }}"

Contributing

Contributions are welcome! Whether you are reporting a bug, proposing a feature, or submitting a pull request, we appreciate your help in making ilc better.

Before contributing, please read our Contributing Guidelines to understand:

  • The project's architecture and package design (e.g., the separation of concerns between internal/inputs and internal/ilc).
  • How to format issues and pull requests.
  • Our coding standards.

For AI agents contributing to this repository, please also refer to AGENTS.md for local agent execution guidelines and specialized workspace skills.

Development & Testing

Prerequisites

Ensure you have Go installed (Go 1.18 or later is recommended).

Building from Source

You can compile the binary locally by running:

make build

This compiles the ilc executable using the -buildvcs=false flag to prevent VCS permission errors during sandboxed builds.

Running Tests

To run the automated unit test suite, use the following command:

make test

Or run Go's test runner directly:

go test -buildvcs=false ./...

Manual & Interactive Testing

Since ilc features rich interactive TUI components built on Bubble Tea, you can verify your changes using the configurations inside the examples/ directory:

  1. Verify Numeric Inputs & Constraints:

    ./ilc examples/ilc.yml rate

    Select the rate command, navigate to the rating input, and press the Up and Down arrows to test validation, increment/decrement, and bounds clamping.

  2. Verify Text Validation & Placeholders:

    ./ilc examples/ilc.yml greet

    Verify that default text placeholders render correctly, and that live input regex pattern matching triggers validation ticks/crosses immediately as you type.

  3. Verify Help Cascades:

    ./ilc examples/ilc.yml -help
    # Or for specific subcommands:
    ./ilc examples/ilc.yml weather -help

License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

About

A simple way to create a command-line utility

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

Contributors