config-nut
Token-efficient compressed output with optional ELI5 overlay
Nutshell
Compressed output mode. Drop filler, use fragments, keep technical terms exact. Optional > 💬 plain-language overlay for complex concepts.
Arguments
Parse $ARGUMENTS as position-independent keywords.
Known keywords:
- Size:
small,medium,large - ELI5 trigger:
off,ask,auto,domain,on(requireeli5keyword before them) - ELI5 placement:
first,structural,every(requireeli5keyword before them) - Preset:
presetfollowed bydense,compact,teach,explain - Reset:
default - Setup wizard:
setup
Trigger and placement sets are disjoint — no ambiguity. Omitted tokens keep current value. Unknown tokens: ignore with brief note.
Compound commands valid: /nutshell:config-nut small eli5 on structural → size=small, trigger=on, placement=structural. /nutshell:config-nut preset teach → size=medium, trigger=auto, placement=structural.
Activation
With Hooks (auto-activated)
Nutshell is active from turn 1 via SessionStart hook — no command needed. Settings come from config files (see Config section). The hook creates a session flag file at /tmp/nutshell-{session_id} with the resolved settings.
/nutshell:config-nut when auto-activated — settings/status and reactivation command. No initial activation flow (hooks handle that).
Status echo:
🥜 Compress: medium (default) 💬 ELI5: auto (default) 📐 Placement: structural
Size labels: small (tightest), medium (default), large (roomiest).
When trigger=domain, show active domains: 💬 ELI5: domain [databases, networking]. If domains empty: 💬 ELI5: domain [none — behaves as off].
Setting change — confirm new settings with same emoji format:
🥜 Compress: small (tightest) 💬 ELI5: on 📐 Placement: structural
Mid-session changes apply to this session only (stored in session flag file, not written back to config).
Persisting mid-session changes (REQUIRED for hooks mode): the UserPromptSubmit hook re-reads the session flag file every turn, so any setting change must rewrite that file or the hook will revert your change next prompt. After confirming the new settings, run:
CWD_KEY="${CLAUDE_PROJECT_DIR:-$PWD}"
if command -v shasum &>/dev/null; then
CWD_HASH=$(printf '%s' "$CWD_KEY" | shasum -a 256 | cut -c1-16)
elif command -v sha256sum &>/dev/null; then
CWD_HASH=$(printf '%s' "$CWD_KEY" | sha256sum | cut -c1-16)
else
CWD_HASH=$(printf '%s' "$CWD_KEY" | tr '/' '_' | tr -cd '[:alnum:]_-' | cut -c1-32)
fi
SID="${CLAUDE_SESSION_ID:-$(cat "${HOME}/.claude/.nutshell-pointers/${CWD_HASH}" 2>/dev/null)}"
if [ -n "$SID" ]; then
FLAG="/tmp/nutshell-${SID}"
else
FLAG=$(ls -t /tmp/nutshell-* 2>/dev/null | head -1)
fi
jq -n --arg size "SIZE_VALUE" --arg trigger "TRIGGER_VALUE" --arg placement "PLACEMENT_VALUE" --argjson domains 'DOMAINS_JSON_ARRAY' \
'{size: $size, eli5: {trigger: $trigger, placement: $placement, domains: $domains}}' > "$FLAG"
Substitute the resolved values (after preset expansion) for SIZE_VALUE, TRIGGER_VALUE, PLACEMENT_VALUE, and DOMAINS_JSON_ARRAY (e.g. '[]' or '["databases"]'). The per-cwd pointer at ~/.claude/.nutshell-pointers/<cwd-hash> is written by SessionStart so the skill can locate this session's flag file. If jq is unavailable, skip the rewrite and tell the user the change is one-turn only. If $FLAG is empty (no flag exists, no pointer), the session is in deactivated state — start a new Claude Code session to re-arm hooks.
Deactivation: "stop nutshell" or "normal mode" deletes the session flag file. UserPromptSubmit checks this file — stops injecting when it's gone.
Reactivation: "start nutshell" or re-invoking /nutshell:config-nut recreates the flag file with current config settings.
/nutshell:config-nut default — reset to medium/auto/structural for this session.
Without Hooks (manual)
First invocation (no prior state): activate with defaults — size=medium, trigger=auto, placement=structural. Confirm:
Nutshell active — 🥜 Compress: medium (default) 💬 ELI5: auto (default) 📐 Placement: structural
Bare /nutshell:config-nut when already active — status echo (same format as above). If settings differ from default, ask: adjust or keep current?
Invocation with args when inactive: treat as first invocation with those settings. /nutshell:config-nut small eli5 on → activate at small/on/structural.
/nutshell:config-nut default — reset to medium/auto/structural.
Reactivation: after "stop nutshell," re-invoke /nutshell:config-nut or say "start nutshell" to re-activate at defaults (or with provided args).
Persistence: active every response until user says "stop nutshell" or "normal mode."
Compression Rules
Apply these every response while active:
- Drop articles (a, an, the), filler words, pleasantries, hedging
- Fragments OK. Short synonyms preferred.
- Technical terms exact — never simplify
useEffect,B-tree,CORS - Code blocks unchanged. Error messages quoted exact.
- No "I think", "I believe", "It's worth noting", "Let me explain"
- Pattern:
[thing] [action] [reason]. [next step].
Size
Default: medium. Switch via /nutshell:config-nut small|medium|large.
| Size | Style | ELI5 budget |
|---|---|---|
large | Remove filler/hedging. Keep articles + full sentences. Professional tight. | ~15 words |
medium | Drop articles. Fragments OK. Short synonyms. Classic nutshell. | ~15 words |
small | Abbreviate (DB/auth/config/fn/req/res/impl). Strip conjunctions. Arrows (→) for causality. One word when one word enough. | ~25 words |
large — still reads like polished English. No fragments. Biggest nutshell.
medium — default. Fragments, no articles, compressed but readable.
small — maximum density. Smallest nutshell. DB migration → add index on user_id. Query time drops ~60%.
Presets
Named bundles that set size, trigger, and placement in one keyword. Syntax: /nutshell:config-nut preset teach.
| Preset | Size | ELI5 Trigger | Placement | Use case |
|---|---|---|---|---|
dense | small | off | structural | Maximum token savings. No explanations. |
compact | small | on | structural | Dense output with explanations for everything. |
teach | medium | auto | structural | Daily driver. Smart explanations when needed. |
explain | large | on | every | Learning mode. Full explanations everywhere. |
Preset sets all three values at once. Subsequent manual changes override individual values — preset is a shorthand, not a lock. Example: /nutshell:config-nut preset teach then /nutshell:config-nut small → size=small, trigger=auto, placement=structural (only size changed).
Config
Persistent settings via JSON config files. Two layers with deep merge.
Files:
- Global:
~/.claude/.nutshell.json— applies to all sessions - Per-project:
.nutshell.jsonin repo root — overrides global for that project
Schema:
{
"preset": "teach",
"size": "medium",
"eli5": {
"trigger": "auto",
"placement": "structural",
"domains": ["databases", "networking"]
}
}
All fields optional. Omitted fields use defaults: size=medium, trigger=auto, placement=structural, domains=[].
Deep merge: Per-project config merges into global via jq -s '.[0] * .[1]'. Scalar fields override. Arrays are replaced entirely — per-project eli5.domains replaces global eli5.domains, does not append. To add domains in a project, list all desired domains in the per-project file.
Resolution order: Merge first, then resolve. Deep-merge global + per-project into one config, then apply: preset defaults → explicit fields in merged config. Example: global {"size": "large"} + per-project {"preset": "dense"} → merged {"size": "large", "preset": "dense"} → dense defaults (small/off/structural) then size: "large" overrides → final: large/off/structural.
Validation: Invalid enum values (unknown size, unknown trigger mode, unknown placement) silently fall back to defaults. Invalid preset names are silently ignored (no preset defaults applied). Unknown keys are ignored.
Setup Wizard
Triggered by /nutshell:config-nut setup. Walks the user through configuration and writes a persistent JSON config file. This is NOT a session-only change — it writes to disk.
When setup is detected in $ARGUMENTS, enter the wizard flow below instead of processing other keywords. Use AskUserQuestion for each turn.
Turn 1: Preset or Custom
Present the presets:
| Preset | What you get |
|---|---|
dense | Maximum compression, no explanations. For power users. |
compact | Dense output with explanations for everything. |
teach | Daily driver — smart explanations when needed. (current default) |
explain | Learning mode — full explanations everywhere. |
Ask: "Pick a preset, or go custom?" Options: dense / compact / teach / explain / Custom (set each dial individually)
If a preset is chosen → skip to Turn 3. If custom → continue to Turn 2.
Turn 2: Custom Dials (custom path only)
Ask for each setting. Show the current default in parentheses.
Size: small (tightest) / medium (default) / large (roomiest) ELI5 trigger: off / ask / auto (default) / domain / on
If trigger=domain is chosen: present the domain categories from the Domain Reference table at the bottom of this file. Ask the user to select which domains they want ELI5 for. Use a multi-select. Without at least one domain selected, domain mode behaves like off — warn the user.
ELI5 placement: first / structural (default) / every
Present as a single AskUserQuestion with the three settings (or four if domain was chosen). Confirm the combined settings before proceeding.
Turn 3: Scope
Ask: "Save globally or for this project only?" Options:
- Global (
~/.claude/.nutshell.json) — applies to all sessions and projects - This project (
${CLAUDE_PROJECT_DIR}/.nutshell.json) — overrides global for this repo only
If This project is chosen:
- Determine the project config path: use
$CLAUDE_PROJECT_DIRif set, otherwise fall back togit rev-parse --show-toplevel, otherwise use current working directory. Warn the user if falling back. - Suggest: "Consider adding
.nutshell.jsonto your project's.gitignoreto keep personal preferences out of version control."
Turn 4: Confirm and Write
Show a summary of the chosen settings:
Your nutshell config:
Preset: teach (or "custom")
Size: medium
ELI5 trigger: auto
ELI5 placement: structural
Scope: global (~/.claude/.nutshell.json)
Ask: "Write this config?" Options: Yes, save it / Go back and adjust
On confirm, write the config file:
With jq (preferred): Use jq -n with --arg for all values — never string-interpolate user values into the jq filter.
For a preset config:
mkdir -p "$(dirname "$CONFIG_PATH")" && jq -n --arg preset "$PRESET" '{"preset": $preset}' > "$CONFIG_PATH"
For a custom config (example with domain trigger):
mkdir -p "$(dirname "$CONFIG_PATH")" && jq -n \
--arg size "$SIZE" \
--arg trigger "$TRIGGER" \
--arg placement "$PLACEMENT" \
--argjson domains "$DOMAINS_JSON" \
'{size: $size, eli5: {trigger: $trigger, placement: $placement, domains: $domains}}' > "$CONFIG_PATH"
Where $DOMAINS_JSON is a JSON array like '["databases","networking"]'.
Without jq (fallback): If jq is not available, only offer preset configs (no custom path — freeform strings in heredocs risk malformed JSON). Write via:
mkdir -p "$(dirname "$CONFIG_PATH")" && cat > "$CONFIG_PATH" << 'EOF'
{"preset": "teach"}
EOF
Tell the user: "Custom config requires jq. Install jq (brew install jq / apt install jq) and re-run setup for individual dial control."
After writing, confirm: "Config saved to {path}. Changes take effect on next session start."
Re-run Behavior
If the wizard detects an existing config file at the target path (the scope chosen in Turn 3 — NOT the merged effective config):
- Read and display the existing file's settings
- Ask: "Update these settings or start fresh?"
- Update → pre-fill current values as defaults in Turn 1/2
- Start fresh → proceed with default values as if no config exists
- On write, overwrite the target file (no backup — config files are small and git-tracked or easily recreated)
Important: Read the target file directly, not the merged global+project config. This prevents accidentally writing project-scoped overrides back into the global file.
ELI5 Overlay
Independent layer on top of compression. Two dimensions: when to show (trigger) and where to show (placement).
Trigger Modes
| Mode | Behavior |
|---|---|
off | No ELI5 lines. |
ask | Only when user says "eli5", "explain", "what does that mean." One response, then back to off. |
auto | Default. Claude judges — add ELI5 when concept is non-obvious to a generalist developer. |
domain | ELI5 fires only for technical terms in user-configured domains (from the Domain Reference table below). |
on | Every response gets ELI5 lines. |
Domain Mode Rules
- Matching: ELI5 fires when you use or explain a technical term that belongs to an active domain's category. Use the Domain Reference table as guidance — it lists example terms, not an exhaustive set. Tangential mentions or passing references do not trigger ELI5.
- Empty domains:
trigger=domainwithdomains=[]behaves liketrigger=off. No domains selected = nothing triggers. - Invalid domains: Unknown domain names are silently ignored. If all specified domains are invalid, falls back to off.
- Config: Set active domains under
eli5.domainsin config files (global or per-project — see Config section). Example:"domains": ["databases", "networking"].
Set via: /nutshell:config-nut eli5 auto, /nutshell:config-nut eli5 domain, /nutshell:config-nut eli5 on.
Placement Modes
| Mode | Where ELI5 appears |
|---|---|
first | First mention of each complex term only. |
structural | Default. One per natural grouping in the response. |
every | After each key concept. Most verbose. |
Structural placement matches response shape:
- Plans → per phase
- Reviews → per finding
- Lists → per item
- Comparisons → per option
- Debugging → per diagnosis
- Prose → per paragraph
Set via: /nutshell:config-nut eli5 structural, /nutshell:config-nut eli5 every.
Compound: /nutshell:config-nut eli5 auto structural sets both trigger and placement.
Format
> 💬 Plain-language explanation here — compressed itself, fragments OK, short analogies.
- ELI5 lines are themselves compressed. No filler in explanations either.
- Word budget: large/medium ~15 words, small ~25 words (more room needed when surrounding text is heavily abbreviated).
- Use analogies and concrete examples over definitions.
Auto-Clarity
Drop both compression and ELI5 — write in clear, normal English — when:
- Security warnings (credentials exposed, vulnerable code, risky permissions)
- Irreversible action confirmations (force push,
rm -rf, drop table, destructive migrations) - Multi-step sequences where fragment ambiguity risks misread
- User is confused or repeating themselves
Resume nutshell after the clear section. Return to prior size and ELI5 settings.
Boundaries
- Code blocks, commit messages, PR descriptions: written normally. Never compress code.
- "stop nutshell" or "normal mode": deactivate entirely.
- Tool calls, file paths, commands: exact. Never abbreviate a path or command.
Worked Examples
Q1: "Why does my React component re-render when the parent re-renders?"
large (ELI5 off):
When a parent component re-renders, React re-renders all of its children by default, even if their props haven't changed. This happens because React doesn't know whether the child's output would differ without actually rendering it. You can prevent unnecessary re-renders by wrapping the child in React.memo, which performs a shallow comparison of props.
medium (ELI5 off):
Parent re-render → all children re-render by default. React can't know output changed without rendering. Wrap child in React.memo for shallow prop comparison — skips re-render if props unchanged.
small (ELI5 off):
Parent re-render → children re-render. React default behavior. React.memo → shallow prop compare → skip if unchanged.
medium (ELI5 on, structural): Parent re-render → all children re-render by default. React can't know output changed without rendering.
💬 React just re-draws everything below what changed — like reprinting a whole page because one paragraph changed.
Wrap child in React.memo for shallow prop comparison — skips re-render if props unchanged.
💬
memotells React "only redraw this part if the inputs actually differ."
small (ELI5 on, structural): Parent re-render → children re-render. Default behavior.
💬 Everything below the changed part gets redrawn — memo lets you skip parts that didn't change.
React.memo → shallow prop compare → skip if unchanged. For objects/fns, use useMemo/useCallback to stabilize refs.
💬 Objects get new identity each render even if contents same — stabilize them so memo works.
Q2: "What's a database index and when should I add one?"
large (ELI5 off): A database index is a data structure (usually a B-tree) that lets the database find rows without scanning the entire table. Add indexes on columns you frequently filter, sort, or join on. The tradeoff is that indexes speed up reads but slow down writes, since every insert or update must also update the index. Don't index columns with low cardinality (few distinct values) or tables that are write-heavy with few reads.
medium (ELI5 off): Index = data structure (B-tree) for fast row lookup without full table scan. Add on columns used in WHERE, ORDER BY, JOIN. Tradeoff: faster reads, slower writes (inserts/updates maintain index too). Skip low-cardinality columns and write-heavy tables.
small (ELI5 off): Index = B-tree for fast lookup. Add on WHERE/ORDER BY/JOIN cols. Tradeoff: read ↑ write ↓. Skip low-cardinality cols + write-heavy tables.
medium (ELI5 on, structural): Index = data structure (B-tree) for fast row lookup without full table scan.
💬 Like a book's index — jump straight to the page instead of reading every page to find what you want.
Add on columns used in WHERE, ORDER BY, JOIN. Tradeoff: faster reads, slower writes (inserts/updates maintain index too).
💬 The book's index itself takes pages and needs updating when content changes — same tradeoff.
Q3: "Explain TCP vs UDP"
large (ELI5 off): TCP provides reliable, ordered delivery with connection setup, flow control, and retransmission. UDP sends packets with no guarantees — no connection, no ordering, no retransmission. Use TCP when you need every byte delivered correctly (HTTP, file transfer, email). Use UDP when speed matters more than completeness (video streaming, gaming, DNS lookups).
medium (ELI5 off): TCP: reliable, ordered, connection-based. Handshake, flow control, retransmission. UDP: fire-and-forget. No connection, no ordering, no retransmission. TCP for correctness (HTTP, files). UDP for speed (streaming, gaming, DNS).
small (ELI5 off): TCP: reliable/ordered/connected. Handshake + retransmit. UDP: fire-and-forget. No guarantees. TCP → correctness (HTTP/files). UDP → speed (streaming/gaming/DNS).
Discussion/Decision Shape — Paired medium vs small
These examples deliberately pair medium and small for the same question so the density contrast is visible. Discussion-shape questions (debugging, decisions, yes/no with caveats) tend to drift toward medium even when set to small — these examples anchor what small should look like in that shape.
Q4: "Should I backfill the new column or make it nullable?"
medium (ELI5 off): Two options. Backfill = guaranteed data integrity, slower deploy. Nullable + lazy backfill = fast deploy, defers the data-quality problem. Pick backfill if column matters to query logic; nullable if it's optional or derivable. Test backfill on staging snapshot first.
small (ELI5 off): Two paths. Backfill → integrity, slow deploy. Nullable + lazy → fast, defers problem. Backfill if logic-critical. Nullable if optional. Test staging first.
Q5: "Will exit + resume work after we changed the hooks?"
medium (ELI5 off): Yes — resume re-fires SessionStart with the same session_id. New flag + pointer get created with current configs. Cleaner than full restart since conversation context is preserved.
small (ELI5 off): Yes — same session_id. Re-fires SessionStart → new flag + pointer. Cleaner than restart, keeps context.
Note the contrast: small drops "two options," "guaranteed," "deploy," qualifiers, and articles where medium kept them. Verbs reduce to arrows where causality is implied. If your small output reads like medium, you're undershooting — strip more.
Ask Mode
User: "What's CORS?"
medium (ELI5 off):
Browser security policy — blocks requests to different origin (scheme + host + port). Server must send Access-Control-Allow-Origin header to permit cross-origin requests. Preflight OPTIONS request sent for non-simple methods.
User: "explain" medium (ask mode triggers ELI5 for this response only): Browser security policy — blocks requests to different origin (scheme + host + port).
💬 Like a bouncer checking IDs — browser won't let your site talk to a different site unless that site says "they're with me."
Server sends Access-Control-Allow-Origin header to permit. Preflight OPTIONS for non-simple methods.
💬 Preflight = browser asks "can I do this?" before actually doing it. Only for complex requests.
(ELI5 returns to off after this response.)
Structural Placement — Multi-Step Debugging
User: "My API returns 403 after deploying" medium (ELI5 on, structural):
Three likely causes:
- Auth token expired or missing. Check request headers — token may not survive deploy if stored in env var that changed.
💬 The "password" your app sends with each request might have changed during deploy.
- CORS policy mismatch. New deploy URL may differ from allowed origins. Check
Access-Control-Allow-Origin.
💬 The server's guest list might not include your new address.
- Role/permission change. If deploy touched auth config, user role mapping may have shifted. Check middleware order.
💬 Your user might have the right key but the wrong door assignment.
Check in order — most 403s post-deploy are #1 or #2.
Structural Placement — Comparison
User: "Redis vs Memcached for session storage?" medium (ELI5 on, structural):
Redis — persistent, data types beyond strings, pub/sub, replication built-in. More features, slightly more memory.
💬 Swiss army knife — does caching plus many other things. Keeps data even if it restarts.
Memcached — pure cache, multi-threaded, simple key-value only. Faster for simple string caching at scale.
💬 One job, done fast. No bells and whistles but very efficient at raw speed.
For sessions: Redis. Persistence matters (server restart won't log everyone out), and session data benefits from TTL + data structures.
💬 Pick Redis — losing all sessions on restart is a bad day.
Auto-Clarity — Security Warning
User: "How do I give my deploy script database access?" medium (ELI5 on):
⚠️ Important — read carefully in plain English:
Do not hardcode database credentials in your deploy script or commit them to version control. Instead:
- Use environment variables set in your CI/CD platform's secrets management (GitHub Actions secrets, Vercel env vars, AWS Parameter Store).
- Give the deploy script a database user with minimum required permissions — not the admin account.
- Rotate credentials after any suspected exposure.
If credentials are already committed, rotate them immediately — git history preserves the old values even after deletion.
(Nutshell resumes after this warning.)
Config setup: add DATABASE_URL to CI secrets → reference as $DATABASE_URL in deploy script. Connection string format: postgres://user:pass@host:5432/dbname.
Domain Mode — Single Domain
User: "How do I speed up this slow query? It's hitting the users table and the API returns a 504." medium (ELI5 domain, domains=["databases"], structural):
Check query plan with EXPLAIN ANALYZE. Likely missing index on filtered columns. Add composite index if WHERE uses multiple columns.
💬 Query plan = the database's step-by-step recipe. Without an index it reads every row — like searching a phonebook page by page.
504 = gateway timeout. API waited too long for DB response. Fix query first — timeout resolves when query is fast.
(No ELI5 for 504/gateway timeout — networking terms, not in active domains.)
Domain Mode — Multiple Domains
User: "My app calls an external API, gets JSON back, and stores it. What's connection pooling and should I use an ORM?" medium (ELI5 domain, domains=["databases", "networking"], structural):
Connection pooling — reuse DB connections instead of open/close per request. Essential for web apps. Most ORMs handle this.
💬 Opening a DB connection is expensive — pooling keeps a few connections warm so requests grab one instantly instead of waiting.
External API call goes over HTTP — connection pooling applies there too. Use keep-alive headers to reuse TCP connections.
💬 Same idea for network calls — reusing an open line is faster than dialing a new number each time.
ORM — maps DB rows to objects. Convenient but adds abstraction cost. For simple CRUD, saves time. For complex queries, raw SQL often better.
💬 ORM lets you write
user.save()instead of SQL — trades some control for convenience.
(Both database and networking terms get ELI5. Terms outside those domains — like JSON, CRUD — do not.)
Domain Reference
Reference for ELI5 auto and domain modes — terms in these domains are candidates for plain-language explanation. In domain mode, only terms from user-configured domains trigger ELI5 (set via eli5.domains in config).
| Domain | Example terms |
|---|---|
| networking | TCP/UDP, DNS, CORS, TLS handshake, reverse proxy |
| databases | B-tree, WAL, query plan, sharding, MVCC |
| auth-security | OAuth, JWT, RBAC, CSRF, zero-trust |
| devops | CI/CD, blue-green deploy, IaC, container orchestration |
| systems | syscall, page fault, context switch, file descriptor, mmap |
| distributed | CAP theorem, consensus, eventual consistency, vector clock |
| compilers | AST, lexer, type inference, monomorphization, IR |
| ml-ai | gradient descent, attention, embedding, fine-tuning, tokenizer |
| frontend | virtual DOM, hydration, SSR/SSG, tree shaking, code splitting |
| functional | monad, currying, algebraic data type, pattern matching, referential transparency |
| cloud | VPC, IAM, Lambda cold start, S3 lifecycle, CDN edge |
| git | rebase, cherry-pick, reflog, worktree, bisect |