webcodr

jwtd - your fully local CLI JWT decoder

A few months ago I looked for a local JWT decoder and didn’t like what I found, so I decided write a small CLI-based decoder that works fully offline. No token will ever leave the machine it’s running on.

Nothing big, just a tiny program written in Go to decode a JWT with go-jose and print the JSON contents to the console. I wanted a short name. JWT decode or jwtd. No fancy name, just a useful tool.

A friend asked if it could verify signatures or decrypt JWE contents. Nope, but that was a perfect opportunity to try Opus 4.6 which was pretty new at the time.

Many iterations and tokens later, the jwtd can do much more. Yes, it’s mostly written by AI agents, but it’s still small enough to understand what’s going on and there’s a pretty large test suite to ensure quality. I use multiple models and regularly check with if something can be improved, make security audits etc. – I will not ship a program I can’t understand or I feel insecure about.

What?

The main focus is still decoding JWTs and show the contents. Of course with colored output and timestamps are human-readable (an expiration warning for exp fields is coming soon). We support a wide variety of JWS signatures, JWE key types and JWE content encryption algorithms.

There’s an interactive mode for tokens and keys, so you didn’t have to clean up your history afterwards. You can also pipe a token into jwtd if you like. We’re also accepting keys via environment variable and even support raw HMAC secrets.

Where?

All major operating systems are supported, but to be honest, my main focus is macOS and Linux. You can get jwtd via Homebrew, the AUR, Fedora COPR, Nix and Scoop. WinGET is coming soon. There are also pre-built deb and rpm packages available on GitHub.

How?

To decode a token just supply it as argument:

$ jwtd eyJhbGciOiJSUzI1NiIs...

Header:
{
  "alg": "RS256",
  "typ": "JWT"
}

Payload:
{
  "sub": "user_2048",
  "admin": true,
  "iat": "2026-07-22T03:00:00Z (1784689200)",
  "exp": "2026-07-22T04:00:00Z (1784692800)",
  "note": null
}

Signature: eyJfX2p3dGRfXyI...

Need to verify a JWS signature?

jwtd --key /path/to/public-key.pem eyJhbGciOiJSUzI1NiIs...

For decryption just use the private key instead of the public key.

Don’t want to pass the key path as parameter? No problem, just use the environment variable JWTD_KEY.

To use the interactive mode just omit the token or the value of the key parameter.

That’s just a small overview, you can find a full usage guide, every installtion option and more on the official website jwtd.sh or on GitHub.

Find things even faster with srchr

Inspired by my last post about rgp and fdp I got the idea to combine both into a more powerful and easier tool called srchr.

It’s a TUI written in Rust and uses similar or the same libraries as rg, fd, fzf and bat, but in one package. There are no external dependencies and it comes for macOS, Linux and Windows in aarch64 and x86-64.

Currently srchr is not available via package managers, but there’s an installation script:

curl -fsSL https://raw.githubusercontent.com/webcodr/srchr/main/install.sh | sh

I will also publish srchr on Homebrew, the AUR and perhaps some other package managers.

Beware, it’s still under development and the UI is functional, but not finished yet. At the moment there is just a hardcoded Tokyp Night inspired color theme, but this will also change in the near future.

srchr on GitHub

Update

It’s now available on Homebrew for macOS and Linux!

brew install webcodr/tap/srchr

Using fd, rg, fzf and bat to find things fast

Modern terminal programs like fd or rg make it really easy to find stuff, but there’s still room for improvement. In this post I will show you how to use fish to write to small functions with fd, rg, fzf and bat to search for file names and file content with an interactive list and even a preview in the terminal.

I don’t know that stuff?

First things first. If you already know fd or the other tools from above, feel free to skip this section.

  • fd is a more user-friendly replacement for find
  • rg or ripgrep is grep an steroids, respecting git ignore files and skips binaries or hidden files
  • fzf is a command-line fuzzy finder. You basically can pipe everything into fzf and just type to filter the output
  • bat is a modern version of cat with syntax highlighting and git integration

Say hi to rgp and fdp

function rgp
    set -l search_term $argv[1]

    if test -z "$search_term"
        echo "Usage: rgp <search_term>"

        return 1
    end

    rg -l $search_term | fzf --preview 'bat --color always {}' --bind 'enter:become("$EDITOR" {+})'
end

function fdp
    set -l search_term $argv[1]

    if test -z "$search_term"
        echo "Usage: fdp <search_term>"

        return 1
    end

    fd -tf $search_term | fzf --preview 'bat --color always {}' --bind 'enter:become("$EDITOR" {+})'
end

That’s all you need. Put them into your fish config file or the fish functions folder.

Usage

rgp something – this will use rg to search recursively search the current directory for files with something inside. The result is piped into fzf. You know can filter the result further with fzf or select a file with the arrow keys and bat will give you a live preview of the file with syntax highlighting. Pressing enter will open the selected file with your default editor (environment variable EDITOR).

fdp works similar, but looks for file names instead of the file content and also pipes the result to fzf for further filtering, preview or opening the selected file.

Don’t like fish? Here’s a version that works with bsh or zsh.

Note: the local keyword is not POSIX-compliant. If you need that, just remove it, but this makes search_term global, so use with care.

rgp() {
    local search_term=$1

    if [ -z "$search_term" ]; then
        echo "Usage: rgp <search_term>"
        
        return 1
    fi

    rg -l "$search_term" | fzf --preview 'bat --color always {}' --bind 'enter:become("$EDITOR" {+})'
}

fdp() {
    local search_term=$1

    if [ -z "$search_term" ]; then
        echo "Usage: fdp <search_term>"
        
        return 1
    fi

    fd -tf "$search_term" | fzf --preview 'bat --color always {}' --bind 'enter:become("$EDITOR" {+})'
}

Fix Omarchy Gaming (Vulkan)

After setting up my new notebook, I wanted to try some games on Steam. Works like a charm on my mini PC with Omarchy, but not this time. After starting a game, it takes a few seconds and nothing, the game silently crashes.

After fiddling and searching around, Proton logging etc., still no cause in sight. Then I tried Doom 2016 and it worked! But why? Doom starts with an OpenGL renderer, after switching to Vulkan it behaves like the other games. So there’s something wrong with Vulkan?

Vulkan Tools (Arch package vulkan-tools) to the rescue! After running vulkaninfo it became pretty clear that something important was missing: the Radeon Vulkan driver. No driver, no Vulkan. No Vulkan, no Proton …

Thankfully it’s easy to fix, just install the missing packages:

sudo pacman -S vulkan-radeon mesa mesa-vdpau lib32-vulkan-radeon lib32-mesa

It should work immediately after the installation. There’s already a GitHub issue with a pull request to solve this, but it’s open for a weeks now and it’s not clear, when the fix will be merged and released.

I don’t know why it’s no problem with my other PC, but I have installed Omarchy the old way on this machine (Arch Install and the Omarchy setup script). My best guess is that the bug was introduced with Omarchy’s ISO setup.

How does it run?

Well, that Ryzen AI MAX+ 390 is an absolute beast. Doom 2016 runs with over 100 fps with max settings on the internal display (2880x1800) and still far beyond 60 fps in 4K on my main monitor. Just don’t use fullscreen mode, it flickers like hell (known problem of Doom 2016 with the Vulkan renderer).

The HP ZBook Ultra no gaming notebook, but it has a really powerful APU and is still a quite compact and light device on the level of a 14" MacBook Pro. I don’t want a 250 W 4 kg behemoth of a notebook.

Hyprland Trackpad Tips & Tricks

For first time in my life I bought a PC notebook. I didn’t even consider to boot in the pre-installed Windows 11 and installed Omarchy right away.

The HP ZBook has a quite good trackpad, even compared to MacBooks, but some things seemed off. No right-click with two fingers, instead it would only work in the lower right corner. And I absolutely hate tapping, I want real clicks.

Turns out, it’s not the hardware. It’s all configurable in Hyprland. In Omarchy you can find the settings in .config/hypr/input, sub-category input:touchpad.

Here are some handy options to tweak the settings:

input {
  touchpad {
    # Enable two-finger clicks for right-clicking
    clickfinger_behaviour = true

    # Disable tapping
    tap-to-click = false

    # Enable natural scrolling
    natural_scroll = true

    # Disable the trackpad while typing (accidental tapping etc.)
    disable_while_typing = true
  }
}

There is even more like tapping maps, middle button emulation etc. – you can find all options here.