claude-tutor
Turn Claude Code into your personal tutor, personalized learning plans, adaptive quizzes, SM-2 spaced repetition, and a web dashboard. Works with any topic.
What claudemy runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
> /plugin marketplace add assafkip/claudemy> /plugin install claudemy@claudemy
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
python3In the plugin's words
How claudemy describes its own hook set.
Hands the ElevenLabs API key from the plugin's configuration prompt to the audio renderer for this session.
Where it lives
#!/usr/bin/env python3
"""SessionStart hook: pass the plugin's ElevenLabs key to this session's shell.
Claude Code asks for the key once through the plugin's `userConfig` prompt
(masked, kept in the OS credential store) and hands it to hooks as
CLAUDE_PLUGIN_OPTION_ELEVENLABS_API_KEY. The audio renderer reads
ELEVENLABS_API_KEY, so this hook writes one export line to CLAUDE_ENV_FILE,
which Claude Code sources before each Bash command in the session.
It does nothing when the option is unset, when ELEVENLABS_API_KEY is already
set in the environment, or when CLAUDE_ENV_FILE is not provided. It never
prints the key.
"""
import os
import sys
def main() -> int:
key = os.environ.get("CLAUDE_PLUGIN_OPTION_ELEVENLABS_API_KEY", "").strip()
env_file = os.environ.get("CLAUDE_ENV_FILE")
if not key or not env_file or os.environ.get("ELEVENLABS_API_KEY"):
return 0
# Single-quote for the shell; the only character that needs escaping inside
# single quotes is the single quote itself.
quoted = "'" + key.replace("'", "'\\''") + "'"
line = f"export ELEVENLABS_API_KEY={quoted}"
# SessionStart fires on startup, resume, clear and compact. Append only when the
# last export of the key in the file is a different one: repeats stay single, and
# switching back to an earlier key still makes it the active one. Read as bytes,
# since other hooks write to this file too and may not write UTF-8.
try:
with open(env_file, "rb") as f:
data = f.read()
except OSError:
data = b""
exports = [l for l in data.splitlines() if l.startswith(b"export ELEVENLABS_API_KEY=")]
if exports and exports[-1] == line.encode("utf-8"):
return 0
with open(env_file, "a", encoding="utf-8") as f:
f.write(("\n" if data and not data.endswith(b"\n") else "") + line + "\n")
return 0
if __name__ == "__main__":
sys.exit(main())
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
You wanted to learn negotiation. You got a 40-hour course, a syllabus someone wrote years ago, and a tab you never reopened. Claudemy is a free Claude Code skill that builds the course you will actually finish. You name a topic.
Turn Claude Code into your personal tutor, personalized learning plans, adaptive quizzes, SM-2 spaced repetition, and a web dashboard. Works with any topic.
Research Application Toolkit is a single repository that ships host-specific integrations for both Claude Code and Codex.