agentleFS
mthlimthli/skills/statusline-vocab/SKILL.md

statusline-vocab

Skill19 starsChanged 3 months agoMITView on GitHub

Manage the statusline-vocab feature — a `Stop` hook picks one English word worth learning from each conversation and renders `{emoji} {word} /IPA/ pos. {translation}` on the Claude Code statusline (translation language configurable; Chinese by default). Trigger on "/statusline-vocab" or when the user asks to install / configure / check / uninstall / switch language / debug the vocab statusline. Do NOT auto-trigger — explicit invocation only.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Put a "word of the conversation" segment on the Claude Code statusline so the user passively learns English vocabulary from their own work.

    line 13
  2. Distinctive:

    Before doing anything destructive, verify the prerequisites.

    line 49
  3. Very specific:

    After inserting, run Step 4 to confirm.

    line 119
  4. Distinctive:

    If the user asks for a tunable that requires more than a one-line edit (e.g.

    line 188
  5. Very specific:

    No word ever appears: Check ~/.claude/vocab/extract.log for errors.

    line 208
  6. Very specific:

    Hook fires twice on every turn: Look for duplicate entries in ~/.claude/settings.json under hooks.Stop — possible if the file was …

    line 220
  7. Very specific:

    If the user wants a richer experience later (Anki export, daily review prompt, frequency-based spaced repetition over history.jsonl), build …

    line 234

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

statusline-vocab

Put a "word of the conversation" segment on the Claude Code statusline so the user passively learns English vocabulary from their own work. Three moving parts:

  1. ~/.claude/hooks/vocab/extract.sh — Stop hook. Each time the assistant finishes a turn, this hook reads the last ~30 transcript entries, calls claude -p --model claude-haiku-4-5 to pick one English word worth learning (lemma + US IPA + POS + translation + emoji; target language configurable), and writes JSON to ~/.claude/vocab/current.json. A 3-minute cooldown prevents the word from churning every single turn within one conversation.
  2. ~/.claude/vocab/current.json + ~/.claude/vocab/history.jsonl — current word plus an append-only wordbook history of everything that's been picked.
  3. ~/.claude/statusline-command.sh — reads current.json and appends a vocab segment to the statusline output.

Rendered example (with default Chinese; swap to any language via --lang):

➜ skills git:(develop) · Opus 4.7 ctx:12% · 📝 transcript /ˈtrænskrɪpt/ n. 誊本,逐字记录

When to use this skill

The user has typed /statusline-vocab or otherwise referenced this feature. Likely intents:

  • Install on a fresh machine — set everything up (optionally with --lang)
  • Status / diagnose — "why isn't a word showing up?", "what's installed?", "what language am I on?"
  • Switch language — "translate to Spanish", "use Japanese definitions" → install.py config --lang …
  • Reconfigure other knobs — cooldown, model, selection criteria (edit extract.sh directly)
  • Uninstall — remove the hook and tell the user how to clean the statusline

Pick the right subcommand below based on which of these the user wants. If unclear, run status first so the conversation has a shared baseline.

Workflow

Step 1 — Detect environment

Before doing anything destructive, verify the prerequisites. The installer script does this too, but it's worth knowing what it checks so you can give a useful error if something is missing:

  • claude CLI on PATH (the hook shells out to claude -p)
  • jq on PATH (both the hook and the statusline parse JSON with it)
  • ~/.claude/ exists (true on any machine where Claude Code has been launched)

Step 2 — Run the installer

python <skill-dir>/scripts/install.py install

The installer is idempotent — running it twice does nothing the second time. It:

  1. Creates ~/.claude/hooks/vocab/ and ~/.claude/vocab/ if missing.
  2. Copies the canonical extract.sh from the skill into ~/.claude/hooks/vocab/extract.sh (overwrites the file each run — this is how the user picks up skill updates).
  3. Registers the hook under hooks.Stop in ~/.claude/settings.json. Skips if the exact command path is already present. Makes a .bak-vocab backup before writing.
  4. Handles the statusline based on its current state — see Step 3.

Step 3 — Statusline integration

The installer does not auto-edit a custom statusline. It branches into three cases and prints which one it took:

Case A — no statusline exists (~/.claude/statusline-command.sh missing or empty) → installer writes the bundled default-statusline.sh verbatim. The default is intentionally plain (dir · model · ctx · vocab); users who want a richer look can edit freely after.

Case B — already integrated (statusline contains the # vocab:start marker) → installer skips, prints "already integrated".

Case C — existing custom statusline without markers → installer prints the snippet below and refuses to auto-modify. Your job in the conversation: read the user's ~/.claude/statusline-command.sh, understand how it builds its output line, and insert the snippet so the vocab segment lands at the end of the line with a · separator. Always show the diff before writing so the user can sanity-check.

Insertion snippet (always surround with the markers so a future uninstall can find it):

# vocab:start — statusline-vocab skill
vocab_file="$HOME/.claude/vocab/current.json"
vocab_part=""
if [ -f "$vocab_file" ]; then
  v_word=$(jq -r '.word // empty' "$vocab_file" 2>/dev/null)
  if [ -n "$v_word" ] && [ "$v_word" != "null" ]; then
    v_emoji=$(jq -r '.emoji // "📖"' "$vocab_file" 2>/dev/null)
    v_ipa=$(jq -r '.ipa // ""' "$vocab_file" 2>/dev/null)
    v_pos=$(jq -r '.pos // ""' "$vocab_file" 2>/dev/null)
    v_meaning=$(jq -r '.meaning // ""' "$vocab_file" 2>/dev/null)
    vocab_part=" · ${v_emoji} \033[1;94m${v_word}\033[0m \033[2m${v_ipa}\033[0m \033[0;33m${v_pos}\033[0m ${v_meaning}"
  fi
fi
# vocab:end

How to wire ${vocab_part} into the final output depends on how the statusline is structured:

  • Statusline builds a single line variable and prints once at the end (most common pattern, matches the bundled default): append ${vocab_part} to that variable just before the final printf / echo.
  • Statusline calls printf multiple times inline: refactor lightly so the last printf gets ${vocab_part} concatenated onto its argument. Don't add a separate printf call after, because Claude Code's statusline keeps trailing newlines literal.
  • Statusline outputs multiple lines on purpose (e.g. uses printf '%b\n' twice): pick the line you want vocab on, and append there.

After inserting, run Step 4 to confirm.

Step 4 — Verify

python <skill-dir>/scripts/install.py status

Reports: hook script present? hook registered? statusline integrated (markers found)? what word is current? how many history entries?

To preview rendering without waiting for a real Stop, pipe a stub event:

echo '{"workspace":{"current_dir":"'"$PWD"'"},"cwd":"'"$PWD"'","model":{"display_name":"Test"},"context_window":{"used_percentage":0}}' \
  | bash ~/.claude/statusline-command.sh

The vocab segment only appears once a word has been extracted — that happens after the next real Stop hook fires in a Claude Code session. To force an extract right now, point the hook script at an existing transcript:

latest=$(ls -t ~/.claude/projects/*/*.jsonl | head -1)
printf '{"transcript_path":"%s","hook_event_name":"Stop"}' "$latest" \
  | ~/.claude/hooks/vocab/extract.sh
cat ~/.claude/vocab/current.json

Configuration

Translation language

The meaning field is translated into a target language — Chinese by default, but switchable. The language is stored in ~/.claude/vocab/config so it survives across upgrades and doesn't require editing extract.sh. Two ways to set it:

# At install time:
python <skill-dir>/scripts/install.py install --lang Spanish

# Anytime after, without reinstalling:
python <skill-dir>/scripts/install.py config --lang Japanese

# Inspect current:
python <skill-dir>/scripts/install.py config

The value is passed verbatim to the prompt ("…tutor for a {LANG}-speaking learner. … concise translation in {LANG}, under ~15 characters or 3-4 words…"), so any language the model knows works: Chinese, Japanese, Korean, Spanish, French, German, Portuguese, Italian, Russian, Vietnamese, Thai, …

The change takes effect on the next Stop hook fire. The currently-displayed word (current.json) is not retroactively re-translated — wait for the cooldown to expire or rm ~/.claude/vocab/current.json to force a fresh extract.

Other knobs

For knobs that don't change often, edit ~/.claude/hooks/vocab/extract.sh directly. They live at the top under # --- Knobs ---:

KnobDefaultEffect
COOLDOWN180 (seconds)Skip re-extraction if current.json is fresher than this. Higher = more stable word; lower = updates more often. Raising to 600 gives roughly one word per "real" conversation.
MODELclaude-haiku-4-5Swap for claude-sonnet-4-6 if you want richer choices. Cost goes up; latency too (still async, so doesn't block).
DEFAULT_LANGChineseOnly used when no config file exists. The config file always wins.
The prompt bodysee fileEdit the skip-list ("the", "use", "make"…), the difficulty bias, or the JSON schema. After editing, the next Stop picks up the new prompt — no restart needed.

If the user asks for a tunable that requires more than a one-line edit (e.g. "only extract on weekdays", "rotate through three difficulty levels"), edit the script in place.

Uninstall

python <skill-dir>/scripts/install.py uninstall

This:

  1. Removes the hook entry from ~/.claude/settings.json (backup at .json.bak-vocab-uninstall).
  2. Deletes ~/.claude/hooks/vocab/extract.sh.
  3. Leaves ~/.claude/vocab/ intact so the user's history.jsonl survives.
  4. Leaves the statusline alone, but prints the marker lines so the user (or Claude) can strip them. If the statusline was the bundled default and the user wants a clean slate, they can just rm ~/.claude/statusline-command.sh.

Troubleshooting

  • No word ever appears. Check ~/.claude/vocab/extract.log for errors. The most common cause is claude not found from the hook's non-interactive shell — Claude Code hooks inherit a minimal PATH. Fix by symlinking claude into /usr/local/bin/ or by editing the hook to use the absolute path. Verify manually with the "force an extract" snippet above.
  • Same word keeps showing. Cooldown is doing its job; either wait it out or rm ~/.claude/vocab/current.json to force a re-extract on the next Stop.
  • Statusline renders broken / garbled. jq is probably missing. The snippet swallows individual jq errors but ends up with an empty vocab segment, which usually looks fine. If color codes are leaking as raw \033[... text, the user's terminal doesn't render printf '%b' — switch the insertion to echo -e or drop the colors.
  • Model picks bad words ("the", "use", "make"). Strengthen the skip-list in the prompt inside extract.sh. Haiku follows the skip-list reliably.
  • Hook fires twice on every turn. Look for duplicate entries in ~/.claude/settings.json under hooks.Stop — possible if the file was hand-edited between installer runs. Either re-run the installer (it dedupes) or remove the duplicate manually.

Mental model

Three boundaries, each swappable without touching the others:

  • Trigger — when to think about updating the word. Currently Stop (every assistant turn, rate-limited by COOLDOWN). Could be moved to SessionEnd for "one word per session" semantics.
  • Storage — current.json (the visible word) plus history.jsonl (the running wordbook).
  • Render — how the statusline displays the word. The snippet only reads JSON; you can rewrite the format without ever touching the hook.

If the user wants a richer experience later (Anki export, daily review prompt, frequency-based spaced repetition over history.jsonl), build it as a fourth piece that consumes history.jsonl — don't entangle it with the trigger or render layers.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.