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

qki-cli

v1.1.0

Published

Visual jq alternative with Terminal UI

Readme


The Problem

Every backend developer and DevOps engineer faces the same thing daily:

how to filter nested json jq
jq select where key equals
jq get array element by index
jq extract nested object field

jq is a powerful tool. But its syntax is impossible to memorize.

You work with an API, receive a massive JSON response, and need to extract a single field. You open a browser, google jq syntax, copy an example, adapt it to your structure, make a mistake, google again, try once more...

QKI solves this problem once and for all.


The Solution

cat response.json | qki

An interactive interface opens right in your terminal. You see the JSON tree. Navigate with arrow keys. Expand and collapse nodes. Find the field you need. Press Enter. The correct jq path is automatically copied to your clipboard and printed to stdout.

No more googling. No more memorizing syntax. Visual, fast, precise.


Features

| Feature | Description | |---------|-------------| | Tree navigation | JSON is displayed as an interactive tree with indentation | | Expand/collapse | Objects and arrays can be expanded and collapsed | | Auto jq path generation | A correct jq path is calculated in real time for every node | | Clipboard copy | Press Enter and the path is copied to the system clipboard | | Stdout output | The path is also printed to the terminal for use in pipelines | | Pipe support | Works via cat file.json \| qki | | File support | Works via qki file.json | | Vim keybindings | Navigate with h/j/k/l in addition to arrow keys | | Special chars in keys | Keys with spaces/special characters are automatically quoted | | Cross-platform | Linux, macOS, Windows |


Demo

Input data

{
  "users": [
    {
      "id": 1,
      "name": "Alice",
      "address": {
        "city": "Moscow",
        "zip": "101000"
      },
      "tags": ["admin", "active"]
    },
    {
      "id": 2,
      "name": "Bob",
      "address": {
        "city": "Berlin",
        "zip": "10115"
      },
      "tags": ["user"]
    }
  ],
  "meta": {
    "total": 2,
    "page": 1
  }
}

QKI interface

┌─QKI──────────────────────────────────────────────────────────┐
│>> ▼ root                                                     │
│     ▼ users: [...]                                           │
│       ▶ 0: {...}                                             │
│       ▶ 1: {...}                                             │
│     ▼ meta: {...}                                            │
│       total: 2                                               │
│       page: 1                                                │
│                                                              │
│                                                              │
│                                                              │
├──────────────────────────────────────────────────────────────┤
│ JQ Path: . | Enter: copy & exit | q/Esc: exit               │
└──────────────────────────────────────────────────────────────┘

Navigating to a nested field

Press a few times, then to expand users, then on element 0, then on address, move to city and press Enter:

┌─QKI──────────────────────────────────────────────────────────┐
│   ▼ root                                                     │
│     ▼ users: [...]                                           │
│       ▼ 0: {...}                                             │
│         id: 1                                                │
│         name: "Alice"                                        │
│         ▼ address: {...}                                     │
│>>          city: "Moscow"                                    │
│            zip: "101000"                                     │
│         ▶ tags: [...]                                        │
│       ▶ 1: {...}                                             │
│     ▶ meta: {...}                                            │
├──────────────────────────────────────────────────────────────┤
│ JQ Path: .users[0].address.city | Enter: copy & exit        │
└──────────────────────────────────────────────────────────────┘

Result in clipboard and stdout:

.users[0].address.city

Now you can use it immediately:

cat response.json | jq '.users[0].address.city'
"Moscow"

Installation

From source (recommended)

Requirements: Rust 1.75+ and cargo

git clone https://github.com/filip-mitish/QKI.git
cd QKI
cargo build --release

The binary will be at target/release/qki. Copy it to your PATH:

sudo cp target/release/qki /usr/local/bin/

Or to a local directory:

mkdir -p ~/.local/bin
cp target/release/qki ~/.local/bin/

Make sure ~/.local/bin is in your $PATH:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Via cargo install

cargo install --git https://github.com/filip-mitish/QKI.git

Via NPM

npm install -g qki-cli

The NPM package will automatically compile the binary via cargo build --release during installation. A Rust toolchain is required.


Usage

Basic usage

Read from a file:

qki data.json

Read from stdin (pipe):

cat data.json | qki

Work with APIs:

curl -s https://api.github.com/repos/torvalds/linux | qki

Real-world API examples

GitHub API

curl -s https://api.github.com/users/torvalds | qki

Navigate to public_repos, press Enter, you get:

.public_repos

Docker API

docker inspect nginx | qki

Navigate to the container configuration field you need.

Kubernetes

kubectl get pod my-pod -o json | qki

Visually find the field you need in the pod spec.

AWS CLI

aws ec2 describe-instances | qki

No more remembering the path to Reservations[0].Instances[0].InstanceId.

Terraform

terraform show -json | qki

Navigate through your infrastructure state visually.


Controls

Navigation keys

| Key | Action | |-----|--------| | or k | Move up in the tree | | or j | Move down in the tree | | or l | Expand a node (object/array) | | or h | Collapse a node or jump to parent | | Enter | Copy jq path to clipboard and exit | | q | Exit without copying | | Esc | Exit without copying |

Node icons

| Icon | Meaning | |------|---------| | | Expanded object or array | | | Collapsed object or array | | (space) | Leaf value (string, number, bool, null) |

Current position indicator

The >> symbol to the left of a line shows the currently selected node. The bottom panel displays the full jq path for the current node.


How jq paths are generated

QKI automatically builds a correct jq path for any node regardless of nesting depth and key types.

Regular keys

Keys consisting of letters, digits, and underscores are written with dot notation:

.users
.address.city
.meta.total

Keys with special characters

Keys containing spaces, hyphens, and other special characters are automatically wrapped in quotes:

{
  "my field": "value",
  "some-key": 42,
  "123start": true
}

Generated paths:

."my field"
."some-key"
."123start"

Array elements

Array elements are addressed with square brackets and an index:

.users[0]
.users[1].name
.tags[0]

Nested structures

For deeply nested structures the path is built recursively:

{
  "a": {
    "b": {
      "c": {
        "d": [1, 2, {"e": "deep"}]
      }
    }
  }
}

Path to "deep":

.a.b.c.d[2].e

Mixed structures

{
  "data": [
    {
      "items": [
        {"name": "first"},
        {"name": "second"}
      ]
    }
  ]
}

Path to "second":

.data[0].items[1].name

Architecture

Project structure

QKI/
├── src/
│   └── main.rs          # Main application code
├── bin/
│   └── qki              # NPM wrapper (Node.js)
├── Cargo.toml           # Rust configuration
├── Cargo.lock           # Locked dependencies
├── package.json         # NPM configuration
└── README.md            # Documentation

Dependencies

| Crate | Version | Purpose | |-------|---------|---------| | ratatui | 0.30.0 | Terminal UI framework | | crossterm | 0.29.0 | Cross-platform terminal handling | | serde_json | 1.0.149 | JSON parsing | | arboard | 3.6.1 | System clipboard access |

Internal design

1. Input parsing

QKI accepts JSON from two sources:

  • stdin (via pipe) — detected using io::stdin().is_terminal()
  • file (command line argument) — via fs::read_to_string

Input data is parsed through serde_json into the Value type.

2. Tree construction

The JSON value is recursively converted into a tree of Node structs. Each node contains:

  • Key (k) — field name or array index
  • Value (v) — displayed value
  • Children (c) — vector of child nodes
  • Expansion state (ex) — expanded or collapsed
  • jq path (p) — full jq path to this node
  • Leaf flag (is_leaf) — whether the node is a terminal value

3. Rendering

On each frame the tree is "flattened" into a linear list of visible items (the flatten function). Collapsed nodes skip their children. The list is rendered through the List widget from ratatui with highlighting of the current element.

4. Input handling

Keyboard events are read through crossterm::event. Non-blocking polling is used with a 16ms timeout (60 FPS). Navigation key presses update the current element index. Pressing Enter saves the path and the loop exits.

5. Clipboard copy

On exit via Enter, the arboard library copies the jq path to the system clipboard. If the clipboard is unavailable (headless server, SSH without X11), the path is still printed to stdout.


Comparison with alternatives

QKI vs jq

| Criterion | jq | QKI | |-----------|------|------| | Interactive | No | Yes | | Learning curve | Steep | Zero | | Visual representation | No | Tree-based TUI | | Auto path generation | No | Yes | | Clipboard copy | No | Yes | | Speed for simple tasks | Slow (googling) | Instant | | Filtering & transformation | Yes | No (different purpose) | | Scripting | Yes | Minimal |

QKI does not fully replace jq. QKI helps you find the correct path, which you then use with jq. It is a complementary tool.

QKI vs fx

| Criterion | fx | QKI | |-----------|------|------| | Language | Go/JS | Rust | | jq path generation | No | Yes | | Path copy | No | Automatic | | Binary size | ~10MB | ~3MB | | Startup speed | Medium | Fast | | Dependencies | Node.js (for some features) | None |

QKI vs jless

| Criterion | jless | QKI | |-----------|-------|------| | Language | Rust | Rust | | jq path generation | Partial | Full | | Path copy | No | Automatic | | Search | Yes | No (planned) | | Vim navigation | Yes | Yes | | Focus | Viewing | Path generation |


Pipelines and integration

Usage in scripts

When you press Enter, QKI outputs the path to stdout, which allows it to be used in pipelines:

PATH_EXPR=$(cat data.json | qki)
cat data.json | jq "$PATH_EXPR"

Integration with fzf

cat data.json | jq 'paths | map(tostring) | join(".")' | fzf

QKI is more convenient for this task as it shows context and values.

Shell alias integration

Add to your .bashrc or .zshrc:

alias jqi='qki'
alias jsonpath='qki'

Or a more advanced alias with auto-apply:

jqauto() {
    local path
    path=$(cat "$1" | qki)
    if [ -n "$path" ]; then
        echo "Applying: jq '$path'"
        cat "$1" | jq "$path"
    fi
}

Usage:

jqauto response.json

Integration with curl

apiq() {
    local path
    path=$(curl -s "$1" | qki)
    if [ -n "$path" ]; then
        curl -s "$1" | jq "$path"
    fi
}

Usage:

apiq https://api.github.com/users/torvalds

Building from source

Requirements

  • Rust 1.75 or higher
  • cargo (installed alongside Rust)

Installing Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Debug build

cargo build

Binary: target/debug/qki

Release build

cargo build --release

Binary: target/release/qki

Run tests

cargo test

Check code

cargo clippy

Format code

cargo fmt

NPM package configuration

The NPM package uses a postinstall hook to automatically compile the Rust code:

{
  "scripts": {
    "postinstall": "cargo build --release"
  }
}

When installing via npm install -g qki-cli:

  1. NPM downloads the package
  2. cargo build --release is executed
  3. The binary target/release/qki is created
  4. NPM registers the wrapper bin/qki as a global command
  5. The wrapper launches the compiled Rust binary when invoked

Error handling

QKI implements strict error handling at every stage:

| Situation | Behavior | |-----------|----------| | No data on stdin and no file argument | Prints no data, exit code 1 | | Error reading stdin | Prints err_stdin, exit code 1 | | Error reading file | Prints err_file, exit code 1 | | Invalid JSON | Prints bad json, exit code 1 | | Terminal initialization error | Prints raw err, exit code 1 | | Alternate screen error | Prints altscreen err, exit code 1 | | Clipboard unavailable | Path is printed to stdout only | | Rendering error | Clean TUI exit |

All errors are printed to stderr, keeping stdout clean.


Limitations

  • Does not support filtering or data transformation (that is what jq is for)
  • Does not support search within the tree (planned)
  • Does not support JSON editing
  • Does not support YAML, TOML, or other formats (planned)
  • On very large JSON files (100MB+) parsing delay is possible
  • Clipboard may not work in headless environments without X11/Wayland

Roadmap

v1.1

  • [ ] Search by keys and values (/ to start search)
  • [ ] Syntax highlighting for values (strings, numbers, bools, null in different colors)
  • [ ] Configurable colors via configuration file

v1.2

  • [ ] YAML input support
  • [ ] TOML input support
  • [ ] Multiple path selection
  • [ ] Selected data export

v2.0

  • [ ] Built-in real-time jq filter
  • [ ] Split view: tree on the left, jq result on the right
  • [ ] Path selection history
  • [ ] Bookmarks for frequently used paths

FAQ

Why yet another JSON viewer?

QKI is not a viewer. It is a tool for generating jq syntax. The core value is automatic creation of the correct path and instant clipboard copy.

Why Rust?

  • Fast startup (critical for CLI tools)
  • Minimal memory usage
  • No runtime dependencies
  • Single static binary

Does it work over SSH?

Yes, but the clipboard will not function without X11 forwarding. The path is still output to stdout, so you can use it manually.

Does it work with large files?

QKI loads the entire JSON into memory. For files up to 50MB it works well. For files above 100MB there may be a parsing delay. For gigabyte-sized files it is recommended to pre-filter with jq.

Can I use it together with jq?

That is exactly the intended workflow. QKI finds the path, jq applies it:

# Step 1: find the path visually
cat data.json | qki
# Copied: .users[0].address.city

# Step 2: apply with jq
cat data.json | jq '.users[0].address.city'

Why the name QKI?

Quick Key Inspector — a fast key inspector for JSON structures.


Contributing

Contributions of any kind are welcome. Here are a few areas where help is needed:

  1. Tree search — implementing / for searching by keys and values
  2. Color highlighting — different colors for JSON value types
  3. YAML/TOML support — automatic format detection
  4. Tests — unit tests for path generation and parsing

How to contribute

git clone https://github.com/filip-mitish/QKI.git
cd QKI
cargo build
cargo run -- test.json

Create a branch, make your changes, open a Pull Request.


License

MIT License

Copyright (c) 2026 tripock

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.