❯ rotini --documentation█

2026

rotini v1.5.0

Rotini v1.5.0 has been released, together with new releases of every go-rotini package it uses.

New

  • Local and cascading flags. A flag now applies only to its own command unless it is cascading: true, which makes it apply to its command and every command below, as help’s “Global Flags” already described it. Given anywhere else, a local flag is a usage error that says where it belongs (--init belongs to "app", not "app db migrate"). Its required, group and dependency rules apply only when its command runs, and completion follows the same scope. Env and config values are not affected. See commands, flags and arguments.
  • Prefixed integers. base: prefixed on an integer input accepts 0x1F, 0o17 and 0b101, with a sign and _ between digits, from every source, defaults and enums included. Help notes it, the contract and JSON Schemas describe it, and rotini diff rates adding it safe. See times, patterns and negated forms.
  • Your own metadata. metadata: is a map of strings on commands, flags, arguments, env, config and stdin, for your own tools. It’s copied into the generated definitions and the contract as written, and shown on no page. See your own metadata.
  • Streams of several kinds. output_stream: true with output: {oneOf: [...]} streams items of several kinds, each naming itself in one shared tag. The generated code gives you a sealed interface only those kinds satisfy, WriteOutputItem fills in the tag, DecodeOutput and rotinitest.Output decode each item into its kind, and output checks check each item. See streams of several kinds.
  • Completion fallback per request. rtx.SetCompletionFallback lets a completer choose what the shell falls back to for one request, so an argument can offer remote names when some match and local paths otherwise. Completion scripts generated by v1.4 already understand it. See completers.
  • Smaller and faster. A typical generated CLI is 3.36 MB stripped, down from 3.84 MB, and a process run adds 0.33 ms over a plain Go program, down from 0.38 ms. In process, a run takes 9.1 µs and 57 allocations, down from 23.4 µs and 329, and printing help takes 3.0 µs. A config file is read and decoded once per inputs read, schema check included, and a program with env or config inputs allocates about 60% less per run. See what a program links.

Changed

  • Flags are local unless they cascade. Mark cascading: true on every flag users give with a sub-command. A root --help or --version seeded before v1.3 isn’t cascading, so <command> --help becomes a usage error until you mark it; validate and generate warn about it.
  • Integers from env and config files are decimal. PORT=0x10 is an invalid value and 010 reads as ten, unless the input declares base: prefixed. Bounds are checked on every such value.
  • Config files are stricter. A repeated key in a YAML or JSON config file or stdin document fails, as does a second YAML document, and an unreadable config file is an error even when it’s optional. A null config value (mode:, ~) counts as not set, so it no longer overrides a flag or a default. A comment-only file loads as empty.
  • Completion scripts. Every shell now inserts candidates exactly as given, quotes included, and bash works under bash 5.2 and in a shell running with set -e. Regenerate and reinstall your completion scripts.
  • validate and generate check more. New errors for Go identifier and file name clashes, Windows device names and Go keyword package names; new warnings for multi-line summaries and an env variable two inputs read differently.
  • rotini -v is gone; the companion answers --version only, as rotini init seeds it.
  • Windows paths in messages are quoted as typed: "C:\Users\me", not "C:\\Users\\me".
  • OutputDef is no longer comparable with ==, since it gained the kinds of a multi-kind stream.

Upgrading from v1.4

Bump github.com/go-rotini/rotini and the rotini tool to v1.5.0, raise version: in your spec and conf to 1.5.0, and run rotini generate. Then:

  1. Mark cascading: true on each flag your users give with a sub-command, including a root --help or --version seeded before v1.3. rotini validate lists the ones it can tell.
  2. Add base: prefixed to any integer input whose env or config values use 0x, 0o or 0b.
  3. Regenerate and reinstall your completion scripts.
  4. Update tests that match Windows paths in messages, or that compare OutputDef values with ==.
  5. Run rotini validate: a few specs v1.4 accepted are now errors, and it says which.

See upgrading for the general steps.

rotini v1.4.1

Rotini v1.4.1 has been released.

This version does contain more breaking API changes and a good bit of usage churn due to refining how rotini goes about some of its init/alloc/execution processes. I tried to keep many of the changes invisible, but there are changes that impact how users interact with rotini through its spec/conf files, the api surface used in a “main.go” entrypoint for building an executable binary, and rotini command handler files. When I saw the imported packages, symbol table, allocs, dispatch timing, and base rotini-powered binary size from the last release, I decided to re-focus on performance optimizations and again eat the cost of breaking the existing contract.

New

  • Smaller binaries, faster start-up. Generated code now links only the runtime features its spec declares. A CLI with no config files, stdin or plugins no longer carries the config codecs, the JSON Schema compiler or os/exec: a 13-command CLI went from 6.4 to 3.3 MB stripped on linux, and starts about 0.25 ms faster. Regenerate to get it. See what a program links.
  • Agent-ready CLIs. Declare effects on commands and flags, give flags roles agents read (dry-run, confirm, machine-output, sort and more), and keep commands from agents with agent: false. rotini generate can write tool definitions for MCP, OpenAI and Gemini, an Agent Skills page, llms.txt, and permission rules for agent harnesses. The new go-rotini/mcp module serves a Rotini CLI’s tools over MCP, from its own binary or with rotini-mcp serve. See agent-ready CLIs.
  • Checking for breaking changes. rotini diff <old> [new] compares two contracts and reports what changed for your users, rule by rule. The old one can be a file, a git ref or a module version, so a CI step can fail a pull request that breaks a flag. Removals you planned with deprecated_since and removed_in show as expected. See checking for breaking changes.
  • Config profiles and write-back. profiles: selects a block of config from --profile, an environment variable or a default. rotini.SetConfigValue, UnsetConfigValue and ConfigFilePath edit a user’s YAML, JSON, JSONC, dotenv or TOML file in place, checked against the spec, so config set is a few lines. Rotini can also write a JSON Schema for your users' config file, so their editor completes it. See profiles and writing config values.
  • Files and streams. Stdin can be streamed instead of read whole, with the jsonl and bytes formats. inputfile and outputfile inputs come with rotini.OpenInput and rotini.CreateOutput, and a failed write to stdout now fails the run. See files and streams.
  • Parsing. Reusable flag sets, flags that depend on another flag’s value, a directory flag (role: chdir, like git -C), response files, partial passthrough, env and config fallbacks for arguments, several time layouts per input, and relative times. See sharing flags between commands and a directory flag.
  • Help and completion. Enum values with descriptions, aliases and deprecations; help topics; stability: experimental|beta; --explain-inputs; the usage line as data with rtx.Usage(). Completion hides flags already set, lists required flags first, keeps your order, and gains host, group and command kinds, a Nushell script and a Carapace export. See what completion offers.
  • Testing. The rotinitest package runs a command from a test with a typed inputs value, in its own environment and directory so tests can run in parallel, and decodes its structured output. See testing with typed inputs.
  • Bring a CLI over. rotini import builds a spec, conf and handler stubs from a live Cobra, Kong or urfave/cli program, without touching its files, and reports what it couldn’t carry over. See rotini import.

Changed

  • The default reporter writes infos and successes to stderr, so stdout carries only your program’s output.
  • Completion keeps a completer’s order and an enum’s declared order. The __complete protocol ends with :32 where it sent :0.
  • Usage lines put flags before arguments: app sub [flags] <name>.
  • Stricter input checks. existingfile and existingdir values from the environment or a config file are now checked, list bounds count real elements, and type: url needs a scheme. Run rotini validate after upgrading.
  • A broken config file no longer breaks --help or --version.

Upgrading from v1.3

Bump github.com/go-rotini/rotini and the rotini tool to v1.4.1, raise version: in your spec and conf to 1.4.0, and run rotini generate. Then:

  1. In main.go, replace cmd.Program with cmd.NewProgram(). rotini generate points at an old main.go that still uses it. To swap or wrap one command’s handler, pass your own handler set to cmd.NewProgramWith(handlers).
  2. WithOutputChecks() becomes WithOutputChecks(true).
  3. Tests that read infos or successes from stdout need to read stderr, and tests pinned to the __complete output need the new directives.
  4. A help template you seeded with template: true needs {{.Label}} on each flag row for the new alignment. Help now adds “(repeatable)” itself, so drop it from your summaries.
  5. Run rotini validate: a few specs and inputs v1.3 accepted are now errors, and it says which.

See upgrading for the general steps.

rotini v1.3.0

Rotini v1.3.0 has been released.

New

  • Flags that skip the run. Mark a flag short_circuit: true and, when it’s set on the command line, the command’s required inputs and value rules are waived, so rtx.Inputs succeeds and the handler can act on the flag. --help is the obvious one: app deploy --help now prints help even when the service to deploy is missing. Input that can’t be read, such as an unknown flag or a value that isn’t a number, is still an error. rotini init now seeds --help and --version this way, answered once in the root handler’s CascadingPreRun, so new command stubs carry no help code at all. See flags that skip the run.
  • Check inputs you collected yourself. rtx.CheckInputs(v, rotini.PresenceOf(v)) checks values from a prompt, a file or an API against the spec’s rules, and reports them the way the command line would. See checking inputs you collected yourself.
  • rotini generate --dry-run. Works out everything generate would write or remove and changes nothing: exit 0 when the generated code matches the spec, 2 when it doesn’t, 1 on an error. Set generate.dry_run_env: CI in the conf and go generate ./... checks every CLI in the module on CI. rotini init --dry-run previews a new CLI the same way. See rotini generate.
  • Help shows where a flag’s value can come from. A flag with an environment or config fallback gets a line under it:
    -r, --replicas int    how many instances (default 1)
                          also set by CFGCTL_DEPLOY_REPLICAS or config key deploy.replicas
    
    Man pages, markdown pages and the contract document show the same.
  • Completion messages. With messages: on the completion feature, pressing TAB on a value with nothing to offer shows a line of guidance instead, from the spec (complete.message), from the input’s summary, or from a completer with rtx.AddCompletionMessage. Your users can hide them with the variable the conf names in messages_env. zsh and bash 4.4+ show them, and so do kubectl and Docker for a rotini plugin. See completion messages.
  • exit_status is checked. Once a command declares exit_status:, rotini generate warns when its handler exits with a code the list leaves out, and rotini validate reports a code listed twice.
  • Better error handling from handlers. A value from an environment variable or config file that breaks a rule now says where it came from, as in (from environment variable APP_PORT). Errors carry the facts a “did you mean” needs everywhere it makes sense, including env and config values and mistyped plugin commands, and rotini.SuggestionFacts(err) reads them for your own ranking. See handling errors in a handler.

Changed

  • Removing a command no longer breaks go generate ./.... Its handler file is now retired in two runs: the first generate disables it with //go:build ignore, keeping your code, and the next one deletes it. Put the command back before then and the file is restored.
  • Help, man and markdown pages change for every flag with an environment or config fallback. If you test your --help output against a saved copy, update it after regenerating.
  • COMPATIBILITY.md and UPGRADING.md are gone. Rotini is still evolving, so a minor release can include breaking changes; every release lists them in its notes. How to upgrade is now in the guide, under upgrading.

Updated help shape and behavior

The new shape is opt-in: a project left as it is keeps its per-command help flags and the help check in each handler. To get what a fresh rotini init writes:

  1. In the spec, mark the root’s --help flag cascading: true and short_circuit: true, and its --version flag short_circuit: true.
  2. Add a CascadingPreRun to the root handler that answers both, and remove its rotini.NoCascadingPreRun line. rotini generate warns until the hook is there, and a fresh rotini init in a scratch directory shows the hook to copy.
  3. Remove the per-command help flags the cascading one replaces, then run rotini validate.

The help check at the top of your existing handlers no longer runs once the root answers first. It does no harm, so delete it whenever you like.

rotini v1.2.0

Rotini v1.2.0 has been released.

New

  • Structured output. Declare a command’s output: shape in the spec. Rotini generates its Go type, documents it on the help, man and markdown pages, and can publish a JSON Schema per output and a contract document for the whole CLI. See structured output.
  • JSON errors for scripts. rotini.StructuredReporter writes errors and other outcomes as JSON lines.
  • Roff man pages by default, with a section setting. ManPages() and MarkdownPages() return every page, ready to install.
  • Companion CLI adds rotini completion <shell> and rotini man --dir.
  • A guide to building cli plugins with rotini. See plugins.

Changed (breaking)

The API was renamed to be more consistent before more programs depend on it. Regenerate, and the compiler finds every call site to update:

  • Inputs are methods on rtx: rotini.Collect[T](rtx) is now rtx.Inputs[T](), and ParseArgv, ParseEnv, ParseFiles and ParseStdin are now rtx.ArgvInputs, EnvInputs, FileInputs and StdinInputs.
  • Reporting: Program.WithFunnel is now Program.WithReporter.
  • Plugins: the spec keys remote_commands and remote_discovery are now plugins and plugin_discovery, the Remote* types are now Plugin*, and rotini.CobraCompletion is now rotini.PluginCompletion.
  • Input reading: Binder and BindMeta are now InputReader and InputSettings.
  • Dependencies: rotini.NewKey is now rotini.NewDependency.
  • Hooks: the Default* no-op embeds are now No*, such as rotini.NoPreRun.

I really should have released rotini under a 0.X.X to start, but I didn’t. I don’t want to immediately jump into a v2 of the package, and with so few (zero) projects likely depending on rotini I’m going to eat the breaking api surface changes without providing aliases. In the future, once rotini has had more time to bake-in and has more usage, this is the type of thing I would version appropriately.

Fixed

  • A number too large for a narrow flag type, such as 300 for an int8, is now a usage error instead of a silently wrapped value.

rotini v1.0.0

Rotini v1.0.0 has been released.

Rotini is a spec-driven CLI framework for Go. You declare your commands, flags, and arguments in a spec file, then rotini generates typed Go and a handler stub for each command; you write what each command does.

See the documentation to get started!