usage

repository·main·Indexed 21 days ago

https://github.com/jdx/usage

A specification and CLI tool for formally defining CLI tools. It allows developers to describe arguments, flags, environment variables, and configuration files in a single KDL spec to automate the generation of shell completions (bash, zsh, fish, PowerShell, nushell), markdown documentation, man pages, and --help output. Includes integrations for Rust's clap crate (clap_usage v4.0.0) and Go's cobra framework (cobra_usage).

Tokens
87.6K
Snippets
394
Records
472
Agent score
72%

What's inside usage

  1. Overview of Usage Integrations

    main

    Usage Integrations are tools that extract CLI definitions (including commands, flags, arguments, and completions) from existing CLI framework definitions. They convert these definitions into a standardized usage spec in KDL format. This single source of truth can then be used to automatically generate:

    • Shell completions (bash, zsh, fish, PowerShell, and nushell)
    • Markdown documentation
    • Man pages
    • --help output
  2. What is Usage and why use it?

    main

    Usage is a specification and CLI for defining CLI tools, similar to how OpenAPI (Swagger) works for web APIs. Instead of manually writing documentation or autocompletion scripts, you define your CLI in a Usage spec (written in KDL).

    Key benefits include:

    • Generating autocompletion scripts for various shells.
    • Generating markdown documentation, man pages, and help text.
    • Generating type-safe SDK client libraries (TypeScript, Python, Rust).
    • Using an advanced argument parser in any language.
    • Scaffolding a single spec into different CLI frameworks or languages.
  3. What is Usage?

    main

    Usage is a specification and CLI tool for defining CLI tools. It acts as a formal schema for command-line interfaces, similar to how OpenAPI (Swagger) works for web APIs. By defining your CLI using a Usage spec, you can:

    • Generate autocompletion scripts
    • Generate markdown documentation
    • Generate man pages
    • Use an advanced argument parser in any language
    • Scaffold a single specification into different CLI frameworks or even different programming languages
    • [Coming soon] Host CLI documentation on usage.sh
  4. Manage running processes with `--on-busy-update`

    main

    When a file change occurs while the current task is still running, you can control how mise watch reacts using the -o or --on-busy-update flag:

    • do-nothing (default): Ignore new events until the current run finishes.
    • queue: Run the command again once the current run finishes if any events occurred.
    • restart: Terminate the running command and start a new one immediately.
    • signal: Send a signal to the running process (requires --signal <SIGNAL>).

    Related Flags:

    • -r or --restart: Shorthand for --on-busy-update=restart.
    • --stop-signal <SIGNAL>: The signal sent to stop the command during a restart (default is SIGTERM on Unix). Use KILL on Windows.
    • --stop-timeout <TIMEOUT>: How long to wait for a graceful exit before force-killing (e.g., 10s).
    # Restart the task immediately if a file changes while it's running
    mise watch -r my-dev-server
    
    # Send SIGINT to a running process instead of killing it
    mise watch -o signal -s SIGINT my-app
  5. Understand the configuration priority in usage

    main

    When resolving configuration properties, usage follows a specific hierarchy. If a property is defined in multiple places, the one with the highest priority wins. The priority order is:

    1. CLI flag (e.g., --user alice)
    2. Environment variable (e.g., MYCLI_USER=alice)
    3. Config file (e.g., ~/.mycli.toml)
    4. Default value
  6. Understand flag precedence with mounted commands

    main

    When using mount to define dynamic commands, the mounted command is treated as a different program. This affects how flags are handled:

    1. Scope Isolation: Once a user enters a mounted command, global flags declared above the mount are no longer offered in completions.
    2. Precedence: If a mounted command declares a flag with the same name as a global flag, the mounted command's flag takes precedence. For example, if a global flag --env <ENV> exists, but a mounted task defines --env <name> with specific choices, the completion will show the task's choices.
    3. Global Propagation: Global flags are still recognized if they are placed before the mounted command (e.g., mycli --env prod run task1). In this position, they propagate into the mount command.
    ```kdl
    flag "-E --env <ENV>" global=#true
    cmd "run" {
    	mount run="mycli mount-usage-tasks"
    }
    
    // Emitted by the mount:
    cmd "task1" {
      flag "--env <name>" {
        choices "dev" "stage" "prod"
      }
    }

    Behavior Summary:

    • mycli run task1 --<tab> $\rightarrow$ Shows only task1 flags.
    • mycli run task1 --env <tab> $\rightarrow$ Shows dev, stage, prod (task choices) instead of the global <ENV>.
    • mycli --env prod run task1 $\rightarrow$ Global flag is correctly recognized and propagates.
  7. Use global flags

    main

    A global flag is recognized by the command that declares it and all its subcommands.

    Behavior:

    • Position Independence: A global flag can appear before or after subcommands (e.g., mycli --verbose run task and mycli run task --verbose are both valid).
    • Mounting: Global flags are passed to any mount reached after the flag is declared. However, the flag is not offered inside the mounted command itself.

    Non-global flags:

    • These belong strictly to the command that declares them.
    • They may appear before a subcommand (e.g., mycli run --force task), but they are not inherited by subcommands and are not passed to mounts.
    // Declaring a global flag
    flag "-f --force" global=#true
  8. How Usage Integrations work

    main

    The integration process follows a specific pipeline:

    1. Extraction: The integration reads the internal representation of a CLI framework (e.g., clap in Rust or cobra in Go).
    2. Transformation: It extracts commands, flags, arguments, and completion logic.
    3. Output: It produces a usage spec in KDL format.

    This KDL-based spec acts as the intermediate representation that drives all downstream artifacts like documentation and shell completion scripts. For a reference implementation of this pattern, see clap_usage.

  9. Mount dynamic commands for task-based CLIs

    main

    If your CLI has dynamic subcommands (e.g., a run command that executes tasks with varying arguments), you can use the mount property to inject usage specifications at runtime.

    To implement this:

    1. Create a hidden command (e.g., mycli mount-usage-tasks) that, when executed, emits a KDL usage spec describing the dynamic tasks.
    2. Define a mount on the parent command in your static spec, pointing to that hidden command.

    When a user triggers completion (e.g., mycli run <tab><tab>), the system executes the mount command and merges its output into the run command's specification.

    Execution Note: Mounts are executed via sh -c (falling back to cmd /c on Windows). If your mount points to a shebang script, a POSIX shell must be available on the PATH.

    // Static usage spec
    cmd "mount-usage-tasks" hide=#true
    cmd "run" {
    	mount run="mycli mount-usage-tasks"
    }

    // Example of what the mount command might emit:

    cmd "task1" {
      arg "arg1" help="task1 arg1"
      flag "flag1" help="task1 flag1"
    }
    cmd "task2" {
      arg "arg1" help="task2 arg1"
      flag "flag1" help="task2 flag1"
    }
  10. Understand command effects (read, write, destructive)

    main

    Commands can declare an effect to describe their impact on the system. This allows consumers (like documentation generators, wrapper scripts, or AI agents) to understand the safety of a command.

    EffectMeaning
    readOnly inspects state. Running it twice is the same as running it once.
    writeCreates or modifies state, but removes nothing the user cannot recreate.
    destructiveMay delete or irreversibly overwrite something. Deserves a confirmation prompt.

    Important Rules:

    • No Inheritance: effect is not inherited by subcommands. Each command must declare its own effect.
    • Unknown is Unsafe: A command with no effect should be treated as "unknown" (consumers should ask for permission).
    • Raising Effects: A flag or argument can raise the effect of a command. The total effect of an invocation is the maximum of the command's effect and the effect of every flag/argument supplied (read < write < destructive).
    • No Lowering: A flag or argument can only ever raise the effect, never lower it (e.g., --dry-run cannot lower a destructive command to read).

    Example of declaring effects:

    cmd "ls" effect="read" help="List installed tools"
    cmd "use" effect="write" help="Install a tool and add it to the config"
    cmd "uninstall" effect="destructive" help="Remove a tool"
    
    // Effect via child node
    cmd "uninstall" {
      effect "destructive"
    }
    
    // Raising effect via flag
    cmd "logs" effect="read" help="Show daemon logs" {
      flag "--clear" effect="destructive" help="Delete stored logs"
      flag "--follow"
    }
    
    // Raising effect via argument
    cmd "settings" effect="read" {
      arg "[setting]"
      arg "[value]" effect="write"   // `settings foo` reads, `settings foo=bar` writes
    }
    cmd "ls" effect="read" help="List installed tools"
    cmd "use" effect="write" help="Install a tool and add it to the config"
    cmd "uninstall" effect="destructive" help="Remove a tool"
    
    // Effect via child node
    cmd "uninstall" {
      effect "destructive"
    }
    
    // Raising effect via flag
    cmd "logs" effect="read" help="Show daemon logs" {
      flag "--clear" effect="destructive" help="Delete stored logs"
      flag "--follow"
    }
    
    // Raising effect via argument
    cmd "settings" effect="read" {
      arg "[setting]"
      arg "[value]" effect="write"   // `settings foo` reads, `settings foo=bar` writes
  11. Understand the execution environment of `run` commands

    main

    When usage executes a run command for completions:

    • Shell Execution: It runs the command via sh -c. This means you can use POSIX shell features like pipelines (|), command sequences (;), and shell builtins.
    • Windows Support: On Windows, if sh is on your PATH (e.g., from Git for Windows), it uses sh -c. Otherwise, it falls back to cmd /c. Note that cmd /c does not support pipelines or sequences; for cross-platform compatibility, keep run to a single command or ensure a POSIX shell is available.
    • Environment & IO: The script is executed with stdin closed and stderr inherited. The environment variable __USAGE is set to the version of usage being used, allowing scripts to detect if they were invoked by the tool.
  12. How completion scripts fetch choices

    main

    Completion scripts generated by usage typically interact with the CLI by calling usage complete-word. This command takes a specification file and the current command line context to return valid completion choices.

    Example of the underlying call made by a completion script:

    $ usage complete-word --file ./mycli.usage.kdl -- mycli cmd1 cmd2 --f
    --force
    --file