npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ahkohd/oyo

v0.1.30

Published

A step-through diff viewer for the terminal.

Readme

oyo

A diff viewer that works two ways: step through changes or review a classic scrollable diff.

https://github.com/user-attachments/assets/0f43b54b-69fe-4cf3-9221-a7749872342b

oyo extends traditional diffs with an optional step-through mode. Use it like a normal diff viewer with scrolling and hunk navigation, or step through changes one at a time and watch the code evolve. You can switch between both modes at any time.

Two ways to use oyo

1. Classic diff (scroll-only)

Review all changes at once, scroll freely, and jump between hunks, just like a traditional diff viewer.

  • Scroll the full diff
  • Jump between hunks
  • No stepping required

Enable with:

  • oy --no-step
  • Toggle in-TUI with s
  • Set stepping = false in config

2. Step-through review (default)

Apply changes incrementally and watch the file transform from old to new.

  • Step change-by-change
  • See precise evolution of the code
  • Useful for large refactors or careful reviews

oyo does not replace classic diffs, it adds a new way to review them.

Features

  • Classic diff mode (no-step) Scroll the full diff with hunk navigation, no stepping required
  • Step-through navigation Move through changes one at a time with keyboard shortcuts
  • Hunk navigation Jump between groups of related changes in both modes
  • Four view modes:
    • Unified: Watch the code morph from old to new state
    • Split: See old and new versions with synchronized stepping
    • Evolution: Watch the file evolve, deletions simply disappear
    • Blame: Per-line git blame gutter (opt-in)
  • Inline review comments: Add/update/remove line and hunk comments across views; printed to stdout on quit
  • Word-level diffing: See exactly which words changed within a line
  • Multi-file support: Navigate between changed files with preserved positions
  • Search: Regex search with to jump between matches
  • Syntax highlighting: Toggle on/off for code-aware coloring (auto-enabled in no-step mode)
  • Blame hints: One-shot or toggle blame previews while stepping (opt-in)
  • Command palette: Search for commands and files without leaving the diff
  • Line wrap: Toggle wrapping for long lines
  • Fold unchanged blocks: Toggle to collapse long context sections
  • Animated transitions: Smooth fade in/out animations as changes are applied
  • Playback: Automatically step through all changes at a configurable speed
  • Git integration: Works as a git external diff tool or standalone
  • Commit picker: Browse recent commits and pick ranges interactively (oy view)
  • Themes: Built-in themes plus .tmTheme syntax themes (configurable, with light/dark variants)
  • Configurable: XDG config file support for customization

Installation

npm (macOS, Linux)

npm i -g @ahkohd/oyo

Pi package (/diff, /review commands)

pi install npm:@ahkohd/pi-oyo

Homebrew (macOS, Linux)

brew install ahkohd/oyo/oy

AUR (Arch Linux)

paru -S oyo

Cargo

cargo install oyo --locked --force

Usage

Classic diff (scroll-only)

oy --no-step
# or toggle in-app with `s`

Step-through diff

oy

Compare files

oy old.rs new.rs

Compare a file against HEAD

oy path/to/file.rs

Runs a working-tree vs HEAD diff for that file (like git diff path/to/file.rs).

Commit picker

oy view

View modes

oy old.rs new.rs --view split
oy old.rs new.rs --view evolution

Autoplay

oy old.rs new.rs --autoplay
oy old.rs new.rs --speed 100

Git ranges

oy --range HEAD~1..HEAD
oy --range main...feature

Staged changes

oy --staged

Review output

# default: prints review comments to stdout on quit
oy --no-step

# also write review comments to a file
oy --no-step --review-output-file /tmp/oy-review.txt

# file-only output (for tool integrations)
oy --no-step --review-output-file /tmp/oy-review.txt --no-print-review

# ephemeral review session (disable autosave/restore)
oy --no-step --no-review-persist

# start a fresh persisted review session (clear saved state for this diff)
oy --no-step --clear-review-session

Git Integration

Recommended (git difftool)

git difftool -y --tool=oy

~/.gitconfig:

[difftool "oy"]
    cmd = oy "$LOCAL" "$REMOTE"

[difftool]
    prompt = false

[alias]
    d = difftool -y --tool=oy

Note: keep your pager (less, moar, moor) for git diff. Do not set core.pager or interactive.diffFilter to oy.


Jujutsu (jj)

[ui]
paginate = "never"
diff-formatter = ["oy", "$left", "$right"]

[diff-tools.oy]
command = ["oy", "$left", "$right"]

Keyboard Shortcuts

Vim-style counts: Most navigation commands support count prefixes (e.g., 10j moves 10 steps forward, 5J scrolls down 5 lines).

| Key | Action | |-----|--------| | / j | Next step (scrolls in no-step mode; moves file selection when focused) | | / k | Previous step (scrolls in no-step mode; moves file selection when focused) | | / l | Next hunk (scrolls in no-step mode) | | / h | Previous hunk (scrolls in no-step mode) | | b | Jump to beginning of current hunk (scrolls in no-step mode) | | e | Jump to end of current hunk (scrolls in no-step mode) | | gb | Blame current step (opt-in, step mode) | | p / P | Peek change (modified → old → mixed) / Peek old hunk | | y / Y | Yank line/hunk to clipboard | | / | Search (diff pane, regex) | | n / N | Next/previous match | | :line / :h<num> / :s<num> | Go to line / hunk / step | | < | First applied step | | > | Last step | | gg | Go to start (scroll-only in no-step mode) | | G | Go to end (scroll-only in no-step mode) | | Space / B | Autoplay forward/reverse | | Tab | Cycle view mode | | Shift+Tab | Cycle view mode (reverse) | | K | Scroll up (supports count) | | J | Scroll down (supports count) | | H | Scroll left (supports count) | | L | Scroll right (supports count) | | 0 | Start of line (horizontal) | | $ | End of line (horizontal) | | Ctrl+u | Half page up | | Ctrl+d | Half page down | | Ctrl+g | Show full file path | | gy / gY | Copy patch for line/hunk | | Ctrl+p | Command palette | | Ctrl+Shift+p | Quick file search | | z | Center on active change | | Z | Toggle zen mode | | a | Toggle animations | | w | Toggle line wrap | | f | Toggle context folding | | t | Toggle syntax highlight | | E | Toggle evo syntax (context/full) | | c / C | Next/prev conflict | | m / M | Add/update line/hunk comment | | x / X | Remove line/hunk comment | | Ctrl+x | Clear all comments | | s | Toggle stepping (no-step mode) | | S | Toggle strikethrough | | r | Replay last step (count supported) | | R | Refresh all files | | Ctrl+f | Toggle file panel | | Enter | Focus file list | | ] | Next file (supports count) | | [ | Previous file (supports count) | | + / = | Increase speed | | - | Decrease speed | | ? | Toggle help | | q / Esc | Quit (prints comments if any; closes help/path popups) |

Clipboard support uses system tools: pbcopy (macOS), wl-copy / xclip / xsel (Linux), clip (Windows). Search is case-insensitive regex; invalid patterns fall back to literal matching.

Configuration

Create a config file at ~/.config/oyo/config.toml:

[ui]
auto_center = true          # Auto-center on active change (default: true)
overscroll = false         # EOF overscroll when centering (opt-in)
topbar = true               # Show top bar in diff view (default: true)
view_mode = "unified"       # Default: "unified", "split", "evolution", or "blame"
line_wrap = false           # Wrap long lines (default: false, uses horizontal scroll)
fold_context = "off"        # "off", "on", or "counts"
scrollbar = false           # Show scrollbar (default: false)
strikethrough_deletions = false # Show strikethrough on deleted text
gutter_signs = true         # Show +/- sign column (unified/evolution)
stepping = true             # Enable stepping (false = no-step mode)

[navigation.wrap]
step = "none"               # "none" | "step" | "file"
hunk = "none"               # "none" | "hunk" | "file"
# [ui.diff]
# bg = false                # Full-line diff background (true/false)
# fg = "theme"              # "theme" or "syntax"
# highlight = "text"        # "text" | "word" | "none"
# max_bytes = 16777216      # Defer diffing above this size (bytes)
# full_context_max_bytes = 2097152  # Full-context render up to this size (bytes)
# defer = true              # Defer large diffs and compute in background
# idle_ms = 250             # Idle time before background diff compute
# extent_marker = "neutral" # "neutral" or "diff"
# extent_marker_scope = "progress" # "progress" or "hunk"
# extent_marker_context = false # show extent markers on unchanged lines
# [ui.blame]
# enabled = false           # Show git blame hints (opt-in)
# mode = "one_shot"         # "one_shot" or "toggle"
# hunk_hint = true          # Show blame hint when jumping to a hunk
# [ui.time]
# mode = "relative"         # "relative" | "absolute" | "custom"
# format = "[year]-[month]-[day] [hour]:[minute]" # Used when mode = "custom"
# [ui.split]
# align_lines = false       # Insert blanks to keep split panes aligned
# align_fill = "╱"          # Fill character for aligned blanks (empty = no marker)
# [ui.evo]
# syntax = "context"         # "context" (non-diff only) or "full" (diff + context)
# [ui.unified]
# modified_step_mode = "mixed" # "mixed" or "modified" (unified pane only)
# theme = { name = "tokyonight" } # Built-ins listed below
primary_marker = "▶"        # Marker for primary active line (single-width char recommended)
primary_marker_right = "◀"  # Right pane marker (optional, defaults to ◀)
extent_marker = "▌"         # Left pane extent marker (Left Half Block)
extent_marker_right = "▐"   # Right pane extent marker (optional, defaults to ▐)
zen = false                 # Start in zen mode (minimal UI)

[ui.syntax]
mode = "on"                # "on" or "off"
# theme = "tokyonight"     # builtin name or "custom.tmTheme" (from ~/.config/oyo/themes)
#                           # default: ui.theme.name, fallback to "ansi"
# [ui.syntax.warmup]
# active_lines = 100       # lines per tick while navigating
# pending_lines = 300      # lines per tick while catching up to a pending checkpoint
# idle_lines = 1000        # lines per tick while idle
# debounce_ms = 80         # wait before warming a new viewport target

[playback]
speed = 200                 # Autoplay interval in milliseconds
autoplay = false            # Start with autoplay enabled
animation = false           # Enable fade animations
animation_duration = 150    # Animation duration per phase (ms)
auto_step_on_enter = true   # Auto-step to first change when entering a file
auto_step_blank_files = true # Auto-step when file would be blank at step 0 (new files)

[files]
panel_visible = true        # Show file panel in multi-file mode
panel_width = 30            # File panel width (columns)
counts = "active"           # Per-file +/- counts: active, focused, all, off

[no_step]
auto_jump_on_enter = true   # Jump to first hunk when entering a file in no-step mode

[comments.mentions]
file_scope = "repo"         # "changed" | "repo" (git-aware via ls-files)
finder = "auto"             # "auto" | "builtin" | "fzf"

Example config:

[ui]
zen = false
auto_center = true
overscroll = false
view_mode = "unified"
gutter_signs = false
topbar = true

[ui.blame]
enabled = true

[ui.theme]
name = "tokyonight"

[ui.diff]
fg = "syntax"
bg = true
highlight = "text"
max_bytes = 16777216
full_context_max_bytes = 2097152
defer = true
idle_ms = 250
extent_marker = "diff"
extent_marker_scope = "hunk"

[ui.syntax]
mode = "on"

[ui.evo]
syntax = "full"

[ui.split]
align_lines = true

[playback]
speed = 200
animation = true
animation_duration = 150
autoplay = false

[comments.mentions]
file_scope = "repo"
finder = "auto"

Config is loaded from (in priority order):

  1. $XDG_CONFIG_HOME/oyo/config.toml
  2. ~/.config/oyo/config.toml
  3. Platform-specific (e.g., ~/Library/Application Support/oyo/config.toml on macOS)

Theme and syntax theme configuration is documented in THEME.md.

diff preview

Diff styling previews are available in DIFF_PREVIEWS.md.

How It Works

Stepping applies changes in file order. The view renders applied changes, highlights the active change, and keeps pending changes muted.

Development

# Build everything
cargo build

# Run tests
cargo test

# Run CLI in development
cargo run --bin oy -- old.rs new.rs