From courseforge
Build, place, and update content on Canvas LMS — styled, accessible, idempotent. Three jobs: (1) migrate a Notion course (a hub with a Page Index + weekly schedule) into a fully built Canvas course; (2) generate content on request — a page, syllabus, assignment, graded discussion, quiz or final exam, study guide — and expertly place it into the right Canvas course, module, item type, position, points, and assignment group; (3) keep every Canvas HTML output on the project style guide. Use whenever the user wants to move, port, migrate, copy, rebuild, or "get my Notion course/lessons/projects/assignments/syllabus into Canvas", populate an empty Canvas shell, bulk-create pages and modules, add or grade assignments and quizzes, build a final exam or study guide, or "put this on Canvas" — even if they never say "Canvas API" or name this skill. Also covers standalone sub-tasks: trimming a course's left-hand nav to a keep-list, and bulk-converting many pages with a parallel workflow. Before any push, ask whether the work should be published or left unpublished. This skill is content-first and does not read student data in normal use; it refuses ad-hoc roster/grade/submission access. It DOES include one OPT-IN blind-grading flow: a sterilizing + pseudonymizing gateway pulls submission TEXT only (identities stay local), you grade by pseudonym, and a dry-run-first poster writes the grades back. That de-identification is best-effort, not a guarantee. For full admin grading with real identities, use the courseforge-admin skill. Also handles FIRST-TIME CONNECTION: when Canvas is not set up yet (no token/config) and the user asks to set up or connect Canvas, to "see", "list", or "show" their courses, shells, or sections, or wants any Canvas action at all, proactively run the Setup-Canvas onboarding (it collects the course URL and a privately-typed token) instead of replying that you have no access. Built and battle-tested on the MGCCC Canvas instance; the conventions reuse cleanly for any Canvas school.
How this skill is triggered — by the user, by Claude, or both
Slash command
/courseforge:courseforgeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill does three things, and most tasks combine them:
references/academic-calendar.mdreferences/blind-grading.mdreferences/conversion-spec.mdreferences/project-course.mdreferences/style-guide.mdreferences/workflow-pattern.mdscripts/Build-GradingBundle.ps1scripts/Compute-DueDates.ps1scripts/Extract-CanvasToken.ps1scripts/Get-TermCalendar.ps1scripts/Post-Grades.ps1scripts/Push-CanvasPages.ps1scripts/Push-CanvasProject.ps1scripts/Set-DueDates.ps1scripts/Setup-Canvas.ps1scripts/Trim-CanvasNav.ps1scripts/Verify-Slots.ps1This skill does three things, and most tasks combine them:
references/style-guide.md — no
exceptions. That is what makes pages survive the sanitizer and look consistent.Re-runs are idempotent — safe to run again to update in place.
On a fresh install the instructor has the skill but has not connected their Canvas yet — there is no token and no config. Any read or build will fail with "no access." Do not just tell the user you can't see Canvas. Offer to connect it now, then run setup. This is the #1 onboarding moment for a non-technical instructor — own it.
Detect it: check the working directory for BOTH a canvas.token file AND a
canvas.config.<id>.json. If you are unsure which folder is the project, ask the user
or use the current directory. If either file is missing, they are not connected —
proactively run onboarding instead of declining:
Preferred — token stays private (never enters the chat). Launch the interactive setup in its own console window so the token is typed hidden, like a password box:
Start-Process powershell -ArgumentList @(
'-NoExit','-ExecutionPolicy','Bypass',
'-File', "$HOME\.claude\skills\courseforge\scripts\Setup-Canvas.ps1",
'-WorkingDir', '<the project folder>'
)
Then tell the user: "A small setup window just opened — paste your Canvas course web
address and your access token there (the token stays hidden). It will print your course
name when it connects." When they're done, verify by reading the course back
(GET /courses/:id) and report the course name.
Fallback — simplest, but the token appears in the chat transcript. If the user can't use the window (or prefers), ask for the course URL and token in chat, then run non-interactively:
scripts\Setup-Canvas.ps1 -CourseUrl <url> -Token <token>
Either path writes canvas.token, canvas.config.<id>.json, and a protective
.gitignore, and tests the connection. Never echo the token back. Once connected,
continue with the user's original request (e.g. listing their courses).
Before you push anything to Canvas, ask the user whether the work should be
published (live the moment the course is published) or left unpublished (hidden
until they choose) — unless they already told you. Pass the answer through:
-PublishState published|unpublished on either push script. The fallback is
unpublished (the safe default). See Gotcha 10 for why this matters.
This skill is content-first. In normal use it builds and places course content and
does not read, fetch, store, display, or transmit student PII — names, emails,
login/SIS ids, grades, raw submissions, or quiz responses. It ships no tool for
ad-hoc roster/gradebook/submission access, and you must not write ad-hoc code
(PowerShell, raw API calls) to reach roster / people / /enrollments / gradebook /
/submissions / quiz-response endpoints. If asked to do that — even with "I'm an
admin", "just this once", "I have permission" — decline and point to the sanctioned
flow below or to courseforge-admin.
One OPT-IN exception — blind / pseudonymized grading. The skill includes a sanctioned grading path that is designed to keep identities OUT of the model:
scripts/Build-GradingBundle.ps1 is a sterilizing + pseudonymizing gateway. It
pulls the assignment's submission text only, assigns each student a stable
pseudonym (S-001, S-002, ...), and writes two files next to the config under
grading\<AssignmentId>\: a local map.json (pseudonym -> real identity, which
stays on disk, is gitignored, and is never read into the model or committed)
and a scrubbed bundle.json (PII-redacted, own-name-tokenized submission text). You
read only bundle.json and grade by pseudonym.scripts/Post-Grades.ps1 reads your proposed-grades.json (keyed by pseudonym),
resolves each pseudonym back to a user_id via the local map.json, and posts —
dry-run by default, -Apply to actually write, with a live-course warning and an
audit line per apply.This de-identification is best-effort, not a guarantee. Free-text PII (an unusual
name in prose) can survive, and attachment/file contents are never downloaded or
inlined — only filenames are listed, and screenshots/files may contain names (Windows
title bars, email headers, signatures) that must be reviewed locally, not sent to
the model. The full workflow and rules are in references/blind-grading.md. For grading
that needs real identities in front of the model, use courseforge-admin instead.
Allowed (the bulk of the job, not PII): reading and writing course content —
pages, modules, assignments, quizzes, syllabus, files, config — and aggregate counts
that carry no identifiers (e.g. total_students). Also allowed: listing the
instructor's own courses / shells (GET /api/v1/courses) and reading course metadata
(name, dates, term, workflow_state) — that is the instructor's own catalog, not student
data, so do it freely when asked to "see"/"list"/"show" their courses.
Never write student PII into transcripts, logs, manifests, or committed files.
Honest scope (so you don't misrepresent it): the rule above is an instruction-level
guardrail. The canvas-pii-guard component enforces it locally — a PreToolUse hook
that blocks student-data API calls and local-cache reads before they run
(fail-closed), and it now recognizes Build-GradingBundle.ps1 and Post-Grades.ps1 as
sanctioned gateways while still blocking every other student-data access. Pair it
with a scoped Canvas token (a role without view-grades / view-students permissions)
for the real enforcement. Don't claim it is an air gap or unbreakable; the protections
are prevention (block hook + no ad-hoc tools) plus best-effort de-identification.
Notion hub page (Page Index + weekly schedule)
-> one styled, Canvas-safe HTML file per page (the conversion — the real work)
-> a manifest mapping each file to its week-module + Learn/Build slot
-> VERIFY each file landed in the right slot (non-negotiable — see Gotcha 1)
-> push: create/update pages, build modules, place items (Push-CanvasPages.ps1)
-> trim the course nav (Trim-CanvasNav.ps1)
Everything lives under a project working directory (the user already has one, or make one). Layout used by the scripts:
<project>/
canvas.token # the API token, one line (keep private)
canvas.config.<courseId>.json # { base_url, course_id, course_label }
canvas-export/
pages/<COURSE>/<id>.html # one converted page per Notion page
manifest.<courseId>.json # slot mapping (see below)
canvas.state.<courseId>.json # written by the uploader; enables idempotency
Easiest path — one command does all the token/config setup. For a
non-technical instructor, do NOT make them find folders, copy paths, or hand-create
a .token file (Windows hides extensions, so they end up with canvas.token.txt).
Instead run the onboarding script from their working directory:
scripts\Setup-Canvas.ps1
It asks just two things — their course web address and their access token
(typed hidden) — then writes canvas.token and canvas.config.<id>.json correctly,
adds a .gitignore so the token can't be pushed, and tests the connection,
printing the course name on success. It is forgiving: if they already saved the
token as canvas.token.txt, a Canvas Token.txt, or a pasted .rtf in that folder,
it finds it, fixes it, and reuses it instead of asking. You can also drive it
non-interactively: Setup-Canvas.ps1 -CourseUrl <url> -Token <tok>. Never echo the
token back. Prefer this over the manual steps below.
If you must set up by hand instead:
fetch and search) —
only for Notion-sourced builds (Mode A)..rtf, run
scripts/Extract-CanvasToken.ps1 to pull it into canvas.token (RTF splits
the token across formatting runs; the script rejoins it). Otherwise just have
them save it as plain text in canvas.token. Never echo the token back.https://SCHOOL.instructure.com/ courses/12345 → base_url https://SCHOOL.instructure.com, course_id 12345.
Write canvas.config.<id>.json. Prefer an unpublished shell first.GET /api/v1/courses/:id (expect the course name back).Fetch the hub page. It has a Page Index (the canonical list of pages, each with
its real title) and a weekly schedule (which page is the Learn/Read vs the
Build/Do/Assess for each week). Build a work-list: for every page, record
notion_id, title, module ("Week N — Theme"), module_position, item_type, position.
Trust the week themes for placement, but be skeptical of the schedule's page→week links (they can be miswired). The titles in the Page Index are reliable; verification in Step 4 is what actually guarantees correctness.
Pages the schedule references but that live in other Notion courses (shared projects, quizzes) are gaps — don't fabricate them. List them for the user.
Read references/style-guide.md (the component library + hard rules) and
references/conversion-spec.md (the exact per-page conversion instructions).
Write one <id>.html per page into canvas-export/pages/<COURSE>/.
The output is a single inline-styled <div> (no <html>/<head>). It must
survive the Canvas sanitizer: inline styles only; no tables, <script>,
<style>, class=, nested lists, <ol>, <br>, or box-shadow (see Gotcha 3).
Apply the humanizer lightly (Gotcha 4).
For more than ~10 pages, fan out a parallel workflow — one agent per page —
instead of converting serially. See references/workflow-pattern.md. It's
dramatically faster (51 pages in ~7 minutes vs. a long serial slog).
canvas-export/manifest.<id>.json:
{
"course_label": "IMT 1213 — Game Theory and Mechanics",
"pages": [
{ "notion_id": "abc…", "file": "canvas-export/pages/IMT1213/abc….html",
"title": "Course Syllabus", "module": "Start Here",
"module_position": 1, "item_type": "Info", "position": 1 },
{ "notion_id": "def…", "file": "…/def….html",
"title": "What Is a Game?", "module": "Week 1 — What Is a Game?",
"module_position": 2, "item_type": "Read", "position": 1 }
]
}
module_position orders the modules (use a "Start Here" at 1, then weeks at 2..14
so the syllabus sits on top). position orders items inside a module (Learn=1,
Build=2, Assess=3). The Canvas page title comes from title; the page body from
file.
scripts\Verify-Slots.ps1 -Root "<project>" -ManifestPaths .\canvas-export\manifest.<id>.json
This compares every file's rendered <h2> hero (the page actually fetched)
against its slot title. Exit code 0 = safe to push. Any mismatch means
content scrambled (Gotcha 1) — fix first.
scripts\Push-CanvasPages.ps1 `
-ConfigPath .\canvas.config.<id>.json `
-ManifestPath .\canvas-export\manifest.<id>.json `
-StatePath .\canvas.state.<id>.json `
-PublishState unpublished # ASK the user first (Gotcha 10); default unpublished
Creates/updates each page, builds the modules, and places items in order. It's
idempotent via the state file — re-run any time to push edits without
duplicating. Use -WhatIf for a dry run.
scripts\Trim-CanvasNav.ps1 -BaseUrl https://SCHOOL.instructure.com -CourseIds 12345
Hides the institutional bloat, leaving a clean keep-list. Override -Keep for a
different school/layout (find tab ids via GET /courses/:id/tabs).
GET /courses/:id/pages and /modules?include[]=items to confirm counts. Report
the module/item structure, the cross-course gaps from Step 1, and any
syllabus values worth confirming (credit hours, CRN) before the user publishes.
You will often be asked to create content and put it on Canvas ("write a final exam and add it", "make a Week 14 study guide", "add a rubric page to Week 3"). The placement is the skilled part; the steps:
references/style-guide.md
(one outer <div>, hero <h2>, <h3> section cards, the navy/gold components).
Humanize prose (Gotcha 4). Write each file under canvas-export/pages/<COURSE>/.
For a batch (e.g. a study guide + exam per course), fan out subagents.
Hints are not solutions: never put a complete, copy-paste-able solution (full code or a finished
worked example) in an assignment's hints OR requirements. Give a skeleton (class + method
signatures with // TODO: where the graded logic goes), name the APIs the student needs without
assembling them, and use at most one sparing ____ fill-in-the-blank. The student must still write
every line the rubric grades. Litmus test: if pasting the hint earns the rubric, it gives away too much.points and an assignment group so the gradebook is
organized (e.g. Assignments / Midterm / Final Exam).Push-CanvasProject.ps1 (see references/project-course.md).
For plain pages only, the lesson manifest + Push-CanvasPages.ps1 is enough.Verify-Slots.ps1 on the pages, push (idempotent),
then confirm via the API (GET /modules?include[]=items, /assignments, /quizzes).Derive, do not ask. The skill pulls the term start from the Canvas course
dates and the length from the course's own Week N modules, so it normally
needs NO instructor input to schedule. Run scripts/Set-DueDates.ps1 -Auto:
start_at from GET /courses/:id (date part); if null it
falls back to the term start via GET /courses/:id?include%5B%5D=term (the bracket
MUST be percent-encoded as %5B%5D or Canvas 404s).Week N modules. Do NOT use
Canvas end_at for length: end_at is the access-end, padded past finals
(e.g. Dec 25 / Dec 17), not the instructional/finals end (full-term finals are
Dec 7-11). The module count is the reliable length.references/academic-calendar.md (a fenced json block), looked up by
scripts/Get-TermCalendar.ps1, which infers the term from the start date
(month >= 8 -> Fall, 1-4 -> Spring, 5-7 -> Summer).Only ASK the instructor when the course is a blank shell (no start date AND no
Week N modules) or the term is not in the calendar table; in those cases -Auto
stops with an explicit message. The default due rule is the chosen weekday (default
Monday) after each week at 11:59 PM, auto-shifted past holidays, full-break weeks
skipped, with the final on the finals-window end. The Week-1 anchor gives Week 1 a
full first week (its due day is the first chosen weekday MORE than 6 days after start),
so a Thursday face-to-face start and the Monday online start produce the SAME table.
Always SHOW the week-by-week table for approval before applying. Set-DueDates.ps1
is dry-run by default (prints the table it would write); pass -Apply to write.
For a hand-built table, the explicit path still works: compute with
scripts/Compute-DueDates.ps1 -AsJson and pass it via Set-DueDates.ps1 -DueDatesJson.
The calendar dates shift yearly, so re-check the source and extend the json table each
year.
The project/capstone section below is the detailed reference for Mode B's graded pieces (assignments, discussions, quizzes, mixed modules, and the pages-only-to-gradebook migration).
A lesson course is pages-only. A project/capstone course also has a front-page
Home, a Syllabus tab, upload assignments, graded discussions (e.g. weekly
standups), graded quizzes (Classic Quizzes, e.g. a final exam), and modules
whose items mix Page / Assignment / Discussion / Quiz / SubHeader. That whole shape
is built by scripts/Push-CanvasProject.ps1 from a project manifest — see
references/project-course.md for the manifest schema (including the quizzes
question-bank format and assignment groups) and the run command. The
page-conversion work (Steps 1-2) is identical; only the manifest and the push
script differ. A graded discussion is a discussion_topics POST with an
assignment[...] block (Gotcha 5); it also appears in the assignments list, which
is expected. A graded quiz is created unpublished, its questions are (re)built, then
its publish state is set from -PublishState; a graded quiz (quiz_type=assignment)
likewise carries a backing assignment that shows in the assignments list and
gradebook (also expected).
Upgrading a pages-only lesson course to a real gradebook: map each former
"Assignment — X" wiki page to an assignments[] entry whose file is that same
HTML (it becomes the assignment description), parse its points from the page's
Total: N points rubric, then delete the now-redundant wiki page so students
do not see a duplicate. references/project-course.md has the recipe.
Notion IDs in one workspace share a long prefix; the fetch tool intermittently
resolves a different same-prefix page, non-deterministically — even pulling a
page from another course. Always run Verify-Slots.ps1 before pushing.
When it flags mismatches, the correct page bodies are almost always already on
disk under the wrong filenames (it's a permutation). Reassemble by content:
read each correct-content file's <h2> to identify it, then write it to the
filename of the slot that wants that content, fixing the eyebrow line and footer
week to match the slot. Read all sources into memory before writing any targets
(it's a cycle). Only re-fetch as a last resort, and use the full dashed UUID
(8-4-4-4-12) which resolves more precisely than the bare 32-char id.
The same flakiness bites notion-create-pages: the child id it returns can be a
wrong same-prefix page. Don't trust that id — confirm the create by re-fetching the
known parent hub and reading its updated child list / Page Index.
PUT /courses/:id/tabs/:tab_id silently no-ops on a form body (returns 200,
changes nothing). Send a JSON body with Content-Type: application/json.
Trim-CanvasNav.ps1 already does this — don't "simplify" it back to a form post.
It strips box-shadow (so don't rely on it — use borders), and removes <table>,
<script>, <style>, class=, id= styling, nested <ul> in <li>, <ol>,
and <br>. Convert tables to label/value <div> rows or a bold heading + a
single-level <ul>. Code goes in a <div> with white-space: pre-wrap (escape
< > &). Canvas also auto-appends the school's own theme <script> to every page
body — that's expected, not yours.
Apply the humanizer to body prose only: drop em dashes (use commas/periods/ colons/parens), cut filler/hedging/AI-vocabulary. Keep em dashes in page titles and module names (renaming a module spawns a duplicate, and titles mirror Notion). Keep emoji tasteful (a goal 🎯, an alert ⚠️, a practice ✅; drop decorative 💡). Much source prose is the instructor's own writing — the footprint should be small.
Canvas renders the page title as the only <h1>, so the hero title is an <h2>
and section headings are <h3>. Keep it that way for screen readers.
.ps1 as ANSIA literal — (or any non-ASCII char) in a script string literal becomes â€",
and the smart quote can even break parsing. So: keep .ps1 files pure ASCII;
build an em dash as [char]0x2014 when you must emit one; read data files with
-Encoding UTF8; send request bodies as UTF-8 bytes. Best of all, keep
em-dash text (titles, module names) in the manifest JSON (read as UTF-8), never
in a script literal — the supplied scripts do this, which is why there is no
title-repair pass.
PUT /pages/:url with a new title gives the page a new slug, so a stored url goes
stale. Trust the url in the PUT response before adding the page to a module
(Push-CanvasPages.ps1 does this on update). Project pages dodge this by upserting
by a fixed slug and never renaming.
POST /modules/:id/items 400s on a form body for Assignment / Discussion /
SubHeader items and is unreliable for Page items. Send JSON ({ "module_item": {...} }, UTF-8 bytes). Both push scripts use the Add-ModuleItem helper for this.
(Module create/update/delete still take form bodies — only the item add is JSON.)
replace_content reorders child PAGES but not inline DATABASESInline child databases stay pinned where they are. To position a database, move it
with move-pages; don't fight replace_content. (Authoring-side, only relevant
when you also restructure the Notion source.)
Pages, assignments, discussions, quizzes, and modules all have a published flag.
Content becomes student-visible the moment the course itself is published, and a
published quiz/assignment with no availability window is immediately takeable —
so a final exam can go live early by accident. Before any push, ask published vs
unpublished and pass -PublishState; the fallback is unpublished. For exams, also
recommend setting the assignment/quiz available from / until and due_at dates so
they unlock only during finals.
The API token inherits the owner's full permissions: in any course with enrolled
students it can read names, emails, login/SIS ids, grades, submissions, and quiz
responses. That capability is exactly why the Student Data Policy (top of this
file) limits this skill to content in normal use and routes the only student-data path
through the sanctioned blind-grading gateway (Build-GradingBundle.ps1 ->
Post-Grades.ps1), which keeps raw identities local and tokenizes what the model sees.
Outside that flow, decline ad-hoc roster / gradebook / submission access. For
grading with real identities use courseforge-admin; other real student-data
needs go through the institution's approved process. Never echo or write student PII
(names, emails, ids, grades) into transcripts, logs, or committed files — and never
commit grading\ (the local map.json lives there).
PUT /pages/:slug upserts — reuse the stored slug to avoid duplicatesOn this instance PUT /pages/:slug creates the page if the slug does not exist and
updates it if it does. So on a re-push, use the existing slug (from the state file
that maps notion_id -> url) rather than a freshly derived one, or you will create a
second page instead of updating the first. New pages get a clean, stable slug you
control (e.g. week-15-final-exam). When you convert an assignment page into a real
Assignment object, delete the old wiki page by its slug so the two do not coexist.
references/style-guide.md — Canvas-safe component library, palette, accessibility.references/conversion-spec.md — the exact per-page conversion prompt (reuse verbatim for workflow agents).references/workflow-pattern.md — how to fan out the bulk conversion across agents.references/project-course.md — project/capstone courses: the project manifest (assignments, graded discussions, front page, syllabus, mixed-type module items) and how to push them.references/blind-grading.md — the OPT-IN blind/pseudonymized grading workflow (sterilizing+pseudonymizing gateway -> grade by pseudonym -> dry-run-first poster), the local-map/never-commit rule, and the screenshot/best-effort caveats.references/academic-calendar.md — MGCCC term formats + Fall 2026 anchor dates (start/finals/breaks), the source-of-truth calendar URL (re-check yearly), and the DEFAULT due rule used by the due-date scripts.scripts/Setup-Canvas.ps1 — one-command onboarding (use this first for non-technical users): asks for the course web address + access token (hidden), writes canvas.token + canvas.config.<id>.json, adds a protective .gitignore, and tests the connection. Forgiving of a stray canvas.token.txt / Canvas Token.txt / pasted .rtf (finds, fixes, reuses). Params: -WorkingDir, -CourseUrl, -Token, -CourseLabel, -ShowToken.scripts/Push-CanvasPages.ps1 — idempotent lesson-course uploader + module builder (params: ConfigPath, ManifestPath, StatePath, -PublishState published|unpublished, -WhatIf).scripts/Push-CanvasProject.ps1 — idempotent project/capstone builder: pages + front page + syllabus tab + graded assignments + graded discussions + graded quizzes (Classic Quizzes) + assignment groups + mixed-type modules (params: ConfigPath, ManifestPath, StatePath, -PublishState published|unpublished, -SkipModules, -WhatIf).scripts/Verify-Slots.ps1 — hero-vs-slot check for Page bodies; run before every push. (Does not inspect assignments/discussions — spot-check those by hand.)scripts/Trim-CanvasNav.ps1 — nav trim via JSON body.scripts/Extract-CanvasToken.ps1 — pull a token out of an .rtf into canvas.token.scripts/Build-GradingBundle.ps1 — OPT-IN blind-grading sterilizing + pseudonymizing gateway: fetches submission text, writes a LOCAL grading\<id>\map.json (gitignored, never read by the model) and a scrubbed, pseudonymized bundle.json to grade from (params: -ConfigPath, -AssignmentId, -TokenPath, -OutDir). Sanctioned by canvas-pii-guard.scripts/Post-Grades.ps1 — pseudonym-aware grade poster: reads map.json + proposed-grades.json, resolves each pseudonym to a user_id, dry-run by default, -Apply to post; refuses unknown pseudonyms; live-course warning + audit (params: -ConfigPath, -AssignmentId, -TokenPath, -OutDir, -Apply). Sanctioned by canvas-pii-guard.scripts/Compute-DueDates.ps1 — compute a week-by-week due-date table from a term start, week count, finals-window end, and break ranges (default due = the chosen weekday after each week at 23:59, auto-shifted past holidays, full-break weeks skipped, final on the finals end). Week-1 anchor = first chosen weekday >6 days after start (start day-of-week no longer shifts the schedule). Deterministic (ParseExact); prints a table and supports -AsJson (params: -StartDate, -Weeks, -FinalsEnd, -Breaks, -DueTime, -DueWeekday, -AsJson).scripts/Get-TermCalendar.ps1 — read the machine-readable term table (fenced json block) from references/academic-calendar.md; infer the term from a start date (month >= 8 -> Fall, 1-4 -> Spring, 5-7 -> Summer) and return its finalsEnd + breaks, or nothing if the term is not in the table (caller then asks the instructor). Dot-source it for the Get-TermCalendar / Get-TermName functions, or run standalone (params: -StartDate, -CalendarPath).scripts/Set-DueDates.ps1 — apply a due-date table to a course's assignments + quizzes by reading the project manifest: maps each "Week N" module to its DueAt, resolves each Assignment/Quiz item by name/title to its Canvas id, and PUTs assignment[due_at]/quiz[due_at]. Skips "Start Here"; dry-run by default, -Apply to write. Two ways to supply the table: explicit -DueDatesJson (a Compute-DueDates -AsJson file), or -Auto which derives it with no instructor input — start from the Canvas course start_at (term-start fallback via ?include%5B%5D=term), length from the manifest's max Week N (NOT raw end_at, which is the padded access-end), finals+breaks from Get-TermCalendar; stops and asks only for a blank shell or an unknown term (params: -ConfigPath, -ManifestPath, -DueDatesJson, -Auto, -DueTime, -DueWeekday, -TokenPath, -Apply, -WhatIf).Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub billathekilla737/garris-canvas-tools --plugin courseforge