Skip to content

oops-rs/face

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

Strategy Auto-picked when Override
exact Enum-ish field (low cardinality, high coverage) --strategy=exact
prefix Path-like strings (/, ::, or . separators) --strategy=prefix=2
top Free-form strings (top-N by frequency + (other)) --strategy=top=10
bands Continuous numeric (default 5 equal-width bands) --strategy=bands --bands=N
quantiles Heavily skewed numeric distributions --strategy=quantiles
natural One-dimensional Jenks breaks --strategy=natural
similar Algorithmic 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
human Reading. Tree-rendered, colored if TTY, with bars.
json Programmatic consumers. Nested envelope, stable schema.
json-flat jq pipelines that prefer flat iteration.
jsonl-items Downstream record consumers. One item per line.
tsv Spreadsheets, fzf, awk / cut.
csv Spreadsheets. RFC-4180 quoting.
markdown Pasting 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       = 20
color          = "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 = false
scale  = 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

License

Stars

Watchers

Forks

Contributors

Languages