@jamestesting888/tum-mcp 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changes
2
2
 
3
+ ## 0.7.0 — 2026-10-26
4
+
5
+ - Share one course app between Study and Exercises, with a single workspace
6
+ selector in the sidebar footer and a responsive mobile outline.
7
+ - Add `prepare-exercises` for source-backed weekly exercises with optional hints,
8
+ explanations, solutions and prepared checks. Support editable tables, long math
9
+ and code drafts, graph selections/paths and line graph sample points. Open work
10
+ uses rubric comparison; the app does not execute code or grade proofs.
11
+ - Keep step navigation in place, shorten controls and show section sources through
12
+ compact reference icons. Preserve browser progress, drafts and passage comments.
13
+ - Use `.tum-mcp/` for private course state and a cited agent course handbook covering
14
+ course organization, schedules, coursework/platforms, assessment and bonus rules.
15
+ Track reviewed source versions, coverage gaps and changed supporting sources.
16
+ - Rename `sync-study` to `sync-course` and `publish-study` to `publish-course`;
17
+ retain managed migration for previous skill names and preserve personal overrides.
18
+ - Keep the shipped app a minimal reusable component template, explicitly distinct
19
+ from an example of good course teaching. Keep rich synthetic tests out of the package.
20
+
21
+ New projects use `app/`; existing `study/` websites remain supported. Run
22
+ `tum-mcp study upgrade PROJECT` explicitly to rename legacy `.study/` metadata;
23
+ it preserves private records and archives the old root instructions. The separate
24
+ `study build PROJECT --upgrade-layout` moves a verified legacy web folder to `app/`
25
+ with a backup. Neither package installation nor update migrates course folders or
26
+ rebuilds personal sites automatically. Exercise publication is opt-in.
27
+
28
+ ## 0.6.1 — 2026-10-25
29
+
30
+ - Make `tum-mcp update` download and validate the latest npm release before
31
+ replacing the invoked installation and refreshing configured clients.
32
+ Preserve newer installed versions; `--local` refreshes without contacting npm.
33
+ - Make `tum-mcp uninstall` remove managed client setup and the npm package in
34
+ one command. Keep saved data by default; stop package removal on conflicts.
35
+ Client-only removal retains npm, and `--keep-package` supports managed cleanup.
36
+ - Run npm through Node on all platforms, serialize package operations, and use
37
+ an external worker while Windows replaces package files.
38
+ - Test actual global installations against a private synthetic registry, including
39
+ failed downloads/preparation/replacement, customized files, and data preservation.
40
+
3
41
  ## 0.6.0 — 2026-10-25
4
42
 
5
43
  - Merge course setup and updates into `sync-study`, with recovery for incomplete
@@ -20,8 +58,8 @@
20
58
  Reuse isolated test fixtures,
21
59
  retain native/platform coverage and expose slow tests and failures in CI.
22
60
  - Preserve Windows CRLF study instructions through repeated synchronization.
23
- - Group Artemis and Zulip implementation by provider, retaining read-only campus
24
- tools and Moodle compatibility. Fix OCR with aliased macOS temporary paths.
61
+ - Group Artemis and Zulip implementation by provider, retaining read-only
62
+ course tools. Fix OCR with aliased macOS temporary paths.
25
63
 
26
64
  Upgrading does not rebuild personal study sites automatically. Ask your agent to
27
65
  update the prepared website; customized renderer files are preserved for review.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Connect your coding agent to Moodle, Artemis and Zulip. Read course content and
4
4
  build a private, source-backed study workspace. Unofficial; independent of TUM.
5
5
 
6
- **Latest release: [0.6.0](https://www.npmjs.com/package/@jamestesting888/tum-mcp).**
6
+ **Package: [@jamestesting888/tum-mcp](https://www.npmjs.com/package/@jamestesting888/tum-mcp).**
7
7
  [Release notes](CHANGELOG.md).
8
8
 
9
9
  ## Agent setup
@@ -36,13 +36,9 @@ Select services with **Space**, then **Enter**. Choose an installed browser and
36
36
  complete sign-in there. Only selected tabs open; Zulip needs no API key.
37
37
  Enable the [course and channel scopes](docs/tum-mcp.md#connections-and-scopes) you want to use.
38
38
 
39
- | Client | Setup |
40
- | --- | --- |
41
- | Codex | `--client codex` · plugin and skills; enable/restart when prompted |
42
- | Claude Code | `--client claude` · MCP registration and skills |
43
- | Cursor | `--client cursor` · MCP registration and all five skills; restart |
44
- | Google Antigravity | `--client antigravity` · MCP registration and all five skills; restart |
45
- | Other MCP clients | `--client generic` · configuration for manual installation |
39
+ Choose a client with `--client`: `codex`, `claude`, `cursor` or `antigravity`.
40
+ Setup configures MCP and installs all six skills; follow any activation or
41
+ restart prompts. Use `generic` for manual MCP setup.
46
42
 
47
43
  For existing Cursor and Antigravity setups, run `tum-mcp update` to add the skills.
48
44
  Updates and uninstall preserve customized files and report them for review.
@@ -51,7 +47,7 @@ Supports macOS, Windows, glibc Linux x86_64/ARM64 and WSL 3.x. In WSL, sign-in
51
47
  opens a Windows browser. Add `--host windows` to setup for a Windows client that
52
48
  uses the WSL backend. [Browser and WSL setup](docs/setup.md#wsl).
53
49
 
54
- Setup supplies a private Python runtime. New connections use encrypted local
50
+ Setup supplies a private Python runtime. Connections use encrypted local
55
51
  files and need no native keyring, Git or Docker.
56
52
 
57
53
  ## Read and study
@@ -67,15 +63,16 @@ posting and read-state changes are unavailable.
67
63
 
68
64
  ### Skills and example requests
69
65
 
70
- The package includes five skills. Once you've chosen a course from
66
+ The package includes six skills. Once you've chosen a course from
71
67
  your connections, try requests like these:
72
68
 
73
69
  | Skill | Use it to | Example request |
74
70
  | --- | --- | --- |
75
71
  | `read-content` | Read and explain course sources with citations | "Can you explain what we covered in last week's lecture?" |
76
- | `sync-study` | Create or refresh a course project | "Set up a study project for this course." |
72
+ | `sync-course` | Create or refresh a course project | "Set up a study project for this course." |
77
73
  | `prepare-study` | Build lessons, flashcards and practice | "Make me a study website with flashcards and practice questions." |
78
- | `publish-study` | Put a prepared website on GitHub Pages | "Put my study site online so I can open it on my phone." |
74
+ | `prepare-exercises` | Prepare optional hints, checks and worked solutions | "Help me work through this week's exercises in the same app." |
75
+ | `publish-course` | Put a prepared website on GitHub Pages | "Put my study site online so I can open it on my phone." |
79
76
  | `audio-notes` | Save spoken lessons under `audio/` | "Turn this into audio notes I can listen to on the go." |
80
77
 
81
78
  Useful follow-ups in an existing project:
@@ -90,11 +87,12 @@ Client setup installs managed copies of the
90
87
 
91
88
  ### Recommended study flow
92
89
 
93
- 1. **`sync-study`**: Set up a project for your course and collect its material.
90
+ 1. **`sync-course`**: Set up a project for your course and collect its material.
94
91
  Use it again when new material is available.
95
92
  2. **`prepare-study`**: Turn that material into a study website with explanations,
96
- flashcards and practice questions.
97
- 3. **`publish-study`** (optional): Put the website online so you can study on your
93
+ flashcards and practice questions. Use **`prepare-exercises`** for released sheets
94
+ with prepared interactive guidance. Both workspaces share one app; either can come first.
95
+ 3. **`publish-course`** (optional): Put the website online so you can study on your
98
96
  phone. Check the [publishing requirements](#publish-for-phone-access-optional) first.
99
97
  4. **`audio-notes`** (optional): Turn the prepared content into spoken lessons.
100
98
  Use them to revisit prepared topics while walking or commuting.
@@ -143,19 +141,20 @@ your project looks like this, with audio added when requested:
143
141
 
144
142
  ```text
145
143
  my-course/
146
- ├── AGENTS.md Course overview and source index for your agent
144
+ ├── AGENTS.md Your rules and a short pointer to agent context
147
145
  ├── materials/
148
146
  │ ├── content/ Lectures, slides and reading
149
147
  │ ├── exercises/ Exercise sheets and practice material
150
148
  │ ├── exams/ Available exam and revision material
151
149
  │ └── other/ Announcements and course information
152
- ├── study/
150
+ ├── app/
153
151
  │ ├── content.json Lessons, flashcards and practice questions
154
152
  │ └── index.html Your study website
155
153
  ├── audio/ Optional audio notes
156
154
  │ ├── scripts/ Narration text
157
155
  │ └── tracks/ Audio lessons
158
- └── .study/
156
+ └── .tum-mcp/
157
+ ├── course.md Cited course orientation, rules and unknowns
159
158
  ├── state.json Course identity and source versions
160
159
  └── comments.json Passage comments, when added
161
160
  ```
@@ -167,11 +166,13 @@ stay local. Study progress is saved in your browser.
167
166
  ## Update
168
167
 
169
168
  ```sh
170
- npm install --global @jamestesting888/tum-mcp@latest
171
169
  tum-mcp update
172
170
  ```
173
171
 
174
- `update` prepares the invoked package's runtime and refreshes configured clients.
172
+ `update` downloads the latest release, validates its backend, installs it in the
173
+ same npm location and refreshes configured clients. It never downgrades a newer
174
+ installation. Use `tum-mcp update --local` to refresh the installed version without checking npm.
175
+ From 0.6.0 or earlier, upgrade the npm package once to obtain this command.
175
176
  Sessions, scopes and downloads carry forward. User-edited entries and skills are
176
177
  preserved and reported for review. Reload the client; refresh its cached plugin
177
178
  if needed. Study sites are updated separately through the study workflows.
@@ -179,14 +180,14 @@ if needed. Study sites are updated separately through the study workflows.
179
180
  ## Remove
180
181
 
181
182
  ```sh
182
- tum-mcp uninstall --all
183
- npm uninstall --global @jamestesting888/tum-mcp
183
+ tum-mcp uninstall
184
184
  ```
185
185
 
186
- Managed registrations and unchanged skills are removed. Settings, sessions,
186
+ The npm package, managed registrations and unchanged skills are removed. Settings, sessions,
187
187
  downloads and study projects are preserved. After closing clients, add
188
188
  `--purge-runtime` to remove generated runtimes or `--forget-credentials` to
189
- remove saved local sessions and historical Git tokens. [Removal details](docs/setup.md#update-and-remove).
189
+ remove saved local credentials. [Removal details](docs/setup.md#update-and-remove).
190
+ Use `--client NAME` to remove just one client, or `--keep-package` to retain npm.
190
191
 
191
192
  [Guide](docs/tum-mcp.md) · [Setup](docs/setup.md) · [Study workflows](docs/study.md) · [Development](docs/development.md)
192
193
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  The npm package is the distribution. Its wheel contains one Python package,
4
4
  `src/tum_mcp/`, with peer `moodle/`, `artemis/` and `zulip/` provider packages.
5
- The retired standalone Moodle plugin and its checkout installer are no longer
6
- maintained.
7
5
  Client adapters still generate the configuration each host needs.
8
6
 
9
7
  ## Checks
@@ -19,6 +17,7 @@ npm test
19
17
  npm pack --ignore-scripts --pack-destination dist
20
18
  npm run test:consumer
21
19
  npm run test:upgrade
20
+ npm run test:package-lifecycle
22
21
  ```
23
22
 
24
23
  Python 3.13 is the managed runtime. Install Tesseract for OCR tests. Default tests
@@ -41,7 +40,7 @@ Each layer owns a different failure boundary:
41
40
  | Public upgrade | Earlier published versions, repair, ownership conflicts and removal |
42
41
  | Native platform | OS credential/ACL behavior; WSL Windows browser transport on both engines |
43
42
 
44
- Study integration fixtures build the real starter once, then copy it into a fresh
43
+ Study integration fixtures build the private rich regression fixture once, then copy it into a fresh
45
44
  project for each test. Launcher tests only need an HTML entry point. Neither
46
45
  shares mutable course state. Client transaction tests use five small synthetic
47
46
  skills, including nested binary assets; full-pack export, migration and installed
@@ -57,8 +56,11 @@ CI runs macOS, Windows and Linux, plus both distribution engines on WSL 3.x.
57
56
  Consumer checks install the actual npm artifact, discover tools, build a study
58
57
  site and test available Chromium browsers with temporary profiles and synthetic
59
58
  pages. WSL verifies Windows browser transport. No live account is used.
60
- Upgrade checks cover public 0.4.1/0.5.0, client refresh, customized-file conflicts,
59
+ Upgrade checks cover public 0.6.1, client refresh, customized-file conflicts,
61
60
  failed preparation, runtime repair and removal that preserves work/credentials.
61
+ The package-lifecycle check uses a local synthetic registry and real isolated
62
+ global npm prefixes to verify downloads, backend validation, replacement,
63
+ failure ordering and one-command removal. It does not publish a test release.
62
64
  All five jobs must pass on the exact pushed commit before completion is claimed.
63
65
 
64
66
  Live Moodle checks are opt-in: `TUM_MOODLE_LIVE=1 uv run pytest tests/live -q`.
@@ -97,14 +99,7 @@ tag and artifact integrity before pushing the release documentation.
97
99
 
98
100
  Client setup installs managed copies of the repository's skills; those copies are
99
101
  outputs, not another source tree. Use `skills customize` for a personal override.
100
- The Codex adapter generates a small host plugin because that client uses it to
101
- load skills and MCP together; it is separate from the removed top-level Moodle
102
- plugin distribution.
103
-
104
- Keep Moodle's existing native-store service/account identity, session paths and
105
- download locations stable during migrations. Environment variable names and the
106
- `tum-moodle` CLI alias remain compatible; its implementation now resides inside
107
- `tum_mcp.moodle`. No automatic native-store secret extraction occurs.
102
+ The Codex adapter generates a small host plugin to load skills and MCP together.
108
103
 
109
104
  Extraction uses bounded subprocesses. POSIX enforces CPU/process-group limits;
110
105
  Windows terminates the worker and observed children. Study imports and preview
@@ -115,12 +110,14 @@ material remain untrusted data for the skill.
115
110
 
116
111
  ## Study renderer
117
112
 
118
- Author `study/content.json`. Formula blocks use LaTeX compiled locally into safe
113
+ Author `app/content.json` and optional `app/exercises.json`; legacy `study/` remains supported. Formula blocks use LaTeX compiled locally into safe
119
114
  native MathML; legacy MathML remains accepted. Unit `path` labels group the outline
120
115
  without changing learning-progress revisions. Shared modules render all units;
121
116
  content changes do not require a framework or per-unit HTML.
122
117
 
123
- The distributed design/teaching/verification contracts live under
118
+ Shared renderer assets live in `src/tum_mcp/bundled/course_app/site/`. The shipped
119
+ component preview is deliberately minimal; richer learning pilots live in
120
+ `tests/fixtures/course_app/`. The distributed design/teaching/verification contracts live under
124
121
  `src/tum_mcp/bundled/skills/prepare-study/references/`. Local agent notes and
125
122
  screenshots belong in ignored `docs/local/` or `.omo/`. Keep real course projects,
126
123
  session files, databases, downloads and environment files out of artifacts.
package/docs/setup.md CHANGED
@@ -20,7 +20,7 @@ tum-mcp doctor
20
20
 
21
21
  Use `--client claude`, `cursor`, `antigravity`, or `generic` for another client.
22
22
  Codex needs the plugin enabled and a restart when prompted. Cursor and
23
- Antigravity setup installs all five skills and MCP configuration; restart each
23
+ Antigravity setup installs all six skills and MCP configuration; restart each
24
24
  client afterward. Existing MCP-only setups gain the skills through `tum-mcp update`
25
25
  or another setup run. Generic setup writes configuration for manual installation.
26
26
  Setup preserves unrelated settings and refuses to
@@ -35,11 +35,10 @@ Run **`tum-mcp connect`**, with no arguments, in an interactive terminal:
35
35
  3. Complete TUM sign-in/MFA in the opened window. Only selected service tabs open.
36
36
  4. Return to the terminal for the saved-connection result.
37
37
 
38
- Moodle uses exactly the same selector and storage flow as Artemis and Zulip.
39
- No API key or Keychain prompt is needed. A temporary browser profile keeps the
40
- connection separate from your everyday browser. Ctrl+C closes it and preserves
38
+ All three services use the same sign-in and encrypted session storage flow.
39
+ No API key or native credential-store prompt is needed. A temporary browser
40
+ profile keeps the connection separate from your everyday browser. Ctrl+C closes it and preserves
41
41
  connections that already completed. A dumb terminal uses numbered prompts.
42
- Old service arguments and `--service`/`--auth` flags are no longer accepted.
43
42
 
44
43
  Chrome, Edge, Brave, Chromium, Vivaldi and Opera are detected in their standard
45
44
  locations. Selected preview channels and Opera GX are detected where installed.
@@ -113,19 +112,25 @@ browser discovery still requires functioning interop.
113
112
  Sessions are encrypted with owner-restricted local key files. This avoids native
114
113
  credential-store prompts, but anyone obtaining both key and ciphertext can
115
114
  decrypt them. [Storage locations and recovery](tum-mcp.md#connections-and-scopes)
116
- explain migration and backup. Existing Moodle data and downloads are preserved.
115
+ explain where sessions are saved and how to back them up.
117
116
 
118
117
  ## Update and remove
119
118
 
120
119
  ```sh
121
- npm install --global @jamestesting888/tum-mcp@latest
122
120
  tum-mcp update
123
121
  ```
124
122
 
125
- `update` activates the backend bundled in the invoked npm package and refreshes
126
- previously configured clients. It does not fetch a newer npm version itself,
127
- connect services, change scopes or register additional clients. `setup --client
128
- NAME` explicitly registers one client and can also refresh that client's managed
123
+ From 0.6.1, `update` retrieves the latest npm release, validates its backend in a
124
+ temporary installation, replaces the invoked package and refreshes previously
125
+ configured clients. It preserves the installation prefix and global/local mode.
126
+ It does not connect services, change scopes or register additional clients.
127
+ It never downgrades a newer installation. `update --local` skips npm and repairs
128
+ or refreshes the installed version. Source/pinned builds use this local mode;
129
+ temporary npx and linked source installations are not replaced automatically.
130
+ Automatic replacement also stops if the package directory contains configured
131
+ sessions, downloads or course work; move that data outside the package first.
132
+ For 0.6.0 and earlier, first install the newer npm package once.
133
+ `setup --client NAME` explicitly registers one client and can also refresh that client's managed
129
134
  files. WSL Windows registrations keep their distribution/user/data paths.
130
135
 
131
136
  Ownership receipts track the entries and files installed by TUM MCP. Unchanged
@@ -138,57 +143,40 @@ not selected, and a damaged runtime is repaired into a fresh generation. A newer
138
143
  managed client is not silently downgraded by an older package; intentional
139
144
  rollback starts by unregistering that client, then registering the reviewed version.
140
145
 
141
- 0.4.1/0.5.0 installations without receipts are adopted only after matching their
142
- retained runtime's canonical client output. The seven former workflow skills and the three website skills are replaced by
143
- `read-content`, `sync-study` and `prepare-study`; `audio-notes` adds
144
- spoken lessons derived from the prepared website. Personal overrides
145
- under retired names remain in the config `skills/` directory; review and merge
146
- retrieval guidance into `read-content` before exporting a replacement pack.
147
146
  Reload MCP servers in Cursor/Antigravity, restart Claude as appropriate, and
148
147
  update/reinstall TUM MCP from the personal source in Codex Plugins if its cached
149
148
  copy still shows the older skills. Writing source files does not activate a host.
150
149
 
151
150
  Remove a single client with `tum-mcp uninstall --client cursor` (replace the
152
151
  client as needed; add `--host windows` from WSL for a Windows client). To remove
153
- all managed registrations before uninstalling npm:
152
+ the npm package and all managed registrations:
154
153
 
155
154
  ```sh
156
- tum-mcp uninstall --all
157
- npm uninstall --global @jamestesting888/tum-mcp
155
+ tum-mcp uninstall
158
156
  ```
159
157
 
160
158
  Custom edits and user-added files are preserved. Codex's local source/catalog
161
159
  entry can be removed, but its cached/active plugin must also be removed or
162
- disabled through Codex Plugins. No npm uninstall hook deletes user data.
160
+ disabled through Codex Plugins. Package removal runs only after successful
161
+ client cleanup; conflicts preserve npm so you can review and retry. `--all`
162
+ is accepted explicitly, and `--keep-package` removes client setup without npm.
163
+ No npm lifecycle hook performs cleanup or reads credentials.
163
164
 
164
165
  For explicit cleanup, **close coding clients first**, then run:
165
166
 
166
167
  ```sh
167
- tum-mcp uninstall --all --purge-runtime --forget-credentials
168
- npm uninstall --global @jamestesting888/tum-mcp
168
+ tum-mcp uninstall --purge-runtime --forget-credentials
169
169
  ```
170
170
 
171
171
  `--purge-runtime` deletes verified managed runtime generations after the Python
172
172
  helper exits, and removes unchanged managed bootstrap/browser-driver caches.
173
173
  Active tracked backends or unrecognized runtime files stop automatic removal.
174
174
  Paths overlapping configured assignment work, downloads or Moodle data are
175
- protected. `--forget-credentials` removes local encrypted service sessions, keys
176
- and participation Git tokens; it does not revoke remote credentials. Native
177
- Keychain/Credential Manager/Secret Service records and legacy Moodle sessions
178
- stay intact. Settings, custom skills, course cache, downloads and assignment
179
- work are retained by these commands. No command recursively deletes the campus
175
+ protected. `--forget-credentials` removes saved local credentials and session
176
+ keys; it does not revoke remote credentials. Settings, custom skills, course
177
+ cache, downloads and assignment work are retained by these commands.
178
+ No command recursively deletes the campus
180
179
  data root. Course audio and optional shared speech models/runtimes also remain.
181
180
  Remove a shared speech cache only when explicitly requested and no other course
182
181
  needs it; its host paths are documented in the audio skill. If removal is
183
- interrupted, run npm `setup` to recover before reuse.
184
-
185
- ## Existing Moodle-only installations
186
-
187
- The older `tum-moodle` commands and sixteen `moodle_*` tools remain compatible.
188
- The separate source/plugin installer retains its original native credential
189
- store: macOS Keychain, Windows Credential Manager, or Linux Secret Service.
190
- Its native-store identity, existing session paths and downloads remain intact.
191
- The combined `tum-mcp serve` uses local-file storage and directs all login
192
- requests to `tum-mcp connect`. Reconnect once through that selector to create the
193
- new session; no automatic extraction of older native-store secrets occurs.
194
- Disable the older plugin when activating TUM MCP to avoid duplicate tools.
182
+ interrupted, run `tum-mcp setup --client NAME` to recover before reuse.
package/docs/study.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # Course study projects
2
2
 
3
- The five reading, study, audio and publishing skills ship with version **0.6.0**.
3
+ Six focused skills cover source reading, course sync, Study preparation, Exercises preparation, audio and publishing.
4
4
  They run in your local agent, using the connected
5
5
  MCP tools to read course sources. The CLI validates and stores the resulting local
6
6
  files; it does not run an AI model.
7
7
 
8
8
  ## Publish a study website
9
9
 
10
- The optional `publish-study` skill makes a prepared site accessible from a phone
10
+ The optional `publish-course` skill makes a prepared site accessible from a phone
11
11
  or another computer. Its GitHub repository is private; the Pages website is
12
12
  public. An eligible paid GitHub plan is required for Pages from private repositories.
13
13
  This workflow does not provide private website access. Review lesson text,
@@ -29,10 +29,16 @@ tum-mcp study publish --project PROJECT --public-site --repository OWNER/NAME
29
29
 
30
30
  Names use `study-<course>-<semester>` in lowercase with hyphens. A collision gets
31
31
  a stable course suffix; existing repositories are never made public. Settings in
32
- `.study/github.json` bind updates to that course, semester and repository. A
32
+ `.tum-mcp/github.json` bind updates to that course, semester and repository. A
33
33
  failed setup retries the same saved repository rather than creating duplicates.
34
34
 
35
- Only the generated website renderer and lesson data are uploaded. Downloaded
35
+ Study is the default hosted workspace, including for old settings. Exercise
36
+ statements, hints and full solutions need explicit `--include-exercises` on the
37
+ publish command after reviewing their public exposure. `--exclude-exercises`
38
+ returns a combined site to Study only, using the same repository and URL. An
39
+ Exercises-only site also requires explicit inclusion.
40
+
41
+ Only the selected generated website renderer and data are uploaded. Downloaded
36
42
  originals, credentials, AGENTS.md, study inventory, passage comments, progress,
37
43
  audio and arbitrary files stay local. Git itself is not required: the publisher
38
44
  uses authenticated GitHub API calls through `gh`. Local publication settings do
@@ -63,7 +69,7 @@ and offline support are not included.
63
69
 
64
70
  ## Start with one course
65
71
 
66
- Use `sync-study` for both initial setup and later source updates. For a new project,
72
+ Use `sync-course` for both initial setup and later source updates. For a new project,
67
73
  provide the course title and semester when known. The agent verifies
68
74
  its availability, resolves source IDs and asks about ambiguous mappings. A project
69
75
  cannot switch course or semester. Use a separate directory for another course.
@@ -86,18 +92,42 @@ pse-ws26/
86
92
  exams/ Available revision material
87
93
  exercises/ Released statements and practice references
88
94
  other/ Announcements and course information
89
- .study/state.json Course binding, source versions and coverage
90
- study/ Optional interactive study website
95
+ .tum-mcp/
96
+ course.md Cited course structure, logistics, rules and unknowns
97
+ state.json Course binding, source versions and coverage
98
+ notes/ Detailed course/agent notes when needed
99
+ app/ Shared Study and Exercises website
100
+ content.json Optional source-backed Study content
101
+ exercises.json Optional prepared exercise guidance
91
102
  audio/ Optional category audio derived from the prepared website
92
103
  ```
93
104
 
94
- `AGENTS.md` contains a concise prerequisite/notation map, verified assessment facts,
95
- source locations, coverage and next actions. Detailed source content stays in
96
- materials. Existing user instructions are preserved; source text is untrusted data.
105
+ `AGENTS.md` retains user rules and points agents to `.tum-mcp/course.md`. That short
106
+ handbook explains the course's goals, lectures/tutorials, weekly work and platforms,
107
+ assessment/bonus conditions, communication and preparation map. Claims carry source
108
+ links/locators and checked dates; unknowns and conflicting announcements stay visible.
109
+ Detailed source content remains in materials/. All TUM MCP course state, plans and
110
+ agent notes live under .tum-mcp/.
111
+
112
+ `sync-course` reads the relevant authoritative sources and maintains the handbook;
113
+ the CLI scaffolds it without pretending to understand the course. Its review receipt
114
+ records the local generation and hashes of supporting sources. `study inspect`
115
+ reports changed supporting sources and coverage gaps so refreshes can focus on
116
+ affected claims. A reviewed local snapshot does not establish live freshness or
117
+ complete understanding. See the bundled sync-course context reference for the full
118
+ source-review workflow.
119
+
120
+ To rename existing private `.study/` state explicitly, close other course processes
121
+ and run `tum-mcp study upgrade PROJECT`. It preserves unknown private files,
122
+ comments, publication settings and the full old root instructions as
123
+ `.tum-mcp/legacy-AGENTS.md`. Course identity, materials and browser progress stay the
124
+ same. Existing legacy state remains readable until upgrade; two state folders are
125
+ a conflict to reconcile, never silently merged. This differs from the web-folder
126
+ upgrade below.
97
127
 
98
128
  ## State-based workflow
99
129
 
100
- `sync-study` starts with `tum-mcp study status PROJECT`, a read-only local check:
130
+ `sync-course` starts with `tum-mcp study status PROJECT`, a read-only local check:
101
131
 
102
132
  | Mode | Action |
103
133
  | --- | --- |
@@ -112,7 +142,7 @@ The underlying `study init` and `study sync` commands remain separate.
112
142
 
113
143
  ## Keep it current
114
144
 
115
- Ask `sync-study` to compare the project with connected upstream sources. It
145
+ Ask `sync-course` to compare the project with connected upstream sources. It
116
146
  reuses the saved course, semester, source IDs and reliable revisions. It retrieves
117
147
  changed material and updates course knowledge. Incomplete listings never imply withdrawal. Withdrawn sources
118
148
  keep their local originals; edited or deleted managed files are reported for review.
@@ -127,10 +157,9 @@ restores files when possible and reports any remaining conflict.
127
157
 
128
158
  ## Prepare the website
129
159
 
130
- Ask `prepare-study` to create or extend `study/`. Units follow the course's
160
+ Ask `prepare-study` to create or extend the Study workspace in `app/`. Units follow the course's
131
161
  lecture/week sequence when useful, or use consistent topics. For a single course, use a flat list or one category level followed by topics,
132
- including several topics in one week. The multi-area demo also exercises deeper
133
- legacy hierarchies. Every unit has:
162
+ including several topics in one week. Every unit has:
134
163
 
135
164
  - A concise explanation with source links, assumptions and worked examples.
136
165
  - Editable LaTeX formulas and inline variables, diagrams, code or comparison tables where they explain the concept.
@@ -173,7 +202,7 @@ glossaries, cards, questions and feedback. Code and dollar signs stay literal.
173
202
 
174
203
  Select text in Content, Flashcards or Practice, then click **Comment** to record what is unclear.
175
204
  You can also use Ctrl/Command+Alt+M after selecting text. The local preview saves
176
- notes in `.study/comments.json`, separately from learning progress. Click a
205
+ notes in `.tum-mcp/comments.json`, separately from learning progress. Click a
177
206
  numbered passage marker or highlighted text to open its notes beside the quote.
178
207
  Markers sit beside section headings or the current card/question position.
179
208
  Notes can be edited, resolved or reopened; changed passages retain their original quotes.
@@ -186,7 +215,7 @@ automatically while the preview is open. Your draft is kept if a note changes
186
215
  elsewhere; choose Use latest or Keep mine before saving. Reload after the agent
187
216
  rebuilds the teaching content.
188
217
 
189
- Content lives in `study/content.json`; shared renderer modules and CSS keep the
218
+ Content lives in `app/content.json`; shared renderer modules and CSS keep the
190
219
  site extensible. Source hashes identify affected units. A stale source stops a
191
220
  build until the explanation is reviewed. Unchanged units retain their revision;
192
221
  revised questions receive separate progress. Customized templates are preserved
@@ -210,11 +239,44 @@ or an unattended preview, use `tum-mcp study serve ./pse-ws26 --port 8765`.
210
239
  If port 8765 is busy, stop the other preview or choose `tum-mcp study --port 8766`.
211
240
  Keep the same address and port between sessions so browser progress remains available.
212
241
 
213
- The preview binds `127.0.0.1`, serves `study/` and saves comments through a bounded
242
+ The preview binds `127.0.0.1`, serves `app/` (or the existing legacy `study/`) and saves comments through a bounded
214
243
  same-origin endpoint. It makes no campus requests.
215
244
  No course credentials are copied into the website. Browser storage holds progress
216
245
  locally. Nothing is published by these commands.
217
246
 
247
+ ## Prepare weekly exercises
248
+
249
+ Ask `prepare-exercises` to prepare released weekly sheets in the same app. Study
250
+ and Exercises can be prepared in either order. When both exist, a selector at the bottom of the sidebar
251
+ switches workspaces and remembers the current place. On phones it lives inside the
252
+ open course outline; there is no second switch in the reading area. Exercises are
253
+ listed directly under sheets, optionally grouped by category.
254
+
255
+ The original statement and subparts come first. Students can solve on paper, mark
256
+ an exercise Reviewed, reveal a worked solution directly, or open **Guidance**.
257
+ Prepared guidance offers one conceptual step, requested hints and specific repair
258
+ feedback. Numeric checks state units/tolerance; choice checks have four options.
259
+ Fillable tables check numeric cells and explicit text variants individually. Graph
260
+ questions support node/edge selections and ordered paths with a visible diagram.
261
+ Line graphs allow sample-point placement, coordinate edits and keyboard adjustment.
262
+ Long math and programming work use saved writing areas and prepared rubrics; code
263
+ is never executed, and symbolic work, proofs and programs are not automatically
264
+ graded. Arbitrary graph drawing can use paper and rubric comparison. Typing, hints and step-by-step guidance are optional. Reviewed records an
265
+ action, not mastery or an assessment result.
266
+
267
+ Drafts and assistance stay in this browser. Editing a checked answer clears its
268
+ verdict. Semantic changes start fresh progress for only that exercise, while retaining older
269
+ browser records; source-hash review alone preserves progress.
270
+ Reset on each workspace home affects only that workspace. Exercise passage comments
271
+ use the existing private comment service; hosted exports do not expose it.
272
+
273
+ Existing `study/` projects continue to work. New projects use `app/`. To upgrade a
274
+ verified legacy layout explicitly, run `tum-mcp study build PROJECT --upgrade-layout`.
275
+ It keeps the original tree at `.tum-mcp/layout-backup/`, preserves the course identity,
276
+ learning revisions, comments, settings and personal files, and stops on customized
277
+ generated files, an occupied backup or two layouts. Repeating a completed upgrade
278
+ is a no-op. Do not create `app/` alongside an existing `study/` by hand.
279
+
218
280
  ## Create and maintain audio notes
219
281
 
220
282
  Use `prepare-study` to author the interactive course, then ask `audio-notes` to
@@ -225,7 +287,7 @@ Create audio notes from this prepared study website, one file per category.
225
287
  Keep course outputs under audio/; prefer free ElevenLabs with local Kokoro fallback.
226
288
  ```
227
289
 
228
- Audio Notes reads `study/content.json`, retaining the course's language, examples,
290
+ Audio Notes reads `app/content.json`, retaining the course's language, examples,
229
291
  assumptions and limitations. It turns tables, formulas, code and diagram sequences
230
292
  into explanations that work without a screen. It does not independently research
231
293
  the course or create new subject matter. An audio-only request reuses the prepared
@@ -287,29 +349,32 @@ material with the previous verified coverage. This is an agent skill, not a new
287
349
 
288
350
  ## Packaged app template
289
351
 
290
- The complete web-app template is packaged with `prepare-study` in `assets/site/`.
352
+ The shared web-app template is packaged in `bundled/course_app/site/`, separately from skill instructions.
291
353
  It includes the shell, navigation, flip cards, practice, diagrams and styling.
292
- Agents author course content in `study/content.json`; `study build` installs the
354
+ Agents author course content in `app/content.json`; `study build` installs the
293
355
  shared template, preserves customized files and checks sources. This keeps new
294
356
  course sites consistent without rebuilding the interface. `tum-mcp study template`
295
357
  shows the bundled files and their combined revision for reproducible development.
296
358
 
297
- ## Try the synthetic example
359
+ ## Try the component template
298
360
 
299
361
  After installing a locally built development artifact:
300
362
 
301
363
  ```sh
302
- tum-mcp study demo ./algorithms-demo
303
- tum-mcp study serve ./algorithms-demo
364
+ tum-mcp study demo ./component-preview
365
+ tum-mcp study serve ./component-preview
304
366
  ```
305
367
 
306
- This creates a clearly labelled synthetic course on graphs, BFS and DFS, including
307
- LaTeX rendered as native MathML, a step-through graph, comparison tables, flashcards and practice.
368
+ This creates one small Study unit and one exercise covering prose, code, tables,
369
+ formulas, graph/process diagrams, cards and response controls. It is a reusable
370
+ component template, not necessarily a good teaching or question-authoring example.
371
+ Actual course authoring follows reviewed sources and the preparation skills. Rich
372
+ learning pilots stay in private test fixtures.
308
373
  It uses no campus account. An existing edited demo is preserved.
309
374
 
310
375
  ## Local helper inputs
311
376
 
312
- Agents use `tum-mcp study schema snapshot` and `tum-mcp study schema content` for
377
+ Agents use `tum-mcp study schema snapshot`, `schema content` and `schema exercises` for
313
378
  validated input schemas. After authenticated tools verify availability, the agent
314
379
  stages a source snapshot with exact course identity, coverage and resource revisions.
315
380
  The helper does not authenticate the snapshot or grant any campus access itself.
package/docs/tum-mcp.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # TUM MCP guide
2
2
 
3
3
  Package: `@jamestesting888/tum-mcp`, CLI: `tum-mcp`, backend: Python 3.13.
4
- Version **0.6.0** provides read-only course retrieval and five local study workflows.
4
+ Development version **0.6.1 (unreleased)** adds npm package management to the
5
+ read-only course retrieval and six local course workflows.
5
6
 
6
7
  ## Current capabilities
7
8
 
8
- All sixteen `moodle_*` compatibility tools remain available, including materials,
9
- announcements, forums and explicitly scoped private-record reads. Artemis adds
9
+ The sixteen `moodle_*` tools provide materials, announcements, forums and
10
+ explicitly scoped private-record reads. Artemis adds
10
11
  available-course metadata, lectures and learning units, student PDF attachments,
11
12
  released non-quiz statements and permitted exact-commit feedback. Zulip provides
12
13
  scoped channel/topic search and cited discussion reads.
13
14
 
14
15
  Website access is read-only. There are no public exercise workspace, test-run,
15
- submission, start, deployment, rebuild or push workflows. The retained internal
16
- remote gate rejects every action even if an old verification flag is enabled.
16
+ submission, start, deployment, rebuild or push workflows.
17
17
  Local downloads, source inventories, study-site builds and checkpoint storage
18
18
  are local operations; they do not post, submit or mark campus content as read.
19
19
 
@@ -67,22 +67,19 @@ and Python 3.13.15; an existing matching uv, including the verified cached downl
67
67
  is reused. Source checkouts and system
68
68
  Python are unnecessary for consumers. Supported bootstrap targets are macOS
69
69
  arm64/x64, Windows arm64/x64, and glibc Linux arm64/x64; hands-on verification is
70
- recorded separately. New campus sessions use permission-restricted local keys and need no Secret Service.
71
- Clean consumer bootstrap, idempotent setup, diagnostics, and skill validation
72
- passed on macOS ARM64 and in Linux ARM64 and x86_64 containers. The x86_64 check
73
- ran through Docker amd64 emulation on an ARM64 Mac; packaged MCP protocol checks
74
- and an encrypted credential round trip through a real GNOME Secret Service in an
75
- isolated D-Bus session also passed. Real Linux desktop browser sign-in, macOS x64,
76
- and Windows hands-on checks remain open. New storage and terminal behavior need
77
- the current checkout’s test evidence; earlier native-store results do not certify them. The installer targets glibc Linux
78
- x86_64/amd64 and aarch64/ARM64.
79
-
80
- `update` prepares the backend bundled in the invoked package, refreshes
81
- previously configured clients and preserves older runtime generations.
70
+ recorded separately. Campus sessions use permission-restricted local keys.
71
+ CI checks clean installation, updates, removal, MCP discovery and synthetic
72
+ browser workflows on macOS, Windows, Linux and both WSL engines. See the
73
+ [development guide](development.md#checks) for the verification layers.
74
+
75
+ `update` downloads and validates the latest release, replaces the invoked npm
76
+ installation and refreshes previously configured clients. `update --local`
77
+ refreshes the installed version without npm. Retained runtimes keep pinned
78
+ clients working, and newer installed versions are not silently downgraded.
82
79
  [Update and removal behavior](setup.md#update-and-remove) covers ownership
83
- receipts, legacy adoption, conflict handling and explicit cleanup. To obtain a newer public package after publication,
84
- invoke its reviewed version explicitly. It does not publish a release or replace
85
- customized client skills silently. If interrupted setup leaves `setup.lock`,
80
+ receipts, conflict handling and explicit cleanup. `uninstall` removes managed
81
+ clients and npm; `--client NAME` or `--keep-package` retains the package.
82
+ Customized files and saved data are preserved. If interrupted setup leaves `setup.lock`,
86
83
  confirm that no setup process is running before removing that directory locally.
87
84
  An invalid or incomplete runtime is never selected for serving. Failed validation
88
85
  clears its ready marker so explicit setup can rebuild it; older runtimes remain
@@ -108,10 +105,9 @@ tum-mcp scope zulip ACCOUNT_ID --channel CHANNEL_ID
108
105
  `connect` takes no arguments or connection options. Move with up/down, toggle
109
106
  services with Space, and press Enter. Choose a browser when more than one is
110
107
  installed. Only selected tabs open in one temporary browser context. Moodle,
111
- Artemis and Zulip all follow this flow; no separate Moodle login is needed.
108
+ Artemis and Zulip all follow this flow.
112
109
  Sign-in/MFA stays in the browser. `TERM=dumb` uses numbered prompts. Ctrl+C
113
- cancels and retains completed connections. Old service/campus arguments and
114
- `--service`/`--auth` flags are rejected before any browser opens.
110
+ cancels and retains completed connections.
115
111
 
116
112
  Detection covers Chrome, Edge, Brave, Chromium, Vivaldi, Opera and selected
117
113
  preview channels. Set `TUM_MCP_BROWSER` to an executable path for another
@@ -121,41 +117,30 @@ Only service-owned cookies are saved; identity-provider cookies are discarded
121
117
  when the temporary browser closes. Existing browser profiles are never imported.
122
118
 
123
119
  Zulip uses its read-only `/json/` routes with the browser session; no API key is
124
- required. Reconnect through the same selector when a session expires. Existing
125
- API-key records remain readable for compatibility, but key entry is not part of
126
- onboarding. See [Zulip's dispatcher](https://github.com/zulip/zulip/blob/12.0/zerver/lib/rest.py).
120
+ required. Reconnect through the same selector when a session expires.
121
+ See [Zulip's dispatcher](https://github.com/zulip/zulip/blob/12.0/zerver/lib/rest.py).
127
122
  Existing scopes survive reconnection; new identities start with empty scopes.
128
123
  Private Zulip channels require `--private-channel ID` as well as `--channel ID`.
129
124
  `--sensitive` enables own Artemis participation/feedback reads. It grants no
130
125
  website mutation or local execution permission.
131
126
 
132
127
  Settings live outside the installed package in `TUMMCP/config/preferences.json`.
133
- The default data directory is beside the legacy `TUMMoodle` directory on each OS.
134
- Existing Moodle data and its native-store identity are retained in place. New
135
- campus connections deliberately do not read or migrate native-store secrets;
136
- reconnect each service once to use local-file storage. The combined MCP process
137
- uses that storage for all `moodle_*` compatibility tools. The separate Moodle-only
138
- installer keeps its native-store default.
139
- Historical Git credentials remain local data for legacy cleanup; the current
140
- package provides no assignment Git workflow.
141
-
142
- New Moodle sessions live under `TUMMoodle/local-session/`; campus credentials live
143
- under `TUMMCP/credentials/local/`. Each directory has an independent `session.key`.
128
+ Moodle sessions live under `TUMMoodle/local-session/`; Artemis and Zulip credentials
129
+ live under `TUMMCP/credentials/local/`. Each directory has an independent `session.key`.
144
130
  Encrypted records and keys have owner-only permissions: 0700 directories / 0600
145
131
  keys on POSIX; protected current-user DACLs on Windows through PowerShell. No
146
- administrator privileges are requested. This avoids Keychain/Secret Service
132
+ administrator privileges are requested. This avoids native credential-store
147
133
  prompts but protects less than a native store. A copy of both ciphertext and key
148
134
  is sufficient to decrypt a session. Do not share, sync publicly, or package them.
149
135
 
150
136
  Missing or damaged keys fail closed. Restore the matching key from a private
151
137
  backup. To start fresh, stop clients and move the affected **local storage folder**
152
- to a private backup before reconnecting; this also resets its stored Git tokens.
153
- Never remove the legacy Moodle directory or downloads to repair a login.
154
- Native-store records remain available to the older installation for rollback.
138
+ to a private backup before reconnecting. Keep downloaded course materials when
139
+ repairing a login.
155
140
 
156
141
  Zulip message text is retrieved live and is not persisted. Retrieved excerpts may
157
- be transmitted to the coding agent's configured AI provider. Moodle's existing
158
- document cache retains its documented plaintext/expiry behavior.
142
+ be transmitted to the coding agent's configured AI provider. Moodle caches
143
+ documents and extracted text locally in plaintext.
159
144
 
160
145
  Course nicknames can be mapped in `mappings` using explicit per-service IDs.
161
146
  The tools resolve one exact mapping; ambiguity never grants broader access.
@@ -177,10 +162,11 @@ in JSON for troubleshooting. Default diagnostics do not create state folders.
177
162
 
178
163
  ## Study workflows
179
164
 
180
- `read-content` retrieves course information across the three websites. `sync-study`
165
+ `read-content` retrieves course information across the three websites. `sync-course`
181
166
  uses read-only local status to initialize an explicitly chosen available course or
182
167
  reconcile an existing project. Incomplete state is preserved for recovery.
183
- `prepare-study` creates or extends its local interactive site. See the
168
+ `prepare-study` authors lessons; `prepare-exercises` authors guided released sheets
169
+ in the same local app. See the
184
170
  [study guide](study.md) for examples, state structure and helper commands.
185
171
 
186
172
  One project holds one course. Source revisions, coverage and course knowledge
@@ -191,21 +177,19 @@ progress are preserved during package updates/removal.
191
177
  ## Client and skill setup
192
178
 
193
179
  `setup --client codex` prepares a local plugin/marketplace entry using the
194
- portable Agent Plugins schemas. Enable it in
195
- Codex Plugins, restart, and verify real tool and skill loading. Disable the legacy
196
- TUM Moodle plugin when switching so duplicate tools/skills are not active.
197
- The old installation remains available for rollback. Setup reports required UI
198
- steps and never equates writing files with activation.
180
+ portable Agent Plugins schemas. Enable it in Codex Plugins, restart, and verify
181
+ real tool and skill loading. Setup reports required UI steps and never equates
182
+ writing files with activation.
199
183
 
200
184
  The Codex adapter uses a contained Node launcher to pin the managed Python in
201
185
  isolated mode; portable stdio manifests require a bare or contained executable.
202
- The shipped pack contains five skills: `read-content`, `sync-study`,
203
- `prepare-study`, `audio-notes` and `publish-study`. Website-specific retrieval
186
+ The shipped pack contains six skills: `read-content`, `sync-course`,
187
+ `prepare-study`, `prepare-exercises`, `audio-notes` and `publish-course`. Website-specific retrieval
204
188
  guidance is loaded from references within read-content. Teaching, schema and design contracts live
205
189
  with prepare-study; the renderer and synthetic example ship as reusable assets.
206
190
  Audio Notes converts prepared content into category tracks and maintains coverage,
207
191
  scripts, voice settings, candidates and archives under the course's `audio/` root.
208
- Publish Study uses host GitHub CLI authentication to create a private repository
192
+ Publish Course uses host GitHub CLI authentication to create a private repository
209
193
  and a public Pages website after explicit publication consent. It chooses a course
210
194
  and semester name, updates the same site after successful study builds, and shows
211
195
  the hosted URL alongside the local launcher URL. No tokens or original course
@@ -220,7 +204,7 @@ routing quality is a separate evaluation from structural and discovery checks.
220
204
  Claude's invocation metadata follows each workflow's selected policy; automatic
221
205
  selection is enabled by default and never grants permission for a mutation.
222
206
 
223
- `setup --client cursor` writes `~/.cursor/mcp.json` and installs all five skills
207
+ `setup --client cursor` writes `~/.cursor/mcp.json` and installs all six skills
224
208
  under `~/.cursor/skills/`.
225
209
  `setup --client antigravity` installs the same skills under `~/.gemini/config/skills/`
226
210
  and writes `~/.gemini/config/mcp_config.json`; an existing
@@ -291,5 +275,5 @@ it is described as verified. Test data and public fixtures are synthetic. Real
291
275
  course material, local agent notes and screenshots stay under ignored local paths.
292
276
  Maintainer checks include a reviewed tarball inventory, integrity checks and an
293
277
  isolated consumer install. Never publish or stage npm packages as a test. Registry
294
- publication requires separate user authorization and remains paused for this work.
278
+ publication is an explicit maintainer action.
295
279
  `tum-mcp status` reports registry state as `not_checked`; it reads local configuration.
@@ -1,5 +1,5 @@
1
1
  {
2
- "wheel": "tum_mcp-0.6.0-py3-none-any.whl",
3
- "wheelSha256": "8437422504920753d1d6781cd87c57065cfbb8c0630ff0c5fd1de237b2d00426",
2
+ "wheel": "tum_mcp-0.7.0-py3-none-any.whl",
3
+ "wheelSha256": "dabe306ea1695fa03fa119b2673cc93c0ee705b34ebabc6674958fe77f5757a6",
4
4
  "requirementsSha256": "25f9efa7a4ffb0b97abcfeb9d4706d04808273ab1b35abcfaa1ef0b66e87591b"
5
5
  }
package/npm/lock.mjs ADDED
@@ -0,0 +1,43 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { mkdir, rename, rm, rmdir, writeFile } from 'node:fs/promises';
4
+ import { hostname } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ export async function acquireLock(root, name = 'setup') {
8
+ await mkdir(root, { recursive: true, mode: 0o700 });
9
+ const lock = join(root, `${name}.lock`);
10
+ const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
11
+ const guard = join(root, `${name}.claim`);
12
+ try { await mkdir(guard, { mode: 0o700 }); }
13
+ catch { throw new Error(`Another ${name} is claiming the lock. Retry; an interrupted ${name}.claim needs local review.`); }
14
+ try {
15
+ try { await mkdir(lock, { mode: 0o700 }); }
16
+ catch (error) {
17
+ if (error.code !== 'EEXIST') throw error;
18
+ let previous;
19
+ try { previous = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')); }
20
+ catch { throw new Error(`${name} lock has no readable owner. Confirm no operation is running before removing ${name}.lock.`); }
21
+ if (!Number.isSafeInteger(previous.pid) || previous.pid <= 0 || previous.host !== hostname()) {
22
+ throw new Error(`${name} lock belongs to an unknown owner; preserve it for local review.`);
23
+ }
24
+ let alive = true;
25
+ try { process.kill(previous.pid, 0); }
26
+ catch (failure) { if (failure.code === 'ESRCH') alive = false; }
27
+ if (alive) throw new Error(`Another ${name} is running; retry when it finishes.`);
28
+ const abandoned = `${lock}.${randomUUID()}.abandoned`;
29
+ await rename(lock, abandoned);
30
+ await rm(abandoned, { recursive: true, force: true });
31
+ try { await mkdir(lock, { mode: 0o700 }); }
32
+ catch { throw new Error(`Another ${name} acquired the lock; retry when it finishes.`); }
33
+ }
34
+ const temporary = join(lock, `${owner.nonce}.pending`);
35
+ await writeFile(temporary, JSON.stringify(owner), { mode: 0o600, flag: 'wx' });
36
+ await rename(temporary, join(lock, 'owner.json'));
37
+ } finally { await rmdir(guard); }
38
+ return async () => {
39
+ const current = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8'));
40
+ if (current.nonce !== owner.nonce) throw new Error(`${name} lock ownership changed; it was preserved.`);
41
+ await rm(lock, { recursive: true, force: true });
42
+ };
43
+ }
@@ -0,0 +1,96 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { delimiter, dirname, join, basename, resolve, relative, isAbsolute, sep } from 'node:path';
5
+
6
+ export const packageName = '@jamestesting888/tum-mcp';
7
+
8
+ function canonical(value) {
9
+ let path = resolve(value.replace(/^~(?=$|[/\\])/, homedir()));
10
+ const tail = [];
11
+ while (!existsSync(path)) { tail.unshift(basename(path)); path = dirname(path); }
12
+ return join(realpathSync(path), ...tail);
13
+ }
14
+
15
+ export function packageStorage(root, dataDirectory) {
16
+ const home = homedir();
17
+ const base = process.platform === 'darwin' ? join(home, 'Library/Application Support')
18
+ : process.platform === 'win32' ? process.env.LOCALAPPDATA || join(home, 'AppData/Local')
19
+ : process.env.XDG_DATA_HOME || join(home, '.local/share');
20
+ const storage = {
21
+ TUM_MCP_DATA_DIR: canonical(dataDirectory),
22
+ TUM_MCP_CONFIG_DIR: canonical(process.env.TUM_MCP_CONFIG_DIR || join(dataDirectory, 'config')),
23
+ TUM_MOODLE_DATA_DIR: canonical(process.env.TUM_MOODLE_DATA_DIR || join(base, 'TUMMoodle')),
24
+ TUM_MOODLE_DOWNLOAD_DIR: canonical(process.env.TUM_MOODLE_DOWNLOAD_DIR || join(home, 'Downloads/TUM Moodle')),
25
+ };
26
+ const config = storage.TUM_MCP_CONFIG_DIR;
27
+ const settings = join(config, 'preferences.json');
28
+ let workspace = join(home, 'University');
29
+ if (existsSync(settings)) {
30
+ try {
31
+ const prefs = JSON.parse(readFileSync(settings, 'utf8'));
32
+ if (prefs.workspace_root !== undefined) {
33
+ if (typeof prefs.workspace_root !== 'string') throw new Error('Invalid storage path');
34
+ workspace = prefs.workspace_root;
35
+ }
36
+ } catch { throw new Error('Storage settings could not be verified; package files and saved data were preserved.'); }
37
+ }
38
+ for (const path of [...Object.values(storage), workspace]) {
39
+ const inside = relative(root, canonical(path));
40
+ if (inside === '' || (inside.split(sep)[0] !== '..' && !isAbsolute(inside))) {
41
+ throw new Error('Package installation overlaps saved data or course work. Move that data outside the package before update/removal.');
42
+ }
43
+ }
44
+ return storage;
45
+ }
46
+
47
+ export function npmCli() {
48
+ const paths = (process.env.PATH || '').split(delimiter);
49
+ const candidates = [process.env.npm_execpath,
50
+ join(dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js'),
51
+ join(dirname(dirname(process.execPath)), 'lib/node_modules/npm/bin/npm-cli.js'),
52
+ ...paths.flatMap(path => [join(path, 'npm'), join(path, 'node_modules/npm/bin/npm-cli.js')])];
53
+ for (const path of candidates) {
54
+ if (!path || !existsSync(path)) continue;
55
+ const resolved = realpathSync(path);
56
+ if (basename(resolved) !== 'npm-cli.js') continue;
57
+ const manifest = join(dirname(dirname(resolved)), 'package.json');
58
+ if (existsSync(manifest) && JSON.parse(readFileSync(manifest, 'utf8')).name === 'npm') return resolved;
59
+ }
60
+ throw new Error('npm could not be located beside Node or on PATH. Install Node.js with npm, then retry.');
61
+ }
62
+
63
+ export function runNpm(cli, args, capture = false) {
64
+ const result = spawnSync(process.execPath, [cli, ...args, '--ignore-scripts', '--no-audit', '--no-fund', '--no-update-notifier'], {
65
+ shell: false, stdio: capture ? ['ignore', 'pipe', 'inherit'] : 'inherit', encoding: 'utf8', timeout: 600000,
66
+ });
67
+ if (result.error || result.status !== 0) throw new Error('npm operation failed. Saved data and retained client runtimes were preserved; resolve the npm error and retry.');
68
+ return capture ? result.stdout.trim() : '';
69
+ }
70
+
71
+ export function installationTarget(root) {
72
+ const modules = dirname(dirname(root));
73
+ if (basename(modules) !== 'node_modules' || modules.split(/[\\/]/).some(part => ['.pnpm', '_npx'].includes(part))) {
74
+ throw new Error('Package management requires an npm installation. Use update --local for a source/pinned build.');
75
+ }
76
+ if (`${basename(dirname(root))}/${basename(root)}` !== packageName) {
77
+ throw new Error('Automatic package management requires the original package name; aliases were preserved.');
78
+ }
79
+ const parent = dirname(modules);
80
+ const prefix = process.platform === 'win32' ? parent : dirname(parent);
81
+ const shim = process.platform === 'win32' ? join(prefix, 'tum-mcp.cmd') : join(prefix, 'bin/tum-mcp');
82
+ if (existsSync(shim)) {
83
+ const owned = process.platform === 'win32'
84
+ ? readFileSync(shim, 'utf8').replaceAll('\\', '/').includes('node_modules/@jamestesting888/tum-mcp/npm/tum-mcp.mjs')
85
+ : realpathSync(shim) === join(root, 'npm/tum-mcp.mjs');
86
+ if (owned && (process.platform === 'win32' || basename(parent) === 'lib')) return { prefix, global: true };
87
+ }
88
+ const manifest = join(parent, 'package.json');
89
+ if (existsSync(manifest)) {
90
+ const project = JSON.parse(readFileSync(manifest, 'utf8'));
91
+ if (['dependencies', 'devDependencies', 'optionalDependencies'].some(key => project[key]?.[packageName])) {
92
+ return { prefix: parent, global: false };
93
+ }
94
+ }
95
+ throw new Error('The npm installation prefix could not be verified; package files were preserved.');
96
+ }
@@ -0,0 +1,109 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { copyFile, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { basename, dirname, isAbsolute, join, relative, sep } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { acquireLock } from './lock.mjs';
8
+ import { installationTarget, npmCli, packageName, packageStorage, runNpm } from './npm-command.mjs';
9
+
10
+ function runLauncher(root, args, quiet = false) {
11
+ const result = spawnSync(process.execPath, [join(root, 'npm/tum-mcp.mjs'), ...args], {
12
+ stdio: quiet ? ['ignore', 'pipe', 'pipe'] : 'inherit', encoding: 'utf8', shell: false, timeout: 600000,
13
+ });
14
+ if (result.error || result.status !== 0) {
15
+ if (quiet) process.stderr.write((result.stdout || '') + (result.stderr || ''));
16
+ throw new Error(`${args[0]} did not complete. Saved data and customized client files were preserved; review the details and retry.`);
17
+ }
18
+ }
19
+
20
+ function versionParts(version) {
21
+ const match = typeof version === 'string' && version.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/);
22
+ if (!match) throw new Error('npm returned an invalid package version.');
23
+ const prerelease = match[4]?.split('.') || [];
24
+ if (prerelease.some(part => /^0\d+$/.test(part))) throw new Error('npm returned an invalid prerelease version.');
25
+ return { core: match.slice(1, 4).map(BigInt), prerelease };
26
+ }
27
+
28
+ export function newerVersion(latest, current) {
29
+ const left = versionParts(latest), right = versionParts(current);
30
+ for (let i = 0; i < left.core.length; i++) if (left.core[i] !== right.core[i]) return left.core[i] > right.core[i];
31
+ const a = left.prerelease, b = right.prerelease;
32
+ if (!a.length || !b.length) return !a.length && Boolean(b.length);
33
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
34
+ if (a[i] === undefined) return false;
35
+ if (b[i] === undefined) return true;
36
+ if (a[i] === b[i]) continue;
37
+ const numericA = /^\d+$/.test(a[i]), numericB = /^\d+$/.test(b[i]);
38
+ if (numericA && numericB) return BigInt(a[i]) > BigInt(b[i]);
39
+ if (numericA !== numericB) return numericB;
40
+ return a[i] > b[i];
41
+ }
42
+ return false;
43
+ }
44
+
45
+ async function operate(job) {
46
+ const release = await acquireLock(job.target.prefix, 'tum-mcp-package');
47
+ try {
48
+ const scope = ['--prefix', job.target.prefix, `--global=${job.target.global}`];
49
+ if (job.action === 'uninstall') {
50
+ const configured = existsSync(join(job.dataDirectory, 'runtime.json')) ||
51
+ existsSync(join(job.configDirectory, 'clients'));
52
+ if (configured || job.args.some(arg => ['--purge-runtime', '--forget-credentials'].includes(arg))) {
53
+ runLauncher(job.root, ['uninstall', '--keep-package', ...job.args]);
54
+ }
55
+ runNpm(job.npm, ['uninstall', ...scope, packageName]);
56
+ console.log('TUM MCP package and managed client setup removed. Saved user data was retained unless explicitly requested.');
57
+ return;
58
+ }
59
+ const current = JSON.parse(readFileSync(join(job.root, 'package.json'), 'utf8')).version;
60
+ const latest = JSON.parse(runNpm(job.npm, ['view', `${packageName}@latest`, 'version', '--json', ...scope], true));
61
+ if (!newerVersion(latest, current)) {
62
+ console.log(`Installed ${current}; no newer npm release. Refreshing configured clients.`);
63
+ runLauncher(job.root, ['update', '--local']);
64
+ return;
65
+ }
66
+ console.log(`Preparing TUM MCP ${latest}.`);
67
+ const packed = JSON.parse(runNpm(job.npm, ['pack', `${packageName}@${latest}`, '--pack-destination', job.temporary, '--json', ...scope], true));
68
+ if (!Array.isArray(packed) || packed.length !== 1 || packed[0].name !== packageName || packed[0].version !== latest || basename(packed[0].filename) !== packed[0].filename) {
69
+ throw new Error('npm package metadata did not match the selected release.');
70
+ }
71
+ const archive = join(job.temporary, packed[0].filename);
72
+ const staging = join(job.temporary, 'prepared');
73
+ runNpm(job.npm, ['install', '--prefix', staging, '--global=false', archive], true);
74
+ const prepared = join(staging, 'node_modules', packageName);
75
+ runLauncher(prepared, ['setup'], true);
76
+ // Only a healthy prepared release may replace the user's npm installation.
77
+ runNpm(job.npm, ['install', ...scope, job.target.global ? archive : `${packageName}@${latest}`]);
78
+ if (JSON.parse(readFileSync(join(job.root, 'package.json'), 'utf8')).version !== latest) throw new Error('npm did not install the selected version; client activation was stopped.');
79
+ runLauncher(job.root, ['update', '--local']);
80
+ console.log(`Updated TUM MCP to ${latest}. Restart clients to load refreshed skills.`);
81
+ } finally { await release(); }
82
+ }
83
+
84
+ export async function managePackage(action, args, dataDirectory) {
85
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
86
+ const inside = relative(root, process.cwd());
87
+ if (inside === '' || (inside.split(sep)[0] !== '..' && !isAbsolute(inside))) throw new Error('Run package update/removal from outside the installed package directory.');
88
+ const target = installationTarget(root);
89
+ const storage = packageStorage(root, dataDirectory);
90
+ const configDirectory = storage.TUM_MCP_CONFIG_DIR;
91
+ const npm = npmCli();
92
+ const temporary = await mkdtemp(join(tmpdir(), 'tum-mcp-package-'));
93
+ try {
94
+ for (const name of ['package-manager.mjs', 'npm-command.mjs', 'lock.mjs']) await copyFile(join(root, 'npm', name), join(temporary, name));
95
+ const job = { action, args, root, dataDirectory, configDirectory, target, npm, temporary };
96
+ const path = join(temporary, 'job.json');
97
+ await writeFile(path, JSON.stringify(job), { mode: 0o600, flag: 'wx' });
98
+ // This worker and its cwd survive Windows replacing/removing the package files.
99
+ const result = spawnSync(process.execPath, [join(temporary, 'package-manager.mjs'), '--job', path], {
100
+ cwd: temporary, stdio: 'inherit', shell: false, env: { ...process.env, ...storage },
101
+ });
102
+ return result.status ?? (result.signal === 'SIGINT' ? 130 : 2);
103
+ } finally { await rm(temporary, { recursive: true, force: true }); }
104
+ }
105
+
106
+ if (process.argv[1] && process.argv[2] === '--job' && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
107
+ try { await operate(JSON.parse(readFileSync(process.argv[3], 'utf8'))); }
108
+ catch (error) { console.error(error.message); process.exitCode = 2; }
109
+ }
package/npm/runtime.mjs CHANGED
@@ -2,10 +2,11 @@ import { spawnSync } from 'node:child_process';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
4
4
  import { chmod, mkdir, mkdtemp, rename, rm, rmdir, writeFile } from 'node:fs/promises';
5
- import { homedir, hostname } from 'node:os';
5
+ import { homedir } from 'node:os';
6
6
  import { basename, dirname, join, resolve } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { download } from './download.mjs';
9
+ import { acquireLock } from './lock.mjs';
9
10
 
10
11
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
11
12
  const pins = JSON.parse(readFileSync(join(packageRoot, 'npm/uv-artifacts.json'), 'utf8'));
@@ -109,40 +110,7 @@ async function uvExecutable() {
109
110
  return cached;
110
111
  }
111
112
 
112
- export async function acquireSetupLock() {
113
- const lock = join(dataRoot(), 'setup.lock');
114
- const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
115
- const guard = join(dataRoot(), 'setup.claim');
116
- try { await mkdir(guard, { mode: 0o700 }); }
117
- catch { throw new Error('Another setup is claiming the lock. Retry; an interrupted setup.claim needs local review.'); }
118
- try {
119
- try { await mkdir(lock, { mode: 0o700 }); }
120
- catch (error) {
121
- if (error.code !== 'EEXIST') throw error;
122
- let previous;
123
- try { previous = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')); }
124
- catch { throw new Error('Setup lock has no readable owner. Confirm no setup is running before removing setup.lock.'); }
125
- if (!Number.isSafeInteger(previous.pid) || previous.pid <= 0 || previous.host !== hostname()) {
126
- throw new Error('Setup lock belongs to an unknown owner; preserve it for local review.');
127
- }
128
- let alive = true;
129
- try { process.kill(previous.pid, 0); }
130
- catch (failure) { if (failure.code === 'ESRCH') alive = false; }
131
- if (alive) throw new Error('Another setup is running; retry when it finishes.');
132
- const abandoned = `${lock}.${randomUUID()}.abandoned`;
133
- await rename(lock, abandoned);
134
- await rm(abandoned, { recursive: true, force: true });
135
- try { await mkdir(lock, { mode: 0o700 }); }
136
- catch { throw new Error('Another setup acquired the lock; retry when it finishes.'); }
137
- }
138
- await atomicWrite(join(lock, 'owner.json'), JSON.stringify(owner));
139
- } finally { await rmdir(guard); }
140
- return async () => {
141
- const current = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8'));
142
- if (current.nonce !== owner.nonce) throw new Error('Setup lock ownership changed; it was preserved.');
143
- await rm(lock, { recursive: true, force: true });
144
- };
145
- }
113
+ export function acquireSetupLock() { return acquireLock(dataRoot()); }
146
114
 
147
115
  export async function ensureRuntime() {
148
116
  const backend = join(packageRoot, 'npm/backend');
package/npm/tum-mcp.mjs CHANGED
@@ -1,33 +1,58 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from 'node:child_process';
3
3
  import { readFileSync } from 'node:fs';
4
- import { acquireSetupLock, currentPython, ensureRuntime, removeRuntimeFiles } from './runtime.mjs';
4
+ import { parseArgs } from 'node:util';
5
+ import { acquireSetupLock, currentPython, dataRoot, ensureRuntime, removeRuntimeFiles } from './runtime.mjs';
6
+ import { managePackage } from './package-manager.mjs';
5
7
 
6
- const args = process.argv.slice(2);
8
+ let args = process.argv.slice(2);
7
9
  const version = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
8
10
  try {
9
11
  if (args.length === 0 || (args.length === 1 && ['--help', '-h'].includes(args[0]))) {
10
12
  console.log(`TUM MCP ${version}\nCommands: setup [--client codex|claude|cursor|antigravity|generic], connect, scope, discover, status, doctor [--json] [--live], serve, skills, study, logout, uninstall, update\nRun connect without arguments to select services and an installed Chromium browser. Run setup explicitly to provision the managed Python backend.`);
11
13
  } else if (['--version', 'version'].includes(args[0])) {
12
14
  console.log(version);
13
- } else if (['setup', 'update'].includes(args[0]) && args.length === 2 && ['--help', '-h'].includes(args[1])) {
14
- console.log(args[0] === 'update'
15
- ? 'Usage: tum-mcp update\nRefresh previously configured clients for the installed npm version.\nDownload a newer version first: npm install --global @jamestesting888/tum-mcp@latest'
16
- : 'Usage: tum-mcp setup [--client codex|claude|cursor|antigravity|generic] [--host local|windows]\nProvision the managed runtime and register explicitly selected clients.');
15
+ } else if (['setup', 'update', 'uninstall'].includes(args[0]) && args.some(arg => ['--help', '-h'].includes(arg))) {
16
+ const help = {
17
+ update: 'Usage: tum-mcp update [--local]\nInstall the latest npm release and refresh configured clients.\n--local refreshes this installed version without contacting npm.',
18
+ setup: 'Usage: tum-mcp setup [--client codex|claude|cursor|antigravity|generic] [--host local|windows]\nProvision the managed runtime and register explicitly selected clients.',
19
+ uninstall: 'Usage: tum-mcp uninstall [--all] [--purge-runtime] [--forget-credentials]\nRemove managed client setup and this npm installation. Saved data is retained by default.\n--client NAME removes one client only. --keep-package keeps npm installed.',
20
+ };
21
+ console.log(help[args[0]]);
17
22
  } else {
18
- if (args[0] === 'update' && args.length !== 1) throw new Error('Usage: tum-mcp update (no arguments). Use --help for the update workflow.');
19
- const setup = ['setup', 'update'].includes(args[0]);
20
- const python = setup ? await ensureRuntime() : currentPython();
21
- if (!python) throw new Error('SETUP_REQUIRED: Run tum-mcp setup first.');
22
- const forwarded = args;
23
- const env = { ...process.env, TUM_MCP_NPM_LAUNCHER: '1' }; delete env.PYTHONPATH; delete env.PYTHONHOME;
24
- const purge = args[0] === 'uninstall' && args.includes('--purge-runtime');
25
- const release = purge ? await acquireSetupLock() : null;
26
- try {
27
- const child = spawnSync(python, ['-I', '-m', 'tum_mcp', ...forwarded], { env, stdio: 'inherit', shell: false });
28
- if (child.error) throw new Error('Managed backend could not start; run setup again.');
29
- process.exitCode = child.status ?? (child.signal === 'SIGINT' ? 130 : 1);
30
- if (purge && child.status === 0) await removeRuntimeFiles();
31
- } finally { if (release) await release(); }
23
+ let packageAction = args[0] === 'update';
24
+ if (args[0] === 'update') {
25
+ if (args.length > 2 || (args.length === 2 && args[1] !== '--local')) throw new Error('Usage: tum-mcp update [--local]. Use --help for details.');
26
+ if (args[1] === '--local') { packageAction = false; args = ['update']; }
27
+ }
28
+ if (args[0] === 'uninstall') {
29
+ const { values } = parseArgs({ args: args.slice(1), options: {
30
+ all: { type: 'boolean' }, client: { type: 'string' }, host: { type: 'string' },
31
+ 'keep-package': { type: 'boolean' }, 'purge-runtime': { type: 'boolean' }, 'forget-credentials': { type: 'boolean' },
32
+ } });
33
+ if (values.host && !['local', 'windows'].includes(values.host)) throw new Error('Choose --host local or windows.');
34
+ if (values.client !== undefined && !['codex', 'claude', 'cursor', 'antigravity', 'generic'].includes(values.client)) throw new Error('Choose a supported --client name.');
35
+ if (values.client && (values.all || values['purge-runtime'] || values['forget-credentials'])) throw new Error('Client-only removal cannot use --all or data/runtime cleanup options.');
36
+ if (!values.client && !values.all) args.push('--all');
37
+ packageAction = !values.client && !values['keep-package'];
38
+ args = args.filter(arg => arg !== '--keep-package');
39
+ }
40
+ if (packageAction) {
41
+ process.exitCode = await managePackage(args[0], args.slice(1), dataRoot());
42
+ } else {
43
+ let python = ['setup', 'update'].includes(args[0]) ? await ensureRuntime() : currentPython();
44
+ if (args[0] === 'uninstall' && !python) python = await ensureRuntime();
45
+ if (!python) throw new Error('SETUP_REQUIRED: Run tum-mcp setup first.');
46
+ const forwarded = args;
47
+ const env = { ...process.env, TUM_MCP_NPM_LAUNCHER: '1' }; delete env.PYTHONPATH; delete env.PYTHONHOME;
48
+ const purge = args[0] === 'uninstall' && args.includes('--purge-runtime');
49
+ const release = purge ? await acquireSetupLock() : null;
50
+ try {
51
+ const child = spawnSync(python, ['-I', '-m', 'tum_mcp', ...forwarded], { env, stdio: 'inherit', shell: false });
52
+ if (child.error) throw new Error('Managed backend could not start; run setup again.');
53
+ process.exitCode = child.status ?? (child.signal === 'SIGINT' ? 130 : 1);
54
+ if (purge && child.status === 0) await removeRuntimeFiles();
55
+ } finally { if (release) await release(); }
56
+ }
32
57
  }
33
58
  } catch (error) { console.error(error.message); process.exitCode = 2; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamestesting888/tum-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Read-only TUM Moodle, Artemis and Zulip integration with source-backed study workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,9 @@
14
14
  "npm/tum-mcp.mjs",
15
15
  "npm/runtime.mjs",
16
16
  "npm/download.mjs",
17
+ "npm/lock.mjs",
18
+ "npm/npm-command.mjs",
19
+ "npm/package-manager.mjs",
17
20
  "npm/uv-artifacts.json",
18
21
  "npm/backend/*.whl",
19
22
  "npm/backend/requirements.txt",
@@ -31,7 +34,8 @@
31
34
  "prepack": "node npm/build.mjs",
32
35
  "test": "node --test npm/test/*.test.mjs",
33
36
  "test:consumer": "node npm/smoke-consumer.mjs",
34
- "test:upgrade": "node npm/upgrade-consumer.mjs"
37
+ "test:upgrade": "node npm/upgrade-consumer.mjs",
38
+ "test:package-lifecycle": "node npm/package-consumer.mjs"
35
39
  },
36
40
  "publishConfig": {
37
41
  "access": "public"