Interactive jq killer with a visual TUI interface
Navigate JSON with arrow keys. Auto-generate jq syntax. Copy to clipboard with a single keystroke.
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.
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.
| 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 |
{
"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──────────────────────────────────────────────────────────┐
│>> ▼ root │
│ ▼ users: [...] │
│ ▶ 0: {...} │
│ ▶ 1: {...} │
│ ▼ meta: {...} │
│ total: 2 │
│ page: 1 │
│ │
│ │
│ │
├──────────────────────────────────────────────────────────────┤
│ JQ Path: . | Enter: copy & exit | q/Esc: exit │
└──────────────────────────────────────────────────────────────┘
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"
Requirements: Rust 1.75+ and cargo
git clone https://github.com/filip-mitish/QKI.git
cd QKI
cargo build --releaseThe 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 ~/.bashrccargo install --git https://github.com/filip-mitish/QKI.gitnpm install -g qki-cliThe NPM package will automatically compile the binary via cargo build --release
during installation. A Rust toolchain is required.
Read from a file:
qki data.jsonRead from stdin (pipe):
cat data.json | qkiWork with APIs:
curl -s https://api.github.com/repos/torvalds/linux | qkicurl -s https://api.github.com/users/torvalds | qkiNavigate to public_repos, press Enter, you get:
.public_repos
docker inspect nginx | qkiNavigate to the container configuration field you need.
kubectl get pod my-pod -o json | qkiVisually find the field you need in the pod spec.
aws ec2 describe-instances | qkiNo more remembering the path to Reservations[0].Instances[0].InstanceId.
terraform show -json | qkiNavigate through your infrastructure state visually.
| 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 |
| Icon | Meaning |
|---|---|
▼ |
Expanded object or array |
▶ |
Collapsed object or array |
(space) |
Leaf value (string, number, bool, null) |
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.
QKI automatically builds a correct jq path for any node regardless of nesting depth and key types.
Keys consisting of letters, digits, and underscores are written with dot notation:
.users
.address.city
.meta.total
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 are addressed with square brackets and an index:
.users[0]
.users[1].name
.tags[0]
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
{
"data": [
{
"items": [
{"name": "first"},
{"name": "second"}
]
}
]
}Path to "second":
.data[0].items[1].name
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
| 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 |
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.
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
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.
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.
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.
| 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.
| 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 |
| 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 |
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"cat data.json | jq 'paths | map(tostring) | join(".")' | fzfQKI is more convenient for this task as it shows context and values.
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.jsonapiq() {
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- Rust 1.75 or higher
- cargo (installed alongside Rust)
Installing Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shcargo buildBinary: target/debug/qki
cargo build --releaseBinary: target/release/qki
cargo testcargo clippycargo fmtThe 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:
- NPM downloads the package
cargo build --releaseis executed- The binary
target/release/qkiis created - NPM registers the wrapper
bin/qkias a global command - The wrapper launches the compiled Rust binary when invoked
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.
- 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
- Search by keys and values (
/to start search) - Syntax highlighting for values (strings, numbers, bools, null in different colors)
- Configurable colors via configuration file
- YAML input support
- TOML input support
- Multiple path selection
- Selected data export
- 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
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.
- Fast startup (critical for CLI tools)
- Minimal memory usage
- No runtime dependencies
- Single static binary
Yes, but the clipboard will not function without X11 forwarding. The path is still output to stdout, so you can use it manually.
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.
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'Quick Key Inspector — a fast key inspector for JSON structures.
Contributions of any kind are welcome. Here are a few areas where help is needed:
- Tree search — implementing
/for searching by keys and values - Color highlighting — different colors for JSON value types
- YAML/TOML support — automatic format detection
- Tests — unit tests for path generation and parsing
git clone https://github.com/filip-mitish/QKI.git
cd QKI
cargo build
cargo run -- test.jsonCreate a branch, make your changes, open a Pull Request.
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.
QKI — because googling jq syntax every single day is not work, it is suffering.