Review handwritten homework
Skill geopopos/teaching-pipeline-plugin/skills/review-handwritten-homework
Spin up a local QR-code upload server so the user can submit a photo of pen-and-paper / handwritten work from their phone. After the upload lands, read the image and review the work using the course's teaching style. Use whenever the user asks to submit/share/review their handwritten work, paper exercises, "Pen-and-Paper Work", a "spin up a server for sharing/uploading", or any pen-and-paper exercise from any of their courses.From its SKILL.md
npx -y skills add geopopos/teaching-pipeline-plugin --skill review-handwritten-homeworkAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.6 KB, as published. Nobody here has run it
Review Handwritten Homework
This skill orchestrates a phone → laptop image upload via QR code, then reviews the user's handwritten work.
Prerequisites
Before first use, install the Python dependencies (one-time, in whichever venv you use for Claude Code work):
pip install -r ${CLAUDE_PLUGIN_ROOT}/requirements.txt
# or directly:
pip install flask qrcode pillow
For HEIC photos (iPhone default in some configurations), also pip install pillow-heif.
When to invoke
The user says any of:
- "spin up a server for sharing"
- "submit my homework"
- "share/upload my paper work"
- "I want you to review my handwritten work"
- "let me send a picture of my exercise"
- (or implicitly, when the user is in a course that uses Pen-and-Paper Work sections and they're done with one)
Steps
1. Determine the save directory and label
From conversation context, infer:
- Course root — where is the course this work belongs to? Check the user's auto-memory
MEMORY.mdfor active course paths, or the current working directory, or ask the user briefly. - Label — concise filename prefix describing what's being submitted, e.g.
module02_exB,lesson05_pen_paper,chapter03_q4. The label becomes part of the filename so the user can find files later.
The save directory should be <course-root>/homework/. If the directory doesn't exist, the script creates it.
2. Clean up any old server, then start a fresh one
pkill -9 -f qr_upload 2>/dev/null
rm -f /tmp/qr_server.log
sleep 1
Then launch the upload server in the background using Bash with run_in_background=true. Use the user's project venv if available; otherwise python3 system-wide.
<VENV>/bin/python -u ${CLAUDE_PLUGIN_ROOT}/skills/review-handwritten-homework/qr_upload.py \
--dir <COURSE_ROOT>/homework \
--label <LABEL> \
2>&1 | tee /tmp/qr_server.log
The tee lets us read the QR from /tmp/qr_server.log while the script runs.
3. Wait for the QR to render, then display it in the chat
until grep -q "Waiting for upload" /tmp/qr_server.log 2>/dev/null; do sleep 0.3; done
Then Read /tmp/qr_server.log and copy the QR ASCII block (the lines between "Scan this QR:" and the closing ▀▀▀…▀ line) directly into the assistant message inside a triple-backtick code fence. Do not just describe it or point at the file — the user needs to see it rendered in the chat to scan with their phone. Also surface the URL (e.g. http://192.168.1.120:8765/).
4. Arm the upload watcher
Run a polling watcher in the background so the system pings you when the upload lands:
until grep -q "saved:" /tmp/qr_server.log 2>/dev/null; do sleep 0.5; done
UPLOADED=$(grep "saved:" /tmp/qr_server.log | tail -1 | sed 's/.*saved: //')
echo "received: $UPLOADED"
Use Bash with run_in_background=true. You'll get a completion notification when it fires.
5. Read and review
When the watcher fires (or the user says "submitted"), read the saved image with the Read tool, then review using the course's teaching style.
If the course uses the professor skill or its conventions:
- Point at problems, ask a question, stop.
- Do NOT give the fix directly.
- If the work is correct, confirm clearly and tee up the next step.
- If multiple issues, surface the most fundamental one first (architectural/conceptual before stylistic).
Network gotchas (if the user reports the page won't load)
- Firewall —
sudo ufw status. If 8765 isn't allowed:sudo ufw allow 8765/tcp. - Wifi client isolation — common on guest/cafe wifi. Diagnostic: watch for ANY incoming connection (
ss -tn 'sport = :8765') while the user tries the URL on their phone. If nothing arrives even with firewall open, it's the router. Workaround: laptop joins phone hotspot (then phone IS the network, no isolation possible). - Wrong subnet — phone's IP must share the first three octets with the laptop's wifi IP for direct connection on a /24.
- Stale IP in QR — if the user just changed networks, restart the server so
get_local_ip()re-detects.
Script location and flags
The Python script is bundled at ${CLAUDE_PLUGIN_ROOT}/skills/review-handwritten-homework/qr_upload.py. It accepts --dir, --label, --port, and --multi. Single-shot by default (exits after one submission).
What the script handles
- Multi-page submissions — the upload page accepts multiple photos in one go. Files save with a
_pNNsuffix when more than one is in a submission, e.g.module02_exB_20260427_120000_p01.jpg,_p02.jpg. A submission with one photo gets no suffix. - EXIF auto-rotate — photos save upright regardless of how the phone was held. Pillow's
ImageOps.exif_transpose(). If the image format isn't recognized (e.g. HEIC withoutpillow-heif), it falls back to a raw save and the file may render sideways. - Mobile-friendly upload page — auto dark mode, big tap targets, thumbnail previews, "Uploading…" feedback on the submit button.
- Race-free shutdown — the script sleeps 1.5s after the upload before exiting, so Flask delivers the OK page fully (otherwise the phone shows "Loading Error" even though the file saved).