Skip to content

Repository files navigation

face

Fold And Cluster Entries — a Unix-style CLI that groups, ranks, and pages structured command output.

face is a standalone result organizer. Query tools — rg, cargo, gh, your CI logs, your CSV exports, your LLM agent's JSON — answer what are the results?. face answers the next question: how should this result set be grouped, ranked, and paged?

One binary. One job. No daemon, no UI, no config file required.

$ rg TODO --json | face
face: 412 items
[1] src/cli.rs 38 ████████
[2] src/core.rs 27 █████▋
[3] src/parser.rs 19 ████
... 44 more clusters (face --cluster N to drill)

Why face

Most tools stop at here is a flat list of N matches. The moment N grows past a screen, the next question is always the same:

  • "Which file has the most hits?"
  • "Show me only the high-confidence ones."
  • "Group by repo, then by severity, then page through the top bucket."

That step is face. Pipe any structured stream in, get a grouped, paged summary out. Drill into any cluster by number or by ID. Save the envelope and re-process it without re-running the upstream command. No new query language to learn — face reads the data and decides.

Highlights

  • Zero-config, zero-flag by default.cmd | face always produces something useful. Every flag exists to override an automatic decision — none are required.
  • Reads JSON, JSONL, CSV, and TSV with sniffing. Force the parser with --format-in if you must.
  • Auto-detects the items array, the score field, the numeric distribution preset, and a per-axis grouping strategy.
  • Six numeric and categorical strategies plus five algorithmic string-similarity flavors (token, n-gram, edit, LSH, simhash). No neural black-box clustering.
  • Nested grouping to any depth via --by FIELD,FIELD,... or repeatable --within FIELD.
  • Self-consumable JSON envelope.face --format=json | tee and re-pipe for instant redrills — no auto-cache, no daemon, no implicit state.
  • Seven output formats — human tree, JSON (nested or flat), JSONL items, TSV, CSV, Markdown — TTY-aware coloring, NO_COLOR honored.
  • Inline --interactive picker for the moments you want to click around without standing up a TUI.

The full design rationale lives in docs/design.md.


Install

With Homebrew, after the tap has been updated by a GitHub release:

brew install oops-rs/tap/face

From crates.io, after the crates are published:

cargo install face

Or build from source today:

# Latest from the main branch
cargo install --git https://github.com/oops-rs/face --bin face
# Or from a local clone
git clone https://github.com/oops-rs/face
cd face
cargo install --path crates/face-cli

face targets edition 2024 / resolver 3, so a recent stable Rust toolchain (1.85+) is required to build from source.

Noteface is at 0.1.0. The CLI surface is implemented end-to-end and stable enough for daily use, but the JSON envelope's exact shape may still evolve before 1.0. See Status for details.


Quick tour

Group anything

rg TODO --json | face
cargo test --message-format=json | face
gh pr list --json number,title,labels --limit 200 | face
psql -c "SELECT ..." --csv | face
cat results.tsv | face

No flags. face sniffs the format, finds the items, picks a grouping field, and prints a tree. With --verbose you get a one-line provenance summary on stderr explaining what it decided:

$ rg TODO --json | face --verbose
→ 412 items by data.path.text (auto, from rg JSONL)
face: 412 items
...

Drill into a cluster

# 1-based ordinal — matches the [N] labels in human output
face --cluster 1 < results.json
# Canonical comma form
face --cluster file:src/cli.rs < results.json
# Or as repeatable axis=value pairs
face --cluster file=src/cli.rs --cluster score=excellent < results.json

When the destination is a pipe, drilled output defaults to JSONL items (one record per line, no envelope) so jq, fzf, awk, and friends just work:

face --cluster 1 < results.json | jq '.path'
face --cluster file:src/cli.rs < results.json | fzf

Pass --format=human to force the tree view through a pipe, or --format=json to get the full envelope back.

Two-axis grouping

# Group by file, band the score within each file
face --by file --within score --bands 4 < results.json
# Chain --by axes (comma is "next nesting level")
face --by repo,file,kind < results.json

Auto-detection runs per axis, so each --within without an explicit strategy picks its own.

Save once, drill many

face --format=json produces a self-consumable envelope. Pipe it back in and face re-processes the cached clusters instead of re-running upstream:

cmd | face --format=json > /tmp/face.json
face --cluster file:src/cli.rs < /tmp/face.json
face --cluster file:src/cli.rs --page 2 < /tmp/face.json
face --format=markdown < /tmp/face.json
face --within tag < /tmp/face.json # re-cluster

No auto-cache, no daemon — just a shell redirect you control.

Sniff before committing

cmd | face --schema # describe input shape, no grouping
cmd | face --explain # full reasoning for what face would decide
cmd | face --verbose # provenance line on stderr alongside normal output

Example:

$ rg TODO --json | face --explain
input:
format jsonl (sniffed: 412 newline-separated JSON objects)
items . (input is a flat record stream)
score (none detected)
strategy:
axis 1 data.path.text (auto: most-discriminating string field, cardinality=47)
output:
format human (default)
color auto (stdout is a TTY)

Inline picker

cmd | face --interactive

Arrow keys to navigate, Enter to drill, Space to multi-select at the leaf level, Esc / q to back out. When stdout isn't a TTY, face falls back silently to the configured non-interactive format — scripts that conditionally pass --interactive work uniformly in CI.


Strategies

Detected per axis from the data; override with --strategy.

StrategyAuto-picked whenOverride
exactEnum-ish field (low cardinality, high coverage)--strategy=exact
prefixPath-like strings (/, ::, or . separators)--strategy=prefix=2
topFree-form strings (top-N by frequency + (other))--strategy=top=10
bandsContinuous numeric (default 5 equal-width bands)--strategy=bands--bands=N
quantilesHeavily skewed numeric distributions--strategy=quantiles
naturalOne-dimensional Jenks breaks--strategy=natural
similarAlgorithmic string clustering--strategy=similar=token (or ngram, edit, lsh, simhash)

Numeric clusters render highest-to-lowest by default so score-like data leads with the strongest bucket. Cluster labels come from the data itself — value ranges for numeric strategies, the field value for enum strategies, the shared prefix for prefix strategies.


Output formats

--format=Use it for
humanReading. Tree-rendered, colored if TTY, with bars.
jsonProgrammatic consumers. Nested envelope, stable schema.
json-flatjq pipelines that prefer flat iteration.
jsonl-itemsDownstream record consumers. One item per line.
tsvSpreadsheets, fzf, awk / cut.
csvSpreadsheets. RFC-4180 quoting.
markdownPasting into PRs, issues, docs.

-j is shorthand for --format=json. Color follows --color={auto,always,never} (default auto) and respects NO_COLOR and CLICOLOR_FORCE.

When --cluster is set, no --format was passed, and stdout is a pipe, face automatically emits jsonl-items for the drilled page so jq pipelines stay clean.


Configuration

Optional. If absent, every default is built into the binary. Lookup order:

  1. $FACE_CONFIG (full path)
  2. $XDG_CONFIG_HOME/face/config.toml
  3. ~/.config/face/config.toml
[output]
default_format = "human"per_page = 20color = "auto"
[detect]
score_candidates = ["score", "rank", "relevance", "confidence", "bm25"]
items_candidates = ["items", "results", "data", "hits", "matches"]
[strategy]
default_bands = 5
[interactive]
save_threshold = 200
[presets.my-search]
score = "relevance"invert = falsescale = 1.0

Precedence:

CLI flag > $FACE_* env > config.toml > built-in default

--verbose provenance and --explain say which layer won when something looks unexpected. The full schema lives in docs/design.md §13.


Composition recipes

fzf as the picker, face as the cluster engine

ff() {
local saved
saved=$(mktemp)"$@"| face --format=json >"$saved"local id
id=$(face --format=tsv <"$saved"| fzf --with-nth=2,3 | cut -f1)
[ -n"$id" ] && face --cluster "$id"<"$saved"
}
ff rg TODO --json
ff cargo test --message-format=json

Spreadsheet handoff

cmd | face --format=csv > clusters.csv
open clusters.csv # macOS
xdg-open clusters.csv # Linux

PR / issue paste

cmd | face --format=markdown | pbcopy # macOS
cmd | face --format=markdown | xclip # Linux

Project layout

crates/
face-cli/ binary `face` + thin CLI library
face-core/ detection, grouping, clustering, paging, envelope
docs/
design.md canonical CLI / data-model design reference

Status

0.1.0. The full design surface in docs/design.md is implemented end-to-end:

  • JSON / JSONL / CSV / TSV input with sniffing and --format-in override
  • Auto-detection: items array, score field, preset, per-axis strategy
  • Strategies: exact, prefix, top, bands, quantiles, natural, and similar (token / n-gram / edit / LSH / simhash)
  • Nested grouping via --by FIELD,FIELD,... and repeatable --within FIELD
  • Cluster addressing: 1-based ordinal, canonical comma ID, or axis=value
  • Pagination via --page / --per-page
  • Self-consumable envelope: face --format=json round-trips through stdin
  • Output formats: human, json, json-flat, jsonl-items, tsv, csv, markdown
  • Inline --interactive picker (the only carve-out from "no UI")
  • Optional TOML config under $XDG_CONFIG_HOME/face/

The CLI surface is considered stable enough for daily use. The 0.x line means the JSON envelope's exact shape may still shift if a downstream consumer surfaces a need before 1.0.


Contributing

Issues and PRs welcome. Before opening a PR:

cargo test --workspace --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo fmt --all

The design doc is the source of truth for the CLI surface; when in doubt, match it.


License

MIT — see LICENSE.

About

Fold And Cluster Entries — a Unix-style CLI for grouping, paging, and summarizing structured command output.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages