ite-cli 0.1.0

Interactive terminal tree explorer for filesystems and JSON
Documentation
# ite

**i**nteractive **t**ree **e**xplorer — a terminal UI for walking a tree,
poking at it, and doing something useful with whatever you land on.

The elevator pitch: `tree` shows you everything and scrolls off the screen;
`ite` shows you a collapsed tree and lets you open exactly the doors you care
about. Press enter on a leaf and its selection lands on stdout. For a
filesystem tree that is the absolute path; for a JSON document it is the
selected node's JSON Pointer. That's the whole trick, and it composes
beautifully:

```sh
vim "$(ite)"          # pick a file, edit it
cd "$(ite ~/src)"     # pick a directory (ctrl+enter), go there
ite --json response.json      # explore a JSON document
curl -s api.example.com/users | ite   # pipe JSON straight in
```

The interface draws on **stderr**, so stdout stays clean for the value. If you
have ever piped `fzf`, you already know this dance.

## Usage

```sh
ite [OPTIONS] [PATH]
```

| Flag | Meaning |
|------|---------|
| `PATH` | Directory to explore (default: `.`) |
| `-j`, `--json <PATH>` | Explore a JSON file instead of a directory (`-` reads stdin) |
| `-I`, `--no-ignore` | Show ignored files by disabling ignore-file rules |
| `-e`, `--expand <N\|all>` | Start with N levels expanded (`-e 1` opens top-level containers), or all of them |
| `-c`, `--config <FILE>` | Use this config instead of the user config; repeatable, later files win |

### Filesystem

By default, `ite` explores `PATH` (or `.`). It shows dotfiles while respecting
`.gitignore` and friends. `-I` also shows ignored files.

### JSON

Pass `--json PATH` (or `-j PATH`) to explore one JSON document instead.
Objects become tree branches, arrays keep their input order under indexed
children, and scalar values are leaves:

```text
$ ite --json users.json
▼ users [2]
├ ▼ [0] {2} id: 12 · name: "Ada"
│ ├ • id: 12
│ └ • name: "Ada"
└ • [1]: null
```

Accepting a JSON node writes its canonical JSON Pointer, such as
`/users/0/name`; the root pointer is empty. `$path` and `$relpath` in shell
bindings contain the same value.

JSON can also arrive on a pipe, `fzf`-style. When stdin is not a terminal and
no directory is given, `ite` reads a JSON document from stdin; `--json -`
requests the same thing explicitly. Keyboard input then comes from
`/dev/tty`, so the interface works as usual:

```sh
curl -s api.example.com/users | ite
kubectl get pod mypod -o json | ite --json - --expand 1
```

To explore a directory while something is piped in, name it: `producer | ite .`
ignores the pipe and explores the filesystem.

## Exit Codes

Exit codes are honest: `0` means a value was printed, `130` means you quit
without choosing, and a keybinding configured with `exit = true` passes its
command's status through.

## Keys

Navigation is vim-flavored, with arrows for the unconverted:

| Key | Action |
|-----|--------|
| `j` / ``, `k` / `` | Move focus down / up, one visible line |
| `l` / `` | Expand a collapsed container; on an expanded container, focus its first child |
| `h` / `` | Collapse an expanded container; otherwise focus its parent |
| `L` / `shift+→` | Expand recursively |
| `H` / `shift+←` | Collapse recursively |
| `enter` | Expand a collapsed container; on a leaf, print its path or JSON Pointer and exit |
| `ctrl+enter` | Print the focused path or JSON Pointer and exit, container or not |
| `alt+enter` | Print the focused basename or compact JSON value and exit |
| `tab` | Descend into a container (expanding it if needed) |
| `J`, `K` | Next / previous sibling, hurdling expanded subtrees |
| `ctrl+f` / `ctrl+b` | Page down / up |
| `ctrl+d` / `ctrl+u` | Half-page down / up |
| `gg`, `G` | First line, last visible line |
| `q`, `esc`, `ctrl+c` | Quit |

A note for the fine print: `ctrl+enter` and `shift+arrow` require a terminal
that speaks the kitty keyboard protocol (kitty, WezTerm, foot, recent
iTerm2...). Elsewhere, the synonyms — `tab`, `L`, `H` — have you covered.

## Configuration

`ite` reads `$XDG_CONFIG_HOME/ite/config.toml` (usually
`~/.config/ite/config.toml`). Each table is a keybinding; the table name is
the key:

```toml
[ctrl+e]
sh = "vim $path"     # run a shell command on the focused node
exit = true          # then leave ite (default: false)

[alt+s]
sh = "attach-to-review $relpath"
bg = true            # run detached, without leaving the TUI (default: false)

[ctrl+l]
cmd = "expand-recursively"   # or run an ite command instead
```

`sh` commands run via `sh -c` with two environment variables set. For a
filesystem tree, `$path` is absolute and `$relpath` is relative to the
explored root. For JSON, both are the selected node's JSON Pointer. No string
splicing, no quoting accidents — the shell expands them the way shells do.
Without `bg`, the TUI steps aside while your command runs and returns when it
finishes; editors work exactly as you'd hope.

`cmd` accepts any built-in command: `down`, `up`, `expand`, `collapse`,
`expand-recursively`, `collapse-recursively`, `select`, `accept`,
`accept-alternate`, `descend`, `next-sibling`, `prev-sibling`, `page-down`,
`page-up`, `half-page-down`, `half-page-up`, `first`, `last`, `quit`.

User bindings override the defaults, so if you bind `j` to something exotic,
`ite` assumes you meant it.

## Development

You need a Rust toolchain; everything else is `cargo`:

```sh
cargo build            # compile
cargo run -- ~/src     # run against a directory
cargo test             # the test suite (fast, no terminal needed)
cargo clippy --all-targets   # lints; the build is kept warning-free
cargo profile-tui      # headless perf profile: real pty, simulated keys
cargo local-bin        # release-build and install to $XDG_BIN_HOME/ite
```

`cargo profile-tui` (a cargo alias — cargo's answer to npm scripts) spawns
the release binary in a genuine PTY, drives it with keypresses, and prints
per-key latency plus an internal span table. If a keystroke ever feels
sluggish, run it before theorizing; it has already caught one absurd
regression.

The codebase separates decisions from I/O: `app.rs` turns keys into `Effect`
values (print this, run that, quit) and is fully unit-tested without a
terminal; `main.rs` owns the actual terminal and executes effects. If you're
adding behavior, this project is developed **test-first** — write the failing
test, then the code. `AGENTS.md` has the module-by-module map.

For a tight loop, run the tests on save with your watcher of choice:

```sh
cargo watch -x test -x clippy    # cargo install cargo-watch
# or: bacon test                 # cargo install bacon
```

Testing the TUI itself by hand is best done in a real terminal. If you must
script it, use `expect` and be prepared to answer the terminal's
cursor-position query (`ESC[6n`) yourself — ratatui asks at startup and will
wait politely, then give up. See AGENTS.md for the incantation.

This repository uses [Jujutsu](https://github.com/jj-vcs/jj) (`jj commit`,
not `git commit`).

### JSON

To play with a JSON sample directly from a checkout:

```sh
./examples/json-demo.sh
./examples/json-demo.sh --expand all  # start with everything open
```

The script quietly builds the current working copy and opens
[`examples/sample.json`](examples/sample.json). Edit that file to try your own
shapes.