agentsclimarketplace

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

Install
npx -y skills add geopopos/teaching-pipeline-plugin --skill review-handwritten-homework

Assembled 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.md for 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)

  1. Firewall — sudo ufw status. If 8765 isn't allowed: sudo ufw allow 8765/tcp.
  2. 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).
  3. Wrong subnet — phone's IP must share the first three octets with the laptop's wifi IP for direct connection on a /24.
  4. 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 _pNN suffix 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 without pillow-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).

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.