Skip to content

Introduction

Rune is an agent-native CLI framework designed for the agentic era. It is built around two principles:

  • Rune must be a CLI framework that is easy to understand not only for humans, but also for agents
  • CLIs built with Rune must likewise be easy to work with for both humans and agents

In Rune, the file structure under src/commands determines the CLI’s command tree. An index.ts file makes its directory executable as a command. Other routable .ts files in the same directory become sibling subcommands under that directory.

There is no manifest to maintain by hand. You can understand the entire CLI structure at a glance just by looking at the directory layout. Files and directories prefixed with _, plus colocated .test.ts and .spec.ts files, are ignored by routing so related implementation and tests can live near the command. At runtime, only the matched command module is loaded, so no unnecessary code is loaded.

Types for options and arguments are inferred from the definition you pass to defineCommand(). There is no need to write complex type annotations manually.

Mistakes such as placing an optional argument before a required one, duplicating short option names, or creating a name collision with --no-<name> flags are caught at definition time. Hyphenated field names can also be accessed in camelCase, with type safety and autocompletion preserved.

Command options and arguments can be validated with any Standard Schema-compatible library, such as Zod, Valibot, or ArkType. Rune calls validators through the Standard Schema contract, so you are free to choose the schema library that best fits your project without being locked into a specific one.

runCommand() lets you test commands in-process without spawning a child process. It runs the full command execution as-is, including argument parsing, type coercion, validation, and default value resolution. You can assert directly on the result’s exitCode, stdout, stderr, error, and typed output, making command tests fast and reliable.

Setting json: true in defineCommand() enables a built-in --json flag. When the flag is passed, the command’s return value is written to stdout as structured JSON.

In this mode, human-readable stdout from output.log() is automatically suppressed, while output.error() and JSON error payloads are written to stderr so diagnostic messages are not lost. Without --json, the command behaves as a normal text CLI, so a single implementation can serve both humans and machines. When the CLI is invoked by an AI agent, JSON mode is auto-enabled even without --json, so agents receive structured output by default.

For streaming structured output, jsonl: true makes the command emit JSON Lines: each yielded record is serialized as one JSON line and runCommand() exposes those records as result.output.records.

Help output for --help is generated automatically from a command’s description, options, args, and examples. Argument descriptions, primitive defaults, short options, negation flags, and subcommand listings are all included, so there is no help text to maintain separately.

When you need more control, you can customize help rendering per command with defineCommand({ help }) or project-wide with rune.config.ts via defineConfig(), while still building on renderDefaultHelp().

CommandError lets you define structured errors with kind, message, hint, and details. In normal execution, they are displayed in a human-friendly format; in --json mode, they are emitted to stderr as machine-readable error information.

Rune provides an official Agent Skill, an open format for giving AI agents procedural knowledge and project-specific context they can load on demand. The format is supported by multiple AI development tools.

By adding the Rune skill to your project, agents gain on-demand access to Rune-specific knowledge such as file-based routing conventions, defineCommand() usage, and testing patterns. This allows agents to work on Rune projects more accurately and efficiently.