Documentation
Overview
rune is a Ruby CLI and library built to be equally usable by a human at a terminal and an AI agent driving it programmatically.
Every rune command returns the same structured Result internally:
only the rendering changes based on how you're calling it. That's the whole design,
one code path, two audiences.
The three output modes
rune picks a rendering mode automatically based on how it's invoked, or you can force one explicitly with a flag. All three modes execute the exact same command logic; only the output format differs.
1. Human TTY mode (default, interactive terminal)
When stdout is a real terminal and no --json/--ndjson flag is given,
rune prints colorized, human-formatted output:
$ rune run -- echo "hello"
✓ echo hello (6.2ms, exit 0)
hello 2. Agent JSON mode (--json, or automatic pipe detection)
Pass --json explicitly, or simply pipe/redirect rune's output: a non-TTY stdout
switches rendering to JSON automatically, no flag required.
$ rune version | cat
{"status":"ok","data":{"name":"rune","version":"0.2.0","ruby":"4.0.5","fledge":true,"specsync":true}}
Every JSON response has the same envelope: {"status": "ok"|"error", "data": {...}}
(or {"status": "error", "error": "..."} on failure).
3. Agent NDJSON envelope mode (--ndjson)
--ndjson wraps the same result in an {"event": "result"|"error"}
envelope instead of --json's plain {"status": ...} shape, a
format some agent harnesses expect uniformly. For rune run this is still exactly
one line, emitted once the command finishes: PTYRunner buffers the whole run, so
it's an envelope choice here, not incremental streaming. For an actual live event stream as a
long-running or interactive command progresses, see rune watch below,
which emits one NDJSON line per output chunk as it happens.
$ rune run --ndjson -- echo "hello stream"
{"event":"result","status":"ok","data":{"exit_code":0,"clean_output":"hello stream\n",...}} Live interactive passthrough: rune watch
rune run buffers a command's entire output and returns once it finishes, good for
scripting but no good for sitting at the keyboard and driving an interactive program live.
rune watch puts your terminal in raw mode, forwards every keystroke (including raw
escape sequences like arrow keys) to the child as you type, streams its output to your screen
as it happens, and logs every chunk as an NDJSON event to a temp file so an agent can
tail -f the session in real time while a human drives it:
$ rune watch -- some-interactive-cli
[rune watch] live event log: /tmp/rune-watch-12345-1700000000.ndjson
See the quickstart for a full walkthrough
and the reference for the
--log=PATH flag.
Why a real PTY
rune run spawns commands inside a real pseudo-terminal rather than a plain pipe,
so TTY-dependent CLIs and interactive TUIs behave exactly as they would for a human: no
accidental "not a tty" fallbacks, no hanging pagers. Output is stripped of ANSI escape
sequences before it's returned in clean_output, and execution duration is measured
in milliseconds.
Zero runtime dependencies. rune is stdlib-only Ruby, no gems to audit, no version conflicts to chase.