| name | moodle |
| description | Manage Moodle courses via CLI — create/update/delete activities, sections, quizzes, import GIFT questions, and AI-assisted essay grading. Use /moodle-skill:moodle to interact with any Moodle instance. |
| argument-hint | [command] [args...] |
| disable-model-invocation | true |
Moodle Skill — Generic Moodle Course Management
Manage any Moodle course via the moodle-updater.js CLI. All destructive operations require a dry-run first, then explicit user confirmation before --live.
When This Skill Activates
- Explicit:
/moodle-skill:moodle, /moodle-skill:moodle structure
Prerequisites
.env in your project root with MOODLE_URL, MOODLE_SESSION, and COURSE_ID
- Dependencies: Auto-installed on first session via hooks (jsdom, marked, marked-highlight, highlight.js, turndown, playwright)
- Playwright browser: Installed automatically on first
login command
Workflow
Phase 1: Parse Command
- Parse
$ARGUMENTS — if arguments provided, use as command + args and proceed to Phase 2.
- If
$ARGUMENTS is empty, present the Command Reference Table below and ask which command to run.
Phase 2: Dry-Run
login command: Execute directly — opens a browser for authentication. The browser auto-closes once login is detected. Supports --browser msedge or --browser chrome for SSO.
Read-only commands (structure, list-activities, show-label, show-page, dump-form, dump-grading): Execute directly.
NODE_PATH="${CLAUDE_PLUGIN_DATA}/node_modules" node "${CLAUDE_PLUGIN_ROOT}/scripts/moodle-updater.js" <command> <args...>
All other commands: Execute WITHOUT --live first (dry-run is the default).
Phase 3: Live Execution
After showing the dry-run output, ask the user for confirmation before running with --live.
NODE_PATH="${CLAUDE_PLUGIN_DATA}/node_modules" node "${CLAUDE_PLUGIN_ROOT}/scripts/moodle-updater.js" <command> <args...> --live
Command Reference
| Group | Command | Arguments | Description |
|---|
| Setup | login | [--browser msedge|chrome] | Open browser, login, save cookie to .env (auto-closes) |
| Read | structure | — | Show course structure |
| Read | list-activities | <sectionId> | List activities in a section |
| Read | show-label | <moduleId> | Show label HTML |
| Read | show-page | <moduleId> | Show page HTML |
| Update | update-label | <moduleId> <htmlFile> | Replace label content |
| Update | update-page | <moduleId> <htmlFile> | Replace page content |
| Update | update-summary | <sectionId> <htmlFile> | Replace section summary |
| CRUD | create-url | <sectionNum> <name> <url> | Create URL activity |
| CRUD | create-page | <sectionNum> <name> <htmlOrMdFile> | Create text page (.md auto-converts) |
| CRUD | create-label | <sectionNum> <htmlOrMdFile> | Create label (no name field; .md auto-converts) |
| CRUD | create-resource | <sectionNum> <name> <file> | Upload file resource |
| CRUD | create-assign | <sectionNum> <name> <htmlOrMdFile> [--open ts] [--due ts] | Create assignment |
| CRUD | create-forum | <sectionNum> <name> <htmlOrMdFile> | Create forum |
| CRUD | create-quiz | <sectionNum> <configJson> | Create quiz (SEB, time limit) |
| Manage | delete-activity | <moduleId> | Delete activity |
| Manage | hide-activity | <moduleId> | Hide activity |
| Manage | indent-activity | <moduleId> | Indent activity |
| Manage | move-activity | <moduleId> <afterModuleId> | Move activity |
| Sections | add-section | — | Append a new empty section at the end |
| Sections | delete-section | <sectionId> | Delete section |
| Sections | duplicate-section | — | Duplicate last section |
| Sections | move-section | <sectionId> <targetSectionId> | Move section before target |
| Sections | rename-section | <sectionId> <title> | Rename section |
| Quiz | import-gift | <cmid> <giftFile> | Import GIFT questions |
| Quiz | add-questions-to-quiz | <cmid> [categoryName] | Assign questions to quiz |
| Quiz | delete-quiz-questions | <cmid> [categoryName] | Delete questions in category |
| Grading | grade-essay scrape | <cmid> --gift <path> [--slot N] --output <file> | Export student responses as JSON |
| Grading | grade-essay submit | <cmid> --grades <file> [--live] | Submit grades to Moodle |
| Assignment | assign-students | <cmid> [--out <path>] | List participants + submission status |
| Assignment | assign-subs | <cmid> [--all] [--user <id>] | Download submissions (online text + files) |
| Assignment | assign-grades | <cmid> [--user <id>] | Read current grade per participant |
| Assignment | assign-submit | <cmid> --from <grades.json> [--user <id>] [--live] | Write points + comments back to Moodle |
| Gradebook | gradebook | — | Show gradebook tree with item IDs + weights |
| Gradebook | gradebook-create-category | <name> [--aggregation N] [--weight N] [--live] | Create a grade category |
| Gradebook | gradebook-set-weights | <id:weight ...> [--live] | Set weight overrides (IDs from gradebook) |
| Gradebook | gradebook-move-to-category | <cmid> <categoryId> [--live] | Move an activity into a category |
| Diagnostic | dump-form | <type> <sectionNum> | Inspect form fields |
| Diagnostic | dump-grading | <cmid> [--slot N] | Inspect grading page |
grade-essay Workflow
The AI grading workflow is split into scrape → grade → submit:
Step 1: Scrape student responses
NODE_PATH="${CLAUDE_PLUGIN_DATA}/node_modules" node "${CLAUDE_PLUGIN_ROOT}/scripts/moodle-updater.js" grade-essay scrape <cmid> --gift <path> --output grading.json
This produces a JSON file with questions (criteria, model answers) and student responses.
Step 2: Grade (done by Claude in this conversation)
Read the JSON file, grade each student response against the criteria, and produce a grades JSON file.
Step 3: Submit grades
NODE_PATH="${CLAUDE_PLUGIN_DATA}/node_modules" node "${CLAUDE_PLUGIN_ROOT}/scripts/moodle-updater.js" grade-essay submit <cmid> --grades grades-result.json --live
Course Round-Trip Workflow (download → edit → re-upload)
In addition to the moodle-updater.js CLI, the plugin ships standalone scripts to mirror a whole course locally, edit it, and append it to another course.
1. Download a course
Reads MOODLE_URL / COURSE_ID / MOODLE_SESSION from .env and writes a round-trip-friendly tree to <outDir> (default ./kurs).
NODE_PATH="${CLAUDE_PLUGIN_DATA}/node_modules" node "${CLAUDE_PLUGIN_ROOT}/scripts/download-course.js" ./kurs
Layout produced:
kurs/
course.json, state.json, README.md
<NN>-<section-slug>/
section.json, summary.md
<MM>-<type>-<slug>/
activity.json
content.md # page/label/assign/forum/quiz/attendance bodies
files/<originalname> # for resource modules
2. Plan / preview the upload
Walks the tree and prints the equivalent moodle-updater.js commands. No HTTP calls. Uses the target MOODLE_URL / COURSE_ID from .env.
node "${CLAUDE_PLUGIN_ROOT}/scripts/upload-course.js" ./kurs
3. Execute the upload (append-only)
Appends each source section as a NEW section at the end of the target course — existing sections are never touched. Per source section it runs duplicate-section → rename-section → update-summary → create-<type> for each activity.
node "${CLAUDE_PLUGIN_ROOT}/scripts/execute-upload.js" ./kurs
node "${CLAUDE_PLUGIN_ROOT}/scripts/execute-upload.js" ./kurs --section 01 --live
node "${CLAUDE_PLUGIN_ROOT}/scripts/execute-upload.js" ./kurs --live
--live is required to actually write to Moodle. Without it the script only prints the plan.
Session Handling
- Primary method:
login opens a browser, user logs in, browser auto-closes once login is detected, and cookie is saved to .env. Use --browser msedge or --browser chrome for SSO.
- The
MoodleSession cookie expires after inactivity. If a command fails with a session error, run login again.
- Use
structure as a quick connection test.
Rules
- Never run
--live without a dry-run first (except read-only commands).
- Never run
--live without explicit user confirmation.
- Do NOT invoke this skill autonomously — only on explicit
/moodle-skill:moodle.
- If a command fails with a session error, tell the user to run
login again.