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. The one-liner checks for bash before it fetches anything, rather than downloading a payload it could not hand over.curlandtar— for the one-liner only; the clone-and-run path needs neither.curlis also 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
Installs and enables the skill and its 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.2.0/learner_0.2.0_all.deb
sudo apt install ./learner_0.2.0_all.deb
learner-install --level S --synthesis normal --blanks 2
v0.2.0 is this release. Check
Releases for the
current version if you're reading this after a newer one has shipped.
Homebrew
brew tap Tykok/learning-with-claude https://github.com/Tykok/learning-with-claude
brew install learner
learner-install --level S --synthesis normal --blanks 2
A personal tap, not homebrew-core.
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
--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 skill 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.
Alternative: the curl one-liner
curl -fsSL https://raw.githubusercontent.com/Tykok/learning-with-claude/main/bootstrap.sh | sh
It asks for your level, how often a synthesis question should replace a granular
one, and how many holes a fill exercise leaves, then writes everything
under your Claude Code config directory — this is what it fetches and runs under the
hood. To skip the prompts, pass the flags
install.sh takes — bootstrap.sh forwards them through
untouched and owns no defaults of its own:
curl -fsSL https://raw.githubusercontent.com/Tykok/learning-with-claude/main/bootstrap.sh \
| sh -s -- --level S --synthesis normal --blanks 2
To install a specific revision rather than whatever main says today,
name the ref twice — once in the URL your shell reads, once in LEARNER_REF
for the payload that URL then fetches. Anything git resolves works: a release tag, a
branch name, or a commit SHA.
REF=v0.1.0 # a tag, a branch name, or a commit SHA
curl -fsSL "https://raw.githubusercontent.com/Tykok/learning-with-claude/$REF/bootstrap.sh" \
| LEARNER_REF="$REF" sh
LEARNER_REF pins the payload, not bootstrap.sh itself —
your shell has already read that from the URL by the time the variable is visible.
Pinning only one of the two still runs whatever main says, which is why
the ref appears in both places.
What gets installed, and where
Everything lives under $CLAUDE_CONFIG_DIR, written $CFG
below.
| Path | Role |
|---|---|
$CFG/skills/learner/SKILL.md | The learner skill: dispatch, levels, config |
$CFG/skills/learner/references/ | The protocols: quiz.md, hook-quiz.md, improve.md, export.md, update.md, data.md |
$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/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/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/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/export.json | Which Notion database learner export pushes to, and when it last ran. Created on the first export, not by the installer |
Six hook files ship and five are wired, into SessionStart (twice —
learner-onboard.sh and learner-update-check.sh both fire
there), PostToolUse, Stop and SessionEnd.
learner-config.sh is the sixth: it is sourced-only, and Claude Code never
invokes it directly. Four of the other five source it — 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.