Install
What Learner needs, the ways in, what lands where, and which platforms can run it.
Requirements
jqonPATH— needed to install (the hook wiring is merged with it) and needed by every hook at run time except the update-check notifier, which has nojqdependency by design. Without it the rest are inert, andSessionStartsays so once.bashonPATHto install —install.shis a bash script.curlis used at run time — by the update-check hook only, once every 24h, to look for a newer version. Its absence there is silent, not an error — unlikejq,curlis never a hard requirement for anything already installed.- A POSIX-compliant shell at run time — separate from the bash
requirement above, and not satisfied by it. The hooks are plain
shscripts, wired intosettings.jsonassh "…/hooks/learner-quiz.sh". See Platforms. - Claude Code — either
claudeonPATHor an existing config directory.
Claude Code plugin
claude plugin marketplace add Tykok/learning-with-claude
claude plugin install learner@learning-with-claude
Installs and enables the skills and their hooks natively — no ~/.claude file
copying, no learner-install step. Claude Code manages updates itself
(/plugin update learner); run learner update and it will tell you
the same thing rather than trying to curl a second, traditional install on top. Already
installed via curl, clone, Homebrew, or apt? Run uninstall.sh (or
learner-uninstall) first — running both wires every hook twice.
apt (Debian/Ubuntu)
# one time
curl -fsSL https://tykok.github.io/learning-with-claude/apt/learner.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/learner.gpg
echo "deb [signed-by=/usr/share/keyrings/learner.gpg] https://tykok.github.io/learning-with-claude/apt stable main" \
| sudo tee /etc/apt/sources.list.d/learner.list
# from then on
sudo apt update && sudo apt install learner
learner-install --level S --synthesis normal --blanks 2
One curl remains — fetching the repository's trust anchor once, the same
pattern Docker's and HashiCorp's own apt repos use. There is no keyless way to establish
that first trust, but after this one-time step, sudo apt update && sudo apt
upgrade learner is the whole update story — no more curl involved,
ever.
Prefer not to add a repository? Grab the .deb directly from
Releases instead:
curl -LO https://github.com/Tykok/learning-with-claude/releases/download/v0.3.0/learner_0.3.0_all.deb
sudo apt install ./learner_0.3.0_all.deb
learner-install --level S --synthesis normal --blanks 2
v0.3.0 is this release. Check
Releases for the
current version if you're reading this after a newer one has shipped.
Homebrew
brew install tykok/tap/learner
learner-install --level S --synthesis normal --blanks 2
A personal tap (Tykok/homebrew-tap), not homebrew-core. Each tagged release updates it.
Both apt and Homebrew only stage the files and drop learner-install /
learner-uninstall on PATH — neither touches ~/.claude
on its own. Run learner-install afterward, same flags as
install.sh below. Uninstalling reverses the same way:
sudo apt remove learner / brew uninstall learner only remove those
two wrapper binaries — the payload under ~/.claude still needs
learner-uninstall (same as uninstall.sh) to actually come out.
Clone and run
A first-class path, not a fallback: it is the one that lets you read the installer before you run it.
git clone https://github.com/Tykok/learning-with-claude
cd learning-with-claude
./install.sh # prompts for level / synthesis / blanks
./install.sh --dry-run # print what would happen, write nothing
./install.sh --yes # never prompt; defaults for anything unset
./install.sh --level S --synthesis normal --blanks 2 # non-interactive, one shot
To install a release rather than whatever main says today, clone its tag:
git clone --depth 1 --branch v0.5.0 https://github.com/Tykok/learning-with-claude.
There is no path that pipes a download into a shell: every install runs an install.sh
you have on disk and can read first.
--level D|J|C|S|E— the letter, or the full word from the levels table, in any case. It has no default: on a fresh install,--yeswith no level stops rather than guessing. Oncelearner.jsonalready exists this whole prompt/validate step is skipped — the flag has nothing to do, and the existing config is left untouched either way.--synthesis off|rare|normal|often— how often a synthesis question replaces a granular one.--blanks N— holes left in afillexercise, an integer of 1 or more.--dry-run— print what would be written; write nothing.--yes(-y) — never prompt; use the default for anything not passed.
The installer is idempotent: re-running re-copies the hooks and the skills and
re-merges the hook wiring without duplicating entries, and it never overwrites a config
you already have. It writes nothing into any repository. Every path it
touches is under $CLAUDE_CONFIG_DIR (default ~/.claude), and
every wired hook command carries the literal
${CLAUDE_CONFIG_DIR:-$HOME/.claude} rather than an expanded path, so
moving your config directory later needs no reinstall.
Hook wiring is read once, when a Claude Code session starts. Installing (or re-installing) while a session is already open changes nothing in it — quit and start a new session for the hooks to take effect.
What gets installed, and where
Everything lives under $CLAUDE_CONFIG_DIR, written $CFG
below.
| Path | Role |
|---|---|
$CFG/skills/learner/SKILL.md | The learner hub skill: dispatch, levels, config |
$CFG/skills/learner/references/ | The two shared protocols: hook-quiz.md, data.md |
$CFG/skills/{quiz,status,improve,coach,export,update}/ | One skill per subcommand, each holding its own protocol. A plugin install exposes the same directories as /learner:quiz and friends |
$CFG/skills/learner/VERSION | The installed version string; re-copied unconditionally on every install/re-install, so it always matches what's on disk |
$CFG/skills/learner/INSTALL_ORIGIN | How this install got here — curl, brew or apt; re-stamped on every install/re-install |
$CFG/skills/pilot/SKILL.md | The pilot skill: dispatch, the four axes, the privacy paragraph |
$CFG/skills/pilot/references/ | The protocols: rubric.md, score.md, brief.md, dashboard.md, mechanisms.md |
$CFG/hooks/learner-config.sh | Shared config resolution — sourced-only, never run on its own |
$CFG/hooks/learner-onboard.sh | SessionStart — reports a broken install and nothing else |
$CFG/hooks/learner-update-check.sh | SessionStart (second entry) — notifies once a day when a newer version is out; no jq dependency |
$CFG/hooks/coach-gate.sh | PreToolUse on Write/Edit/NotebookEdit — refuses writes outside the delegated slice, when coach mode is on |
$CFG/hooks/learner-record-edit.sh | PostToolUse on Write/Edit — records the files edited this session |
$CFG/hooks/learner-quiz.sh | Stop — the quiz trigger, and the LEARNER-TODO guardrail |
$CFG/hooks/learner-cleanup.sh | SessionEnd — deletes this session's scratch files |
$CFG/hooks/coach-watch.sh | No static entry — armed as a Monitor by learner-onboard.sh at session start, when coach mode is on; runs the review cadence |
$CFG/hooks/coach-armed-check.sh | UserPromptSubmit (second entry) — warns, at most once per session, when coach mode is on but the watcher was never armed |
$CFG/hooks/pilot-record.sh | SessionEnd (second entry, timeout 15) — turns one finished transcript into one line of delegation counters, when Pilot is enabled |
$CFG/hooks/pilot-brief.sh | SessionStart (third entry) — decides whether a scoring pass, a weekly brief, or neither is due |
$CFG/hooks/pilot-nudge.sh | UserPromptSubmit — reminds the dev of a live Pilot manoeuvre; never exits 2, so it can never block or erase a prompt |
$CFG/hooks/learner-sync.sh | Utility — carries the learning record (memory.md, recap.md, learner.json) between machines through a private gist |
$CFG/hooks/learner-agent-track.sh | Counts the subagents in flight so the salvo knows when you are waiting |
$CFG/hooks/learner-event.sh | Utility — the only writer of learner/events.jsonl, the append-only event log the Learner IDE extensions read |
$CFG/settings.json | Hook wiring, merged in; a .bak of the pre-Learner file is kept beside it |
$CFG/learner.json | Your global config |
$CFG/learner/ | Empty directory, created by the installer: the skill writes memory.md, recap.md and export.json into it at runtime, and the update-check hook writes its throttle stamp (.last-update-check) there too |
$CFG/learner/memory.md | Open weak spots — the only file read to choose a question. Created by the skill on first use, not by the installer |
$CFG/learner/recap.md | Readable dashboard: to improve, mastered, session history. Also created on first use |
$CFG/learner/events.jsonl | Append-only event log (one line per question asked, answered, skipped or abandoned) read by the Learner IDE extensions; written only by learner-event.sh, never holds code. Created on the first question |
$CFG/learner/export.json | Which Notion database learner export pushes to, and when it last ran. Created on the first export, not by the installer |
$CFG/learner/pilot-queue | One line per finished session awaiting a scoring pass, written by pilot-record.sh; truncated once drained. Only when Pilot is enabled |
$CFG/learner/pilot.md | The Pilot dashboard: Sessions, Index and Manoeuvres blocks. Written by a scoring subagent, never by the installer |
$CFG/learner/pilot-evidence.md | The quotes behind each numeric score, capped at 200 characters each and fully purgeable with pilot forget |
$CFG/learner/pilot-stamps | score=/brief=/declined= timestamps that pace the scoring pass and the weekly brief |
$CFG/learner/pilot-devlines | Per-session line tallies from the coach watcher, consumed and truncated by the scoring pass |
$CFG/learner/sync.json | Gist id and last push/pull times for learner sync. Created on the first sync, not by the installer |
$CFG/learner/sync-base/ | The state both sides last agreed on — the ancestor for the next learner sync merge |
$CFG/learner/backups/<timestamp>/ | A copy of the local record taken before every learner sync pull writes anything |
Fifteen hook files ship and twelve are wired, into SessionStart
(three times — learner-onboard.sh, learner-update-check.sh
and pilot-brief.sh all fire there), PreToolUse
(coach-gate.sh on the Write|Edit|NotebookEdit matcher, plus
learner-agent-track.sh --start on its own Task matcher),
PostToolUse (learner-record-edit.sh on
Write|Edit, plus learner-agent-track.sh --end on
Task), Stop, SessionEnd (twice —
learner-cleanup.sh and pilot-record.sh, the latter at a
raised timeout of 15 since it reads a whole transcript) and
UserPromptSubmit (twice — pilot-nudge.sh and
coach-armed-check.sh). Three ship unwired:
learner-config.sh is sourced-only, and Claude Code never invokes it
directly; coach-watch.sh is never a static entry either —
learner-onboard.sh arms it as a Monitor at session start,
only once coach mode is on; and learner-sync.sh is never wired to a hook
event at all — the skill invokes it directly, only when the dev types
learner sync. Twelve of the other thirteen source
learner-config.sh — every hook but learner-cleanup.sh,
which needs none of it.
Platforms
| Platform | Supported | Notes |
|---|---|---|
| macOS | yes | needs jq |
| Linux | yes | needs jq |
| Windows via WSL | yes | it is a Linux environment |
| Windows via Git Bash | yes | provides the POSIX sh the hooks need |
| Windows, native | no | the hooks are POSIX sh scripts; with no POSIX shell, Claude Code cannot run them |
Native Windows is a genuine gap rather than an untested platform. Every hook is a
POSIX sh script and each is wired as sh "…", so without a
POSIX shell on the machine there is nothing for Claude Code to execute. WSL and Git
Bash both supply one, and on either of them Learner behaves exactly as it does on
Linux.
Next: Usage — the three shapes a question takes.