Three Small CLI Tools I Built to Make Claude Code Feel Less Like a Black Box
The most common complaint I hear from teammates trying Claude Code for the first time is not "the model is bad". It is "I do not know what the model is doing". The session is opaque — a long scrollback of edits, reads, and bash calls, with no way to skim it or to see at a glance where the last 30 minutes went.
I built three small CLI tools to fix that. None of them are clever. All of them are under 200 lines. The first one I should have built years ago.
1. cc-tail — stream the last N tool calls
The first thing every new Claude Code user wants is a way to see the recent activity. The scrollback is too long to read every time, and the inline UI does not give a summary view. So I wrote a 50-line script that tails the JSONL log under ~/.claude/projects/:
#!/usr/bin/env bash
# cc-tail — show the last N tool calls from the active project
N="${1:-10}"
LATEST=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
[ -z "$LATEST" ] && { echo "no session log"; exit 1; }
tail -n "$N" "$LATEST" | python3 -c '
import sys, json
for line in sys.stdin:
try:
e = json.loads(line)
t = e.get("type", "")
if t == "tool_use":
name = e.get("name", "?")
inp = e.get("input", {})
arg = inp.get("file_path") or inp.get("command") or str(inp)[:60]
print(f"[{e.get(\"timestamp\",\"\")[:19]}] {name}: {arg}")
elif t == "user":
content = e.get("message", {}).get("content", "")
if isinstance(content, str):
print(f"[{e.get(\"timestamp\",\"\")[:19]}] user: {content[:80]}")
except Exception:
pass
'
Run it as cc-tail 20 and you get a readable trace of the last 20 events. It is the difference between "I think the model is doing something weird" and "ah, it is grepping for the wrong path".
2. cc-cost — session cost by project
The Anthropic API returns token counts; the CLI shows them per turn. I wanted the session total, grouped by project. So:
#!/usr/bin/env bash
# cc-cost — total cost of the current Claude Code session
LATEST=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
python3 -c "
import json
in_t = out_t = cache_r = cache_w = 0
with open('$LATEST') as f:
for line in f:
try: e = json.loads(line)
except: continue
u = e.get('message', {}).get('usage', {})
in_t += u.get('input_tokens', 0)
out_t += u.get('output_tokens', 0)
cache_r += u.get('cache_read_input_tokens', 0)
cache_w += u.get('cache_creation_input_tokens', 0)
# Sonnet 4.6 numbers as of writing
cost = in_t*3e-6 + out_t*15e-6 + cache_r*3e-7 + cache_w*3.75e-6
print(f'in: {in_t:,} out: {out_t:,} cache_r: {cache_r:,} cache_w: {cache_w:,}')
print(f'approx cost: \${cost:.2f}')
"
It is approximate. The point is that it is honest. After running it once you stop guessing and start noticing which sessions are the expensive ones.
3. cc-where — where did the model touch the file system?
Most regressions I have debugged were not "the model wrote the wrong code". They were "the model wrote the right code in the wrong place". A 30-line cc-where:
#!/usr/bin/env bash
# cc-where — list the unique file paths the latest session touched
LATEST=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
grep -oE '"file_path":"[^"]+"' "$LATEST" | sort -u
That is the whole tool. Pipe it through wc -l and you get a number; pipe it through xargs -I{} sh -c 'echo "$(git log -1 --format=%h {}) {}"' and you get a useful audit.
What these tools do not do
They do not write to the session, do not change the model, do not require API keys. They are pure observers over the existing JSONL log. That is the point — most "Claude Code is opaque" complaints are really "I do not have a small read-only tool over the data that is already on disk". Once you have one, the opacity goes away.
The hard part is the JSONL format is technically internal and changes between versions. These tools break once a quarter, and I rewrite them in 10 minutes when that happens. Worth it.
Tags: claudecode, codex, cli, devtools, observability, ai
Top comments (0)