An AI-assisted Canvas course builder. Upload a Canvas course template, describe your course, get an .imscc ready to import.
A fork of ClassBuild by Jason Tangen, retargeted at instructors who need to ship Canvas-shaped courses fast: takes a Canvas course export (.imscc) as a structural template, generates AI content for each module that matches the template's pattern, and produces a fresh .imscc for re-import.
Compatible with Canvas LMS — not affiliated with Instructure, Inc. Canvas® is a registered trademark of Instructure, Inc.
| Capability | Where it lives |
|---|---|
Upload a Canvas .imscc template, parse the module structure, classify modules as verbatim / pattern / example-pattern |
/templates page |
| Generate per-chapter Canvas Module content (1× Module Overview, 1+ MN Instructor Notes, 1× MN Discussion) | "Canvas Module" tab on Build |
| Batch generation: "Generate All Canvas Modules" for the whole course at once | Build header |
Export an .imscc that mirrors the template's structure: verbatim modules carry through untouched, pattern modules replaced by generated content, LTI links + web_resources passed through |
"Export for Canvas (.imscc)" button |
Upload a course-outline .docx and have an LLM extract title / description / course information / course materials → populates Canvas's Syllabus tab body at export time |
Setup page |
| Pluggable LLM provider — Anthropic (Claude), Google Gemini or Ollama Cloud for course-content generation | Setup → Course-content provider |
| Pluggable research backend — Claude web search, Tavily, or Wikipedia | Setup → Research backend |
| Advanced-mode toggle — hides multimedia outputs (slides, audio, infographic, weekly challenge, activities) by default to keep the Canvas-focused happy path tight | Setup page bottom |
git clone https://github.com/daTechGuy/CanvasClassBuild.git
cd CanvasClassBuild
npm install
npm run devOpen localhost:5173.
Bring Your Own Key — keys are entered through the Setup page and never leave your browser. There is no server-side component except a Vite dev proxy that exists solely to bypass Ollama Cloud's missing browser CORS headers.
| Provider | Required when… | What it does |
|---|---|---|
| Anthropic Claude | LLM provider = Anthropic, OR research backend = Claude web search | Course-content generation; Claude's built-in web search for the Research stage |
| Google Gemini | LLM provider = Gemini | Course-content generation, called straight from the browser (Google allows CORS — no proxy). Get a key at aistudio.google.com/apikey. The model id is editable (default gemini-3.8-flash). Doesn't run Claude's web-search tool, so pair it with Tavily or Wikipedia research. On Google's free tier prompts may be used to improve its products — check your plan |
| Ollama Cloud | LLM provider = Ollama Cloud | Course-content generation. Free tier doesn't include cloud models — see ollama.com/settings/keys |
| Tavily | Research backend = Tavily | Web search for the Research stage. Free tier covers ~1,000 searches/month |
| OpenAI | Advanced mode + you want slide images / chapter figures | Image generation (gpt-image-2, optional) |
| ElevenLabs | Advanced mode + you want audiobook narration | Text-to-speech (optional) |
The Wikipedia research backend needs no key.
- Upload your Canvas course template. Visit
/templates, drop in an.imsccyou exported from a previous Canvas course. The parser identifies which modules to keep verbatim (Instructor Information, Begin Here / Introductory) and which are placeholder patterns to be replaced (Module 1, Module 2, …). - Setup. Enter the course topic and chapter count (defaults to 16 when a template is selected). Optionally upload a course-outline
.docx; an LLM extracts the title, description, course information, and materials so they populate the Canvas Syllabus tab at export time. - Syllabus. AI generates chapter titles in the locked
Module N: <topic>format. Edit the topic suffix per chapter from the inline editor — the locked prefix is preserved on save. - Research. Choose a backend. Claude web search is the highest-quality but Anthropic-only; Tavily and Wikipedia work with any LLM provider. Skippable.
- Build. For each chapter, click "Generate Canvas Module" (or batch with "Generate All Canvas Modules") to produce the Module Overview + Instructor Notes pages + Discussion. The locked
MN Instructor Notes:/MN Discussion:prefixes are added at export time. - Export. "Export for Canvas (.imscc)" routes through a template-aware exporter. Output: a fresh
.imscccontaining your verbatim modules, your generated Module N modules, all original LTI links, all original web_resources images, and a Syllabus tab body composed from the outline DOCX. Drop it into Canvas via Settings → Import Course Content → Common Cartridge Package.
The upstream ClassBuild also produces:
- Interactive HTML reading with embedded widgets
- Gamified practice quiz with confidence calibration
- In-class quiz (5 shuffled versions + answer keys)
- PowerPoint slides with speaker notes
- AI-narrated audiobook (ElevenLabs)
- AI-generated infographic (Gemini)
- Weekly mastery challenge with 6 question types and SCORM 2004 wrapper
- Discussion starters and classroom activities
These are hidden behind the "Advanced mode" toggle on Setup so the Canvas-focused workflow stays tight. Flip it on and the Build page surfaces all the additional tabs.
Vitest scaffold covering the parser + exporter critical paths. Network-free, runs in <1s.
npm test # run once
npm run test:watch # re-run on file change
npm run test:coverage # generate coverage report (HTML at coverage/index.html)CI runs test:coverage on every push and PR; the resulting coverage/
directory uploads as a workflow artifact (coverage-report, retained 14 days).
Baseline at the time of the test scaffold lands: ~22% lines / ~15% branches
across the instrumented modules (parser, exporters, research backends,
services, setup components). Pages — BuildPage, SyllabusPage,
ExportPage, ResearchPage — are not yet instrumented for unit tests.
Without an uploaded template, "Export for Canvas (.imscc)" writes a native Canvas course export — the same file layout Canvas's own exporter produces (module_meta.xml, wiki_content/ pages, non_cc_assessments/ quizzes, assessment_meta.xml, assignment_groups.xml, syllabus). This matters: a package carrying Canvas's course_settings/canvas_export.txt marker is read by Canvas's native importer, which silently ignores plain Common Cartridge manifest content, so a "generic" cartridge with that marker imports with no modules, pages or quizzes.
Check any cartridge offline with npm run validate:imscc -- file.imscc (it flags exactly that class of problem and runs in CI against real Canvas-made fixtures). To test an import end to end, use the Canvas harness in tools/canvas-test/ — it builds a dev Canvas (with Instructure's QTIMigrationTool; without it quizzes are skipped silently) and shows what Canvas actually built.
Current coverage:
tests/template-parser.test.ts— module classification (verbatim / pattern / example-pattern), prefix detection (Module N:,MN Instructor Notes:, fully-lockedModule N Overview),(Example to Edit)placeholder marker,**EDIT**markers, example-pattern content extraction.tests/imscc-exporter.test.ts— the native-Canvas cartridge (the no-template export): module_meta/manifest consistency, reading as a Canvas Page, both QTI flavours per quiz with correct answers, graded vs practice quizzes + assignment group, published discussions, syllabus, deterministic ids, HTML escaping, slide-deck rendering rules.tests/template-imscc-exporter.test.ts— round-trip: build template fixture → emit IMSCC → re-parse → verify verbatim modules preserved, pattern modules replaced,web_resources/+lti_resource_links/pass through. Outline-field overrides on title / syllabus body / manifest LOM.tests/parse-outline-docx.test.ts— outline-DOCX field extraction with the LLM mocked: clean JSON, code-fenced JSON, partial / empty / malformed responses, char-cap on long input, provider override forwarding.tests/generate-template-chapter.test.ts— Canvas Module generation with the LLM mocked: parse success / failure / missing-required-field, few-shot exemplar embedding, no-example case, Ollama provider plumbing, Anthropic Sonnet default.tests/components/TemplatePicker.test.tsx— empty state, listing uploaded templates, selecting a template setstemplateId+numChapters=16, "No template" doesn't touchnumChapters, active-template notice.tests/components/CourseOutlineUpload.test.tsx— empty preview, populated preview, Clear resets the store, textarea edits write through, blanking a field deletes the key.tests/components/TemplateTitleEditor.test.tsx— locked-prefix display, save reassemblesModule N: <suffix>with prefix intact, blanking the suffix preserves just the prefix, off-pattern titles get a synthesized prefix, Reset rolls drafts back.tests/research-wikipedia.test.ts— Wikipedia backend with LLM +fetchboth mocked: query gen → per-query API call → synthesis call ordering, URL builder + dedup,<span class="searchmatch">snippet stripping, per-query failure doesn't abort batch, progress phase transitions, fallback query when query-gen returns nothing parseable.tests/research-tavily.test.ts— Tavily backend with LLM +fetchboth mocked: missing-key throw, POST body shape (max_results,search_depth,include_answer) + bearer auth, cross-query dedup by URL, content snippets feed into synthesis prompt, hits without a URL are dropped, per-query failure doesn't abort batch, progress phase order,published_datepreserved aspageAge.tests/research-anthropic.test.ts— Anthropic research backend withstreamWithRetrymocked: streamWithRetry called with the Claude key +web_search_20250305tool + forcedprovider:'anthropic', progress phases emitted inthinking → searching → compilingorder, queries appended viaappendQueries, streamed text appended viaappendSynthesisText, dedup by URL across batches,setLatestSourceis the last fresh hit, valid JSON parses into a dossier, malformed text falls back to a dossier built from collected web results.
The app is a static SPA, so any static host works. The one wrinkle is Ollama Cloud requires a serverless proxy: ollama.com doesn't set CORS headers, so direct browser fetches are blocked. A Vercel Edge function ships with the repo at api/ollama-proxy.ts — Vercel picks it up automatically. Other hosts:
- Vercel —
vercel deploy. The Edge function and the SPA rewrite invercel.jsoncome along for free. - Cloudflare Pages — port
api/ollama-proxy.tsto a Workers function (same logic, different runtime API). - Static-only host (Netlify static, GitHub Pages, S3) — Ollama selection won't work; users must pick Anthropic.
The browser always calls /api/ollama-proxy regardless of provider — the Vite dev server forwards in development, the Vercel function forwards in production.
Canvas course CLI (scripts/canvas-course.ts)
Headless equivalent of the UI's Canvas-template flow. Takes a topic + a template .imscc (and optionally an outline .docx), generates a syllabus + Canvas Module content for each chapter, and writes a fresh .imscc ready for Canvas import.
ANTHROPIC_API_KEY=sk-... npx tsx scripts/canvas-course.ts \
--topic "Statistics for Data Science" \
--template ./template.imscc \
--outline ./outline.docx \
--chapters 16 \
--output ./output/statsFlags:
| Flag | Default | Purpose |
|---|---|---|
--topic |
required | Course topic |
--template |
required | Path to a Canvas .imscc template export |
--outline |
— | Path to a course-outline .docx (populates the Canvas Syllabus tab body) |
--chapters |
16 |
Number of Module N modules to generate |
--output |
./output |
Output directory |
--level |
advanced-undergrad |
Audience |
--length |
standard |
concise / standard / comprehensive |
--cohort |
60 |
Class size |
--notes |
— | Additional learner context |
--provider |
anthropic |
LLM provider — anthropic, gemini or ollama. Set ANTHROPIC_API_KEY, GEMINI_API_KEY or OLLAMA_API_KEY to match |
--ollama-model |
gpt-oss:120b-cloud |
Ollama model used when --provider ollama |
--gemini-model |
gemini-3.8-flash |
Gemini model used when --provider gemini |
--concurrency |
3 |
Parallel chapters during Canvas Module generation |
--syllabus |
— | Path to existing syllabus.json (skip regeneration) |
--skip-canvas-module |
false |
Skip per-chapter content generation (for fast manifest-only test runs) |
Gemini example (GEMINI_API_KEY must be set in your environment):
npx tsx scripts/canvas-course.ts \n --provider gemini \n --topic "Statistics for Data Science" \n --template ./template.imscc \n --chapters 8 \n --output ./output/statsBefore relying on a Gemini model, run npm run smoke:gemini (reads GEMINI_API_KEY from your environment): it exercises plain text, streaming, thinking, structured-JSON quiz output and the error paths against the live API.
Ollama example:
OLLAMA_API_KEY=... npx tsx scripts/canvas-course.ts \
--provider ollama \
--ollama-model gpt-oss:120b-cloud \
--topic "Statistics for Data Science" \
--template ./template.imscc \
--chapters 8 \
--output ./output/statsIf ANTHROPIC_API_KEY is set alongside --provider gemini or ollama, outline-DOCX extraction always uses Claude (its instruction-following on the small JSON-extraction task is more reliable) regardless of --provider.
Original ClassBuild CLI (scripts/generate-course.ts)
The upstream CLI is unchanged. It does not know about the Canvas template export, the outline DOCX path, or the Ollama backend — use it for the multimedia (slides + audio + infographic) flow.
src/
services/
template/
parser.ts # parse uploaded .imscc → Template
generateChapter.ts # per-chapter Canvas Module generation
templateImsccExporter.ts # export .imscc using uploaded template as base
parseOutlineDocx.ts # mammoth + LLM extraction
research/
anthropic.ts # Claude web_search backend
tavily.ts # Tavily REST + LLM synthesis
wikipedia.ts # Wikipedia API + LLM synthesis
llm/
anthropic.ts # Anthropic SDK streaming
ollama.ts # Ollama Cloud NDJSON streaming
gemini.ts # Google Gemini SSE streaming (direct from the browser)
store/
templateStore.ts # uploaded templates (parsed + raw .imscc Blob)
pages/
TemplatePreviewPage.tsx # /templates upload + preview
components/
setup/
TemplatePicker.tsx
CourseOutlineUpload.tsx
syllabus/
TemplateTitleEditor.tsx
React 19 · Vite 7 · TypeScript 5.9 · Tailwind CSS 4 · Zustand · JSZip · mammoth.js · Claude (Opus 4.8 / Sonnet 4.6 / Haiku 4.5) · Google Gemini · Ollama Cloud · Tavily · OpenAI gpt-image-2 · ElevenLabs
See CONTRIBUTING.md for dev setup, commit conventions, and what every PR needs. CI runs tsc -b, npm run lint, npm test, and npm run build on every push and PR.
Built on ClassBuild by Jason Tangen — the foundation of this fork. The IMSCC export, Canvas template handling, course-outline DOCX parsing, multi-provider LLM/research routing, and the rebrand are added on top, but the original learning-science engine, prompt library, and content generators are all from upstream.
MIT — same as upstream.