Read the user's own lesson notes, and the class register behind them
Schulcloud says what was uploaded and WebUntis says what was scheduled. Neither says what was *taught* — which point the teacher laboured, which example landed, what "will definitely come up". That lives in two places this server could not reach: the notes the user takes in the lesson, and WebUntis' class register. Notes are a directory of Markdown files (NOTES_DIR), not a table. They have to be writable from a phone in a classroom, readable when Postgres is down, and outlive this project, and files are the only shape that is all three — so the files are the truth and the index is a view of them, the same split as file_texts and the mirror. list_notes and get_note read disk, so they answer before the first crawl; search, what_changed and all three German prompts read them alongside the Schulcloud material. add_note writes one, and is the only thing in this server that writes anything. That is not a hole in the read-only invariant but a different store: it is bounded to NOTES_DIR by the same safeComponent/resolveWithin pair that stops a hostile Schulcloud filename escaping the mirror, so a note titled ../../.ssh/authorized_keys becomes a filename. Schulcloud and WebUntis stay GET-only and allowlisted respectively. NOTES_READONLY refuses writes outright. Appending targets the *lesson*, not the title: "halt das auch noch fest" mid-lesson carries a new title, and deriving the path from it would start a second note every time, which is the one thing append exists to prevent. Notes.app has no export — its bodies are compressed protobuf and the iCloud copy is encrypted — so scripting the app is not the clumsy route to the notes but the only one. scripts/export-apple-notes.js reads them through AppleScript into one JSON object per line, and `schulcloud note import` converts the HTML to Markdown, takes the Notes folder as the subject and the *creation* date as the lesson's date. Attachments cannot come across; a note that was a photo of the board imports as a line saying so, because importing it empty would hide the loss. The class register needed one API property to become cheap: getLessonTopic2017 answers per *series*, not per period, so a term is reconstructed by asking about the latest period of each lesson series and merging back by id — a few dozen calls for a school year rather than one per lesson. untis_lesson_topics now takes a subject as well as a period id, and UNTIS_HISTORY_DAYS of register goes into the index under a kind of its own, so "what did we actually do before the test" is searchable. Sharing the snapshot rather than duplicating it caught one thing on the way: the search tool's live path had to learn notes too, or fresh=true would have quietly disagreed with the index. 305 tests; 88/89 smoke against the local instance, the one failure being the H5P service that instance does not run. The live smoke could not be retaken: that session has lapsed and needs a fresh jwt cookie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
25
README.md
25
README.md
@@ -2,8 +2,9 @@
|
||||
|
||||
Read-only access to a [Schulcloud](https://github.com/hpi-schul-cloud) account —
|
||||
courses, boards, lessons, tasks and files — plus the timetable from
|
||||
[WebUntis](https://www.untis.at/), for **Claude**, via MCP, and for **you**, via
|
||||
a CLI that mirrors your coursework to disk.
|
||||
[WebUntis](https://www.untis.at/) and **the notes you take in class**, for
|
||||
**Claude**, via MCP, and for **you**, via a CLI that mirrors your coursework to
|
||||
disk.
|
||||
|
||||
Both are front ends over one core library and one live Schulcloud session, kept
|
||||
alive on a Pi.
|
||||
@@ -19,8 +20,10 @@ instance, not inferred from the upstream source.
|
||||
> *"Summarise the routing lesson from the LF10 course."*
|
||||
> *"What do I have tomorrow, and has anything been cancelled?"*
|
||||
> *"Quiz me on the DIN 5008 exercise from the DK room."*
|
||||
> *"What did we actually cover in Deutsch before the test — and what did I write down?"*
|
||||
|
||||
Twenty-eight tools, all read-only:
|
||||
Thirty-one tools. Everything that touches Schulcloud and WebUntis is read-only;
|
||||
`add_note` writes a file in your own notes directory and nowhere else.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
@@ -51,6 +54,9 @@ Twenty-eight tools, all read-only:
|
||||
| `api_get` | GET-only escape hatch for uncovered API surface |
|
||||
| `untis_timetable` | the school day from **WebUntis**: lessons, Entfall, Vertretung, room changes, period notes |
|
||||
| `untis_homework` | homework from WebUntis' class register — a separate list from Schulcloud's tasks |
|
||||
| `list_notes` | your own lesson notes — what you wrote down, by subject or date |
|
||||
| `get_note` | one of your notes in full |
|
||||
| `add_note` | write a note down during or after a lesson |
|
||||
| `untis_lesson_topics` | what previous lessons of a subject actually covered ("Unterrichtsinhalt") |
|
||||
|
||||
`download_file` extracts text from **PDF, DOCX, XLSX, PPTX and OpenDocument**
|
||||
@@ -63,6 +69,12 @@ MCP resource (`schulcloud://courses/<id>`, `schulcloud://rooms/<id>`) holding
|
||||
the same overview `get_course` and `get_room` return. In Claude Code, type `@`
|
||||
and part of the course name.
|
||||
|
||||
**Your own notes are the third source.** Schulcloud has the material and
|
||||
WebUntis has the schedule; neither has what the teacher actually stressed. Point
|
||||
`NOTES_DIR` at a directory of Markdown files and `search`, `what_changed` and
|
||||
all three prompts read it alongside everything else — including a migration path
|
||||
out of Apple Notes. See [docs/NOTES.md](docs/NOTES.md).
|
||||
|
||||
**Three ready-made prompts**, in German because the school is:
|
||||
|
||||
| | |
|
||||
@@ -87,6 +99,8 @@ schulcloud sync # mirror coursework to ~/Schulcloud
|
||||
schulcloud refresh --course <id>
|
||||
schulcloud fs tree /courses # browse the file manager ("Dateien")
|
||||
schulcloud fs get "/courses/<course>/<folder>"
|
||||
schulcloud note ls --subject Deutsch
|
||||
pbpaste | schulcloud note add --title Subnetting --subject LF07
|
||||
schulcloud token set # the monthly chore: hand the Pi a fresh Schulcloud token
|
||||
```
|
||||
|
||||
@@ -186,14 +200,15 @@ bypass, "what's new since…" — are sketched with their trade-offs in
|
||||
|
||||
```
|
||||
src/
|
||||
core/ client, types, board assembly, crawler, extraction, paths, WebUntis
|
||||
core/ client, types, board assembly, crawler, extraction, paths,
|
||||
WebUntis and its class register, your own notes
|
||||
store/ Postgres: crawl generations, diffs, full-text search
|
||||
indexer/ crawl → persist → mirror bytes → extract text → index
|
||||
mcp/ MCP server, tools, resources and prompts
|
||||
http/ express app, bearer auth, /api for the CLI
|
||||
cli/ CLI config, API client, sync engine
|
||||
bin/ http, stdio and cli entry points
|
||||
docs/ API findings, auth, deployment, CLI, roadmap
|
||||
docs/ API findings, auth, deployment, CLI, notes, roadmap
|
||||
deploy/ Caddyfile snippet, the Pi's compose file
|
||||
scripts/ probe, smoke, session diagnostics
|
||||
vendor/ upstream clones, git-ignored, for reference only
|
||||
|
||||
Reference in New Issue
Block a user