# Rat turns a shell command into a pager you can act on

> Rat is a Go tool that runs a shell command, captures its output into a pager, and lets configurable annotators mark pieces of that text so that key bindings can run other commands against them. It was built as a tig-like application with almost no opinionated UI logic, and the README still warns that it is super experimental.

**ericfreese/rat** — Compose shell commands to build interactive terminal applications

- Repository: https://github.com/ericfreese/rat
- Stars: 1,289 · Forks: 51
- Language: Go
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/ericfreese-rat

## An interface assembled from the commands you already have

Rat is described as a tool to compose shell commands to build terminal applications, and the Overview gives the design intent in one sentence.

It was developed as part of an effort to build a tig-like application with very little opinionated UI logic, delegating instead to the capabilities of shell commands like git log with its --pretty and --graph options.

That is the whole philosophy. A conventional tool reimplements git log rendering in its own code, and when git changes its output format the tool lags. Rat delegates the formatting to git itself, and adds the part that git does not do, which is letting you act on a line while you look at it.

The mechanism has two halves. Shell commands are executed and the output is captured and displayed in pagers. Configurable annotators parse through that output, adding annotations that can be acted upon to run other shell commands.

So a Rat application is a shell command plus a set of rules about which text is interesting and what to do with it. The repository has an examples/ directory with a git example and a kubectl example, which are the two cases the author considered typical: a long list of commits, and a list of cluster resources.

## Install, and the warning that comes with it

The Getting Started section opens with a warning in capital letters, and it is the first thing an evaluator should read.

WARNING: THIS IS ALL SUPER EXPERIMENTAL AND IS PROBABLY GOING TO CHANGE A LOT.

That warning sits above the install instructions, which are two commands.

```shell
$ go get github.com/ericfreese/rat
$ go build && go install
```

There is no package manager integration, no binary release and no configuration wizard. The build is the standard Go get, build and install flow, and the go.mod file names the module as github.com/ericfreese/rat.

The warning is not decoration. The latest tagged release is v0.1.1, published on 2026-10-02, the same day as the last push to the repository. A project with one release at 0.1.1 is telling you that the configuration format, the key binding names and the action set may all change, and the readme's own advice in the examples section is to try tweaking some things.

For a tool that a person runs on their own machine, that is an acceptable trade. For anything in a script, it is not, and the configuration file in your home directory would need to be revisited on every upgrade.

## The ratrc file and the key bindings inside it

Rat is configured through a file called ratrc in the home config directory, which the documentation places at $XDG_CONFIG_HOME/rat, or ~/.config/rat by default.

The configuration is organised around modes. A mode is a configuration of annotators and key bindings, and a pager can be opened in one or more modes. The mode keyword starts a mode definition and end closes it.

```shell
mode <name>
  ...
end
```

Key bindings can also live outside any mode, and the readme is precise about the difference. Key bindings that are not inside of a mode definition will always be available and do not have the special prefix behavior described later. Inside a mode, a binding can be scoped to annotation classes, which is what makes a mode useful.

The shipped example of global bindings covers movement, paging and mode switching.

```shell
bindkey C-r reload
bindkey j   cursor-down
bindkey k   cursor-up
bindkey C-e scroll-down
bindkey C-y scroll-up
bindkey q   pop-pager
bindkey S-q quit
bindkey M-1 show-one
bindkey M-2 show-two
bindkey M-3 show-three
```

Modifiers are added with C- and S-, and the documentation points at lib/key_event.go for the list of supported named keys. One binding is guaranteed regardless: ctrl+c will always quit.

## Three kinds of annotator, and a binary format for the third

Annotators are the part of the configuration that decides what in the output is worth acting on. Inside a mode definition, the annotate keyword starts an annotation definition, and each annotation has a start, an end, a class and a value.

```shell
mode <name>
  annotate <type> <class> -- <options>
end
```

The type is one of three things, and each one reaches for the shell differently.

A match type takes a shell command that outputs newline-delimited strings, and the annotator searches the pager content for those strings. A regex type takes a regular expression to search for, with Golang regular expressions supported. An external type takes the name of an executable located in ~/.config/rat/annotators/, which will be executed and sent the content of the pager on STDIN.

The external type is where the design earns its keep, because it is the escape hatch for anything the first two cannot express. The cost is that the executable has to print annotations to STDOUT in a specific binary format, which the readme spells out field by field. The start is a byte offset from the beginning of STDIN as a 64-bit little-endian unsigned integer, the end is the same, then a value length in bytes as a 64-bit little-endian unsigned integer, then the value string itself, UTF-8 encoded, of that length.

A binary protocol is not friendly, and it is also what allows an external annotator to report a match that is not on a line boundary, which a line-oriented match type cannot do.

## Three prefixes decide what happens to the output of a command

A key binding inside a mode takes an optional set of annotation classes, an optional new pager mode, and an action. The action is either a named action, whose list lives in action.go, or a shell command to run.

When the action is a shell command, annotation values are exported to the command process as variables named for their annotation class, so an annotation of class file becomes $file in the command. The default behaviour is to open a new pager showing the output of the command. Three prefixes change that.

The exclamation mark means do not open a new pager, execute the command and reload the current pager. The question mark followed by the exclamation mark behaves the same way but confirms with the user first, so you have to press y or n. The greater than sign keeps the default of opening a new pager, but sets up a parent-child relationship so that the parent cursor can be moved from inside the child pager with the ParentCursorUp and ParentCursorDown commands.

That last one is the mechanism behind tig-like drill-down behaviour, and it is why the global bindings include parent-cursor-down and parent-cursor-up alongside the ordinary cursor movement.

When a binding is scoped to annotation classes, the action is only triggered if annotations of those classes are present on the current line. If the classes are omitted, the binding works anywhere in the pager.

## The worked example builds a file manager out of ls and cat

The readme includes a complete example, and it is the fastest way to understand the model because it assembles a file browser out of three ordinary commands.

```shell
mode files
  # Find all files (not directories) in the current directory and
  # annotate with the class "file".
  annotate match file -- ls -a1p | grep -v /

  bindkey enter file preview -- >cat $file
  bindkey e     file         -- !vim $file
  bindkey S-x   file         -- ?!rm $file
end
```

The annotator is a match type whose shell command lists the current directory and filters out directories, so every filename in the pager carries an annotation of class file. The bindings then act on that class.

Pressing enter runs cat with the filename and opens the result in a new pager in the preview mode, using the greater than prefix so the parent cursor can be moved from inside the child. Pressing e opens the file in vim, using the exclamation prefix so the current pager reloads instead of opening another. Pressing Shift and x runs rm, with the question mark prefix so the user is asked to confirm, which is a sensible thing to demand of a destructive binding and a good illustration of why the prefixes exist.

To try it, the readme says to run rat --mode files --cmd 'ls -al'.

The source keyword handles the case where several files need the same rules, importing configuration from another file with a path relative to the rat config directory.

## A small Go program with one release and dated dependencies

The repository layout is small and says something about the shape of the tool. There is main.go at the root, a lib/ directory for the library code, an annotators/ directory, an examples/ directory with the git and kubectl configurations, a go.mod and a go.sum, and a.travis.yml for continuous integration. The readme also links a GoDoc page and a Gitter chat room.

The dependency list is short and dated. It needs github.com/nsf/termbox-go for terminal handling, github.com/mattn/go-runewidth for character widths, github.com/spf13/pflag for flag parsing, github.com/MathieuTurcotte/go-trie for a prefix tree, and github.com/ericfreese/escseq for escape sequence handling. The go.mod file declares Go 1.12.

Those versions are the clearest signal in the repository. That means a terminal UI library pinned to a 2019 revision and a Go 1.12 directive, both worth checking against your own toolchain and terminal before relying on it.

The licence is MIT, with a LICENSE file at the top of the repository. The honest summary is that this is a one-person experiment that demonstrates an idea well, with an example configuration that teaches the model in about ten lines. Whether it is ready to configure your daily workflow is a different question, and the warning at the top of the readme is the author's own answer.

## Conclusion

Adopt Rat when you have a command whose output is a list of things you then want to act on, and you would rather describe that interaction in a config file than write a script, since the annotator and key binding model is small enough to read in an evening. Do not adopt it for anything that has to be reliable, because the Getting Started section is headed with a warning that it is all super experimental and is probably going to change a lot, and there is a single 0.1.1 release. Verify first that the commands your match and regex annotators search for behave as expected, that any external annotator you write emits the byte offset format the documentation specifies, and that your ratrc uses the source keyword rather than duplicating rules across files.

## FAQ

### What is Rat and what problem does it solve?

Rat composes shell commands to build terminal applications. It runs a shell command, captures the output into a pager, and lets configurable annotators mark interesting pieces of that text so key bindings can run other commands against them, with very little opinionated UI logic of its own.

### How do I install Rat?

Run go get github.com/ericfreese/rat and then go build && go install. There is no binary release; the latest tagged release is v0.1.1, and the readme warns that the project is super experimental and is probably going to change a lot.

### Where does Rat keep its configuration?

In a file called ratrc in the home config directory, which is $XDG_CONFIG_HOME/rat or ~/.config/rat by default. Modes, annotators and key bindings are all defined there, and the source keyword imports rules from other files in the config directory.

### What are the three annotator types in Rat?

match, which takes a shell command outputting newline-delimited strings to search for, regex, which takes a regular expression using Golang syntax, and external, which runs an executable in ~/.config/rat/annotators/ and reads the pager content on STDIN, with annotations written to STDOUT in a binary format of byte offsets and length-prefixed values.

### What do the !, ?! and > prefixes do in a Rat key binding?

The exclamation mark runs the command and reloads the current pager instead of opening a new one, the question mark with an exclamation mark does the same but asks the user to confirm first, and the greater than sign opens a new pager as usual while setting up a parent-child relationship so the parent cursor can be moved from inside the child.

### Can a Rat key binding act on a specific kind of line?

Yes. A binding inside a mode can list annotation classes, and the action only triggers if annotations of those classes are present on the current line. Annotation values are exported to the command as variables named for their class, so a class of file becomes $file in the command.

## Sources

- [ericfreese/rat on GitHub](https://github.com/ericfreese/rat)
- [Issues](https://github.com/ericfreese/rat/issues)
- [License: MIT](https://github.com/ericfreese/rat/blob/master/LICENSE)
- [README](https://github.com/ericfreese/rat/blob/master/README.md)
- [Releases](https://github.com/ericfreese/rat/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ericfreese-rat
