For years while working with AI logs, I kept staring at raw LLM request and response payloads - the exact JSON that goes to the model and comes back. That’s where the gold is. cat raw-response.json | jq is fine, until it isn’t. There are visual JSON tools, and the viewers built into various systems, but they’re all different. The interesting bit is always message.content, which is itself a JSON string, which jq happily prints as one long escaped line of \"name\": \"Ada\".... So you decode it by hand. Again. Every time.
I had disparate ways of managing this but never bothered to bring a cohesive single thing together.
So I built a small thing for myself, used it for a bit, and yesterday made it public: inspect-json. It’s on npm too.
npm i -g inspect-json-cli
What it actually does
It’s not an interactive viewer. It’s a terminal renderer that detects the shape of an AI log, decodes the JSON hiding inside JSON strings, and prints the useful parts first. Point it at a captured response and it knows what a chat completion looks like:
$ inspect-json examples/structured-output-response.json
LLM Response
model: nvidia/nemotron-3-nano-omni
usage: 195 total / 58 prompt / 137 completion / 101 reasoning
choice[0]
finish: stop
role: assistant
content: json object
{
"name": "Ada Lovelace",
"born": 1815,
"occupation": "mathematician"
}
That content: json object block is the whole reason it exists. Filtering JSON properties is easy, mixed content not so much. The model returned structured output as a string inside the response JSON, and the tool unwrapped it without me asking. Tool calls get the same treatment - the arguments string is decoded and printed as a compact get_weather({location: "Paris, France"}) signature instead of an escaped blob.
It has request and response lenses, picks the right one by scoring the input, and you can force it with --strategy. There’s a generic JSON fallback for everything else, a --verbose flag when you want the ids and raw metadata back, and --raw to switch off the JSON-in-JSON decoding entirely. Reads from a file or stdin. Color on by default for a TTY, off when piped.
Why this and not one of the existing tools
There are plenty of great JSON tools, and several that do slices of this very well. But each one does a piece. I wanted one thing that defaults the way I want: filter to the parts of an AI log I care about, decode the nested JSON automatically, and answer the questions I’m actually asking - what model, what messages, what tools were exposed, what came back, what was the token usage. It fits my flow because I built it around my flow. That’s the whole pitch. It’s a personal tool I’m making public, not a competitor to anything.
The zero-dependency thing again
Same discipline as kodr: no runtime dependencies, no dev dependencies, Node built-ins only. This is becoming a habit and I’m leaning into it.
The reasoning is boring and practical. A CLI like this is small enough that pulling in a JSON-pretty library, a color library, and an arg parser would mean more lines of package-lock than actual code - plus a standing maintenance tax of version bumps, advisories, and the occasional supply-chain scare, forever, for a tool that fundamentally just walks an object and prints it. Node 22 gives me everything I need: argument parsing, ANSI codes are just strings, and JSON.parse in a try/catch is the entire “is this JSON-in-JSON” detector. The code stays simple and self-contained, the whole thing is auditable in one sitting, and there’s nothing to keep up to date. (Biome does formatting and linting, but that’s a dev tool on my PATH, not a dependency of the package.)
There’s a fair argument that you give up niceties this way. For a focused tool like this, I’ll take the zero-maintenance, read-it-in-one-go tradeoff every time.
The examples in the repo are real - captured request/response pairs from LM Studio running a local reasoning model, which is why they carry reasoning_content and a reasoning token count. They double as the test fixtures. Of course they do.
Have a poke around if you read a lot of AI logs. And if it doesn’t default the way you want - well, it’s small enough to fork. I am sure there are many scenarios I have missed!
Links:
- Repo: paulkohler/inspect-json-cli
- npm: inspect-json-cli
- The strategies (request/response lenses): src/strategies.mjs
- JSON-in-JSON decoding: src/transformers.mjs
- Worked examples: examples/README.md