canvas-lms-mcp 1.4.1 → 1.5.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/README.md CHANGED
@@ -10,14 +10,14 @@
10
10
 
11
11
  MCP server for [Canvas LMS](https://www.instructure.com/canvas). Read courses, assignments, submissions, rubrics, quizzes; grade, comment, manage course content, and handle Canvas admin workflows from any AI agent.
12
12
 
13
- 100 tools across Canvas courses, assignments, submissions, rubrics, quizzes, files, users, groups, enrollments, discussions, modules, pages, calendar, conversations, peer reviews, accounts, analytics, outcomes, student workflows, dashboard, and health checks. Three deployment modes: stdio, HTTP, and library import.
13
+ 104 tools across Canvas courses, assignments, submissions, gradebook history, rubrics, quizzes, files, users, groups, enrollments, discussions, modules, pages, calendar, conversations, peer reviews, accounts, analytics, outcomes, student workflows, dashboard, and health checks. Three deployment modes: stdio, HTTP, and library import.
14
14
 
15
15
  ## Comparison
16
16
 
17
17
  | | canvas-lms-mcp | [vishalsachdev/canvas-mcp](https://github.com/vishalsachdev/canvas-mcp) | [DMontgomery40/mcp-canvas-lms](https://github.com/DMontgomery40/mcp-canvas-lms) |
18
18
  |---|---|---|---|
19
19
  | Language | TypeScript | Python | TypeScript |
20
- | Tools | 88 | 80+ | 54 |
20
+ | Tools | 104 | 80+ | 54 |
21
21
  | License | [![License: MIT](https://img.shields.io/github/license/bruchris/canvas-lms-mcp)](https://github.com/bruchris/canvas-lms-mcp/blob/main/LICENSE) | [![License](https://img.shields.io/github/license/vishalsachdev/canvas-mcp)](https://github.com/vishalsachdev/canvas-mcp/blob/main/LICENSE) | [![License](https://img.shields.io/github/license/DMontgomery40/mcp-canvas-lms)](https://github.com/DMontgomery40/mcp-canvas-lms/blob/main/LICENSE) |
22
22
  | Last commit | [![Last commit](https://img.shields.io/github/last-commit/bruchris/canvas-lms-mcp)](https://github.com/bruchris/canvas-lms-mcp) | [![Last commit](https://img.shields.io/github/last-commit/vishalsachdev/canvas-mcp)](https://github.com/vishalsachdev/canvas-mcp) | [![Last commit](https://img.shields.io/github/last-commit/DMontgomery40/mcp-canvas-lms)](https://github.com/DMontgomery40/mcp-canvas-lms) |
23
23
 
@@ -173,7 +173,7 @@ Once configured, try these prompts with your AI client:
173
173
 
174
174
  ## Tool Inventory
175
175
 
176
- ### All Registered Tools (100)
176
+ ### All Registered Tools (104)
177
177
 
178
178
  | Category | Tools |
179
179
  |----------|-------|
@@ -184,6 +184,7 @@ Once configured, try these prompts with your AI client:
184
184
  | Rubrics | `list_rubrics`, `get_rubric`, `get_rubric_assessment`, `submit_rubric_assessment` |
185
185
  | Quizzes | `list_quizzes`, `get_quiz`, `list_quiz_submissions`, `list_quiz_questions`, `get_quiz_submission_answers`, `score_quiz_question` |
186
186
  | Files | `list_files`, `list_folders`, `get_file`, `upload_file`, `delete_file` |
187
+ | Gradebook History | `list_gradebook_history_days`, `get_gradebook_history_day`, `list_gradebook_history_submissions`, `get_gradebook_history_feed` |
187
188
  | Users | `list_students`, `get_user`, `get_profile`, `search_users`, `list_course_users` |
188
189
  | Groups | `list_groups`, `list_group_members` |
189
190
  | Enrollments | `list_enrollments`, `enroll_user`, `remove_enrollment` |
@@ -199,7 +200,7 @@ Once configured, try these prompts with your AI client:
199
200
  | Student | `get_my_courses`, `get_my_grades`, `get_my_submissions`, `get_my_upcoming_assignments` |
200
201
  | Dashboard | `get_dashboard_cards`, `get_todo_items`, `get_upcoming_events`, `get_missing_submissions` |
201
202
 
202
- 72 tools are read-only and 28 tools perform Canvas write operations.
203
+ 76 tools are read-only and 28 tools perform Canvas write operations.
203
204
 
204
205
  All write tools require appropriate Canvas permissions. Canvas enforces its own permission model -- the MCP server does not bypass it.
205
206
 
@@ -347,6 +348,9 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full contribution and validatio
347
348
  - [Student Guide](docs/student-guide.md) -- Token setup, AI client configuration, 10 example prompts
348
349
  - [Educator Guide](docs/educator-guide.md) -- Grading workflows, write operations, privacy considerations
349
350
  - [Integration Guide](docs/integration-guide.md) -- Three integration patterns with code examples
351
+ - [Agent Discovery](docs/agent-discovery.md) -- Generated tool/workflow manifests and workflow-pack index
352
+ - [Educator Assignment Review Workflow](docs/workflows/educator-assignment-review.md) -- Read-first grading flow with write-safety guidance
353
+ - [Student Weekly Planning Workflow](docs/workflows/student-weekly-planning.md) -- Read-only weekly planning sequence for students
350
354
 
351
355
  ## License
352
356
 
@@ -2,10 +2,10 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkXAOI3FO6cjs = require('../chunk-XAOI3FO6.cjs');
5
+ var _chunk6HRYCB4Ccjs = require('../chunk-6HRYCB4C.cjs');
6
6
 
7
7
 
8
8
 
9
9
 
10
- exports.CanvasApiError = _chunkXAOI3FO6cjs.CanvasApiError; exports.CanvasClient = _chunkXAOI3FO6cjs.CanvasClient; exports.CanvasHttpClient = _chunkXAOI3FO6cjs.CanvasHttpClient;
10
+ exports.CanvasApiError = _chunk6HRYCB4Ccjs.CanvasApiError; exports.CanvasClient = _chunk6HRYCB4Ccjs.CanvasClient; exports.CanvasHttpClient = _chunk6HRYCB4Ccjs.CanvasHttpClient;
11
11
  //# sourceMappingURL=index.cjs.map
@@ -2,7 +2,7 @@ import {
2
2
  CanvasApiError,
3
3
  CanvasClient,
4
4
  CanvasHttpClient
5
- } from "../chunk-XP3FFZQV.js";
5
+ } from "../chunk-X7YODHAN.js";
6
6
  export {
7
7
  CanvasApiError,
8
8
  CanvasClient,
@@ -1022,6 +1022,45 @@ var OutcomesModule = class {
1022
1022
  }
1023
1023
  };
1024
1024
 
1025
+ // src/canvas/gradebook-history.ts
1026
+ var GradebookHistoryModule = class {
1027
+ constructor(client) {
1028
+ this.client = client;
1029
+ }
1030
+
1031
+ async listDays(courseId) {
1032
+ return this.client.request(
1033
+ `/api/v1/courses/${courseId}/gradebook_history/days`
1034
+ );
1035
+ }
1036
+ async getDay(courseId, date) {
1037
+ return this.client.request(
1038
+ `/api/v1/courses/${courseId}/gradebook_history/${encodeURIComponent(date)}`
1039
+ );
1040
+ }
1041
+ async listSubmissions(courseId, date, graderId, assignmentId) {
1042
+ return this.client.request(
1043
+ `/api/v1/courses/${courseId}/gradebook_history/${encodeURIComponent(date)}/graders/${graderId}/assignments/${assignmentId}/submissions`
1044
+ );
1045
+ }
1046
+ async getFeed(courseId, params = {}) {
1047
+ const searchParams = new URLSearchParams();
1048
+ if (params.assignment_id !== void 0) {
1049
+ searchParams.set("assignment_id", String(params.assignment_id));
1050
+ }
1051
+ if (params.user_id !== void 0) {
1052
+ searchParams.set("user_id", String(params.user_id));
1053
+ }
1054
+ if (params.ascending !== void 0) {
1055
+ searchParams.set("ascending", String(params.ascending));
1056
+ }
1057
+ const query = searchParams.toString();
1058
+ return this.client.paginate(
1059
+ `/api/v1/courses/${courseId}/gradebook_history/feed${query ? `?${query}` : ""}`
1060
+ );
1061
+ }
1062
+ };
1063
+
1025
1064
  // src/canvas/index.ts
1026
1065
  var CanvasClient = class {
1027
1066
 
@@ -1043,6 +1082,7 @@ var CanvasClient = class {
1043
1082
 
1044
1083
 
1045
1084
 
1085
+
1046
1086
 
1047
1087
  constructor(config) {
1048
1088
  this.client = new CanvasHttpClient(config);
@@ -1065,6 +1105,7 @@ var CanvasClient = class {
1065
1105
  this.analytics = new AnalyticsModule(this.client);
1066
1106
  this.dashboard = new DashboardModule(this.client);
1067
1107
  this.outcomes = new OutcomesModule(this.client);
1108
+ this.gradebookHistory = new GradebookHistoryModule(this.client);
1068
1109
  }
1069
1110
  };
1070
1111
 
@@ -1080,4 +1121,4 @@ var CanvasClient = class {
1080
1121
 
1081
1122
 
1082
1123
  exports.CanvasApiError = CanvasApiError; exports.CanvasHttpClient = CanvasHttpClient; exports.SEARCH_CONTENT_TYPES = SEARCH_CONTENT_TYPES; exports.OUTCOME_CONTEXT_TYPES = OUTCOME_CONTEXT_TYPES; exports.OUTCOME_DETAIL_LEVELS = OUTCOME_DETAIL_LEVELS; exports.OUTCOME_ROLLUP_AGGREGATE_STATS = OUTCOME_ROLLUP_AGGREGATE_STATS; exports.OUTCOME_ROLLUP_SORT_BY = OUTCOME_ROLLUP_SORT_BY; exports.OUTCOME_SORT_ORDER = OUTCOME_SORT_ORDER; exports.OUTCOME_EXCLUDE_OPTIONS = OUTCOME_EXCLUDE_OPTIONS; exports.CanvasClient = CanvasClient;
1083
- //# sourceMappingURL=chunk-XAOI3FO6.cjs.map
1124
+ //# sourceMappingURL=chunk-6HRYCB4C.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-6HRYCB4C.cjs","../src/canvas/client.ts","../src/canvas/courses.ts","../src/canvas/assignments.ts","../src/canvas/submissions.ts","../src/canvas/rubrics.ts","../src/canvas/quizzes.ts","../src/canvas/files.ts","../src/canvas/users.ts","../src/canvas/groups.ts","../src/canvas/enrollments.ts","../src/canvas/discussions.ts","../src/canvas/modules.ts","../src/canvas/pages.ts","../src/canvas/calendar.ts","../src/canvas/conversations.ts","../src/canvas/peer-reviews.ts","../src/canvas/accounts.ts","../src/canvas/analytics.ts","../src/canvas/dashboard.ts","../src/canvas/outcomes.ts","../src/canvas/gradebook-history.ts","../src/canvas/index.ts"],"names":["body"],"mappings":"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","file":"/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-6HRYCB4C.cjs","sourcesContent":[null,"import type { CanvasClientConfig, CanvasErrorResponse } from './types'\n\nexport class CanvasApiError extends Error {\n status: number\n endpoint: string\n\n constructor(message: string, status: number, endpoint: string) {\n super(message)\n this.name = 'CanvasApiError'\n this.status = status\n this.endpoint = endpoint\n }\n}\n\nconst DEFAULT_MAX_PAGINATION_PAGES = 1000\nconst USER_AGENT = 'canvas-lms-mcp/1.0'\n\nexport class CanvasHttpClient {\n private token: string\n private _baseUrl: string\n private maxPaginationPages: number\n\n constructor(config: CanvasClientConfig) {\n this.token = config.token\n this._baseUrl = config.baseUrl.replace(/\\/+$/, '')\n this.maxPaginationPages = config.maxPaginationPages ?? DEFAULT_MAX_PAGINATION_PAGES\n }\n\n get baseUrl(): string {\n return this._baseUrl\n }\n\n /**\n * Makes a single authenticated request to the Canvas API.\n * Returns `undefined` (typed as `T`) when Canvas responds with 204 No Content,\n * which is the expected response for DELETE operations.\n */\n async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = endpoint.startsWith('http') ? endpoint : `${this._baseUrl}${endpoint}`\n\n const method = (options.method ?? 'GET').toUpperCase()\n if (options.body != null && (method === 'GET' || method === 'HEAD')) {\n throw new Error(\n `GET requests must not include a body (Canvas CloudFront CDN rejects them with 403): ${endpoint}`,\n )\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n }\n if (options.body) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await fetch(url, {\n ...options,\n headers: {\n ...headers,\n ...options.headers,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n if (response.status === 204) {\n return undefined as T\n }\n\n return response.json() as Promise<T>\n }\n\n async paginate<T>(endpoint: string, params?: Record<string, string>): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n url.searchParams.set('per_page', '100')\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value)\n }\n }\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const data = (await response.json()) as T[]\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n async paginateEnvelope<T>(\n endpoint: string,\n envelopeKey: string,\n params?: Record<string, string>,\n ): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n url.searchParams.set('per_page', '100')\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value)\n }\n }\n\n const results: T[] = []\n let nextUrl: string | null = url.toString()\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n },\n })\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as CanvasErrorResponse\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n throw new CanvasApiError(message, response.status, endpoint)\n }\n\n const body = (await response.json()) as Record<string, T[]>\n const data = body[envelopeKey] ?? []\n results.push(...data)\n pages++\n\n nextUrl = this.parseNextLink(response.headers.get('Link'))\n }\n\n return results\n }\n\n private parseNextLink(linkHeader: string | null): string | null {\n if (!linkHeader) return null\n const match = linkHeader.match(/<([^>]+)>;\\s*rel=\"next\"/)\n return match?.[1] ?? null\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasCourse, CreateCourseParams, UpdateCourseParams } from './types'\n\nexport class CoursesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(params?: { enrollment_state?: string }): Promise<CanvasCourse[]> {\n const queryParams: Record<string, string> = {\n 'include[]': 'term',\n }\n if (params?.enrollment_state) {\n queryParams.enrollment_state = params.enrollment_state\n }\n return this.client.paginate<CanvasCourse>('/api/v1/courses', queryParams)\n }\n\n async get(courseId: number): Promise<CanvasCourse> {\n return this.client.request<CanvasCourse>(\n `/api/v1/courses/${courseId}?include[]=term&include[]=total_students`,\n )\n }\n\n async getSyllabus(courseId: number): Promise<string | null> {\n const course = await this.client.request<CanvasCourse>(\n `/api/v1/courses/${courseId}?include[]=syllabus_body`,\n )\n return course.syllabus_body ?? null\n }\n\n async create(params: CreateCourseParams): Promise<CanvasCourse> {\n const { account_id, ...courseFields } = params\n return this.client.request<CanvasCourse>(`/api/v1/accounts/${account_id}/courses`, {\n method: 'POST',\n body: JSON.stringify({ course: courseFields }),\n })\n }\n\n async update(courseId: number, params: UpdateCourseParams): Promise<CanvasCourse> {\n return this.client.request<CanvasCourse>(`/api/v1/courses/${courseId}`, {\n method: 'PUT',\n body: JSON.stringify({ course: params }),\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasAssignment,\n CanvasAssignmentGroup,\n CreateAssignmentParams,\n UpdateAssignmentParams,\n} from './types'\n\nexport class AssignmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasAssignment[]> {\n return this.client.paginate<CanvasAssignment>(`/api/v1/courses/${courseId}/assignments`)\n }\n\n async get(courseId: number, assignmentId: number): Promise<CanvasAssignment> {\n return this.client.request<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}`,\n )\n }\n\n async listGroups(courseId: number): Promise<CanvasAssignmentGroup[]> {\n return this.client.paginate<CanvasAssignmentGroup>(\n `/api/v1/courses/${courseId}/assignment_groups`,\n )\n }\n\n async create(courseId: number, params: CreateAssignmentParams): Promise<CanvasAssignment> {\n return this.client.request<CanvasAssignment>(`/api/v1/courses/${courseId}/assignments`, {\n method: 'POST',\n body: JSON.stringify({ assignment: params }),\n })\n }\n\n async update(\n courseId: number,\n assignmentId: number,\n params: UpdateAssignmentParams,\n ): Promise<CanvasAssignment> {\n return this.client.request<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}`,\n {\n method: 'PUT',\n body: JSON.stringify({ assignment: params }),\n },\n )\n }\n\n async delete(courseId: number, assignmentId: number): Promise<void> {\n await this.client.request<void>(`/api/v1/courses/${courseId}/assignments/${assignmentId}`, {\n method: 'DELETE',\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasSubmission } from './types'\n\nexport class SubmissionsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number, assignmentId: number): Promise<CanvasSubmission[]> {\n return this.client.paginate<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions`,\n { 'include[]': 'submission_comments' },\n )\n }\n\n async get(courseId: number, assignmentId: number, userId: number): Promise<CanvasSubmission> {\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}?include[]=submission_comments`,\n )\n }\n\n async grade(\n courseId: number,\n assignmentId: number,\n userId: number,\n grade: string,\n ): Promise<CanvasSubmission> {\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`,\n {\n method: 'PUT',\n body: JSON.stringify({ submission: { posted_grade: grade } }),\n },\n )\n }\n\n async comment(\n courseId: number,\n assignmentId: number,\n userId: number,\n comment: string,\n ): Promise<CanvasSubmission> {\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`,\n {\n method: 'PUT',\n body: JSON.stringify({\n comment: { text_comment: comment },\n }),\n },\n )\n }\n\n async listMy(courseId: number): Promise<CanvasSubmission[]> {\n return this.client.paginate<CanvasSubmission>(\n `/api/v1/courses/${courseId}/students/submissions`,\n { 'student_ids[]': 'self' },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasRubric, CanvasRubricAssessment, CanvasSubmission } from './types'\n\nexport class RubricsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasRubric[]> {\n return this.client.paginate<CanvasRubric>(`/api/v1/courses/${courseId}/rubrics`)\n }\n\n async get(courseId: number, rubricId: number): Promise<CanvasRubric> {\n return this.client.request<CanvasRubric>(`/api/v1/courses/${courseId}/rubrics/${rubricId}`)\n }\n\n async getAssessment(\n courseId: number,\n assignmentId: number,\n userId: number,\n ): Promise<CanvasSubmission> {\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}?include[]=rubric_assessment`,\n )\n }\n\n async submitAssessment(\n courseId: number,\n associationId: number,\n data: CanvasRubricAssessment['data'],\n ): Promise<CanvasRubricAssessment> {\n return this.client.request<CanvasRubricAssessment>(\n `/api/v1/courses/${courseId}/rubric_associations/${associationId}/rubric_assessments`,\n {\n method: 'POST',\n body: JSON.stringify({ rubric_assessment: { data } }),\n },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasQuiz,\n CanvasQuizSubmission,\n CanvasQuizQuestion,\n CanvasQuizSubmissionQuestion,\n} from './types'\n\nexport class QuizzesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasQuiz[]> {\n return this.client.paginate<CanvasQuiz>(`/api/v1/courses/${courseId}/quizzes`)\n }\n\n async get(courseId: number, quizId: number): Promise<CanvasQuiz> {\n return this.client.request<CanvasQuiz>(`/api/v1/courses/${courseId}/quizzes/${quizId}`)\n }\n\n async listSubmissions(courseId: number, quizId: number): Promise<CanvasQuizSubmission[]> {\n return this.client.paginateEnvelope<CanvasQuizSubmission>(\n `/api/v1/courses/${courseId}/quizzes/${quizId}/submissions`,\n 'quiz_submissions',\n )\n }\n\n async listQuestions(courseId: number, quizId: number): Promise<CanvasQuizQuestion[]> {\n return this.client.paginate<CanvasQuizQuestion>(\n `/api/v1/courses/${courseId}/quizzes/${quizId}/questions`,\n )\n }\n\n async getSubmissionAnswers(quizSubmissionId: number): Promise<CanvasQuizSubmissionQuestion[]> {\n return this.client.paginateEnvelope<CanvasQuizSubmissionQuestion>(\n `/api/v1/quiz_submissions/${quizSubmissionId}/questions`,\n 'quiz_submission_questions',\n { 'include[]': 'quiz_question' },\n )\n }\n\n async scoreQuestion(\n courseId: number,\n quizId: number,\n submissionId: number,\n questionId: number,\n score: number,\n comment?: string,\n attempt?: number,\n ): Promise<void> {\n const submission: Record<string, unknown> = {\n questions: {\n [questionId]: { score, comment },\n },\n }\n if (attempt !== undefined) {\n submission.attempt = attempt\n }\n const body: Record<string, unknown> = {\n quiz_submissions: [submission],\n }\n await this.client.request(\n `/api/v1/courses/${courseId}/quizzes/${quizId}/submissions/${submissionId}`,\n {\n method: 'PUT',\n body: JSON.stringify(body),\n },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasFile, CanvasFileUploadInfo, CanvasFolder } from './types'\n\nexport class FilesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasFile[]> {\n return this.client.paginate<CanvasFile>(`/api/v1/courses/${courseId}/files`)\n }\n\n async listFolders(courseId: number): Promise<CanvasFolder[]> {\n return this.client.paginate<CanvasFolder>(`/api/v1/courses/${courseId}/folders`)\n }\n\n async get(courseId: number, fileId: number): Promise<CanvasFile> {\n return this.client.request<CanvasFile>(`/api/v1/courses/${courseId}/files/${fileId}`)\n }\n\n async upload(\n courseId: number,\n name: string,\n contentBase64: string,\n contentType: string,\n parentFolderPath?: string,\n ): Promise<CanvasFile> {\n const content = Buffer.from(contentBase64, 'base64')\n if (content.toString('base64') !== contentBase64) {\n throw new Error('Invalid base64 content: string is not valid base64 encoding')\n }\n\n // Step 1: Notify Canvas of the pending upload\n const uploadBody: Record<string, string> = {\n name,\n content_type: contentType,\n size: String(content.length),\n }\n if (parentFolderPath) {\n uploadBody.parent_folder_path = parentFolderPath\n }\n\n const uploadInfo = await this.client.request<CanvasFileUploadInfo>(\n `/api/v1/courses/${courseId}/files`,\n { method: 'POST', body: JSON.stringify(uploadBody) },\n )\n\n // Step 2: POST file to upload_url (may be S3 — must NOT add Authorization header)\n const form = new FormData()\n for (const [key, value] of Object.entries(uploadInfo.upload_params)) {\n form.append(key, value)\n }\n form.append('file', new Blob([content], { type: contentType }), name)\n\n // Use redirect: 'manual' so we can handle the 303 → Canvas confirm step ourselves\n let s3Response: Response\n try {\n s3Response = await fetch(uploadInfo.upload_url, {\n method: 'POST',\n body: form,\n redirect: 'manual',\n })\n } catch (networkErr) {\n let hostname: string\n try {\n hostname = new URL(uploadInfo.upload_url).hostname\n } catch {\n hostname = uploadInfo.upload_url\n }\n throw new Error(`Unable to reach file storage (${hostname})`, { cause: networkErr })\n }\n\n // Step 3: If S3 returned a redirect, POST to Canvas confirm URL\n if (s3Response.status >= 300 && s3Response.status < 400) {\n const confirmUrl = s3Response.headers.get('location')\n if (!confirmUrl) {\n throw new Error(`File upload redirect missing Location header (HTTP ${s3Response.status})`)\n }\n let confirmHostname: string\n try {\n confirmHostname = new URL(confirmUrl).hostname\n } catch {\n confirmHostname = ''\n }\n const canvasHostname = new URL(this.client.baseUrl).hostname\n if (confirmHostname !== canvasHostname) {\n throw new Error(\n `File upload redirect points to unexpected host (${confirmHostname}); expected Canvas host (${canvasHostname})`,\n )\n }\n return this.client.request<CanvasFile>(confirmUrl, { method: 'POST' })\n }\n\n if (!s3Response.ok) {\n const body = await s3Response.text()\n throw new Error(\n `File upload to storage failed (HTTP ${s3Response.status}): ${body.slice(0, 500)}`,\n )\n }\n\n const responseText = await s3Response.text()\n let parsed: unknown\n try {\n parsed = JSON.parse(responseText)\n } catch (parseErr) {\n throw new Error(\n `File upload response is not valid JSON (${parseErr instanceof Error ? parseErr.message : String(parseErr)}): ${responseText.slice(0, 500)}`,\n { cause: parseErr },\n )\n }\n if (typeof parsed !== 'object' || parsed === null || !('id' in parsed)) {\n throw new Error(\n `File upload returned unexpected response (no file ID): ${responseText.slice(0, 500)}`,\n )\n }\n return parsed as CanvasFile\n }\n\n async delete(fileId: number): Promise<CanvasFile> {\n return this.client.request<CanvasFile>(`/api/v1/files/${fileId}`, { method: 'DELETE' })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasUpcomingEvent, CanvasUser, CanvasUserProfile } from './types'\n\nexport class UsersModule {\n constructor(private client: CanvasHttpClient) {}\n\n async listStudents(courseId: number): Promise<CanvasUser[]> {\n return this.client.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, {\n 'enrollment_type[]': 'student',\n })\n }\n\n async get(userId: number): Promise<CanvasUser> {\n return this.client.request<CanvasUser>(`/api/v1/users/${userId}`)\n }\n\n async getProfile(): Promise<CanvasUserProfile> {\n return this.client.request<CanvasUserProfile>('/api/v1/users/self/profile')\n }\n\n async searchUsers(\n accountId: number,\n searchTerm: string,\n sort?: string,\n order?: string,\n ): Promise<CanvasUser[]> {\n const params: Record<string, string> = { search_term: searchTerm }\n if (sort) params.sort = sort\n if (order) params.order = order\n return this.client.paginate<CanvasUser>(`/api/v1/accounts/${accountId}/users`, params)\n }\n\n async listCourseUsers(courseId: number, enrollmentType?: string): Promise<CanvasUser[]> {\n const params: Record<string, string> = {}\n if (enrollmentType) params['enrollment_type[]'] = enrollmentType\n return this.client.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, params)\n }\n\n async getUpcomingAssignments(): Promise<CanvasUpcomingEvent[]> {\n return this.client.request<CanvasUpcomingEvent[]>(\n '/api/v1/users/self/upcoming_events?type=Assignment',\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasGroup, CanvasUser } from './types'\n\nexport class GroupsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasGroup[]> {\n return this.client.paginate<CanvasGroup>(`/api/v1/courses/${courseId}/groups`)\n }\n\n async listMembers(groupId: number): Promise<CanvasUser[]> {\n return this.client.paginate<CanvasUser>(`/api/v1/groups/${groupId}/users`)\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasEnrollment } from './types'\n\nexport class EnrollmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(): Promise<CanvasEnrollment[]> {\n return this.client.paginate<CanvasEnrollment>('/api/v1/users/self/enrollments')\n }\n\n async enroll(\n courseId: number,\n userId: number,\n type: string,\n enrollmentState?: string,\n ): Promise<CanvasEnrollment> {\n const body: Record<string, unknown> = {\n enrollment: {\n user_id: userId,\n type,\n ...(enrollmentState ? { enrollment_state: enrollmentState } : {}),\n },\n }\n return this.client.request<CanvasEnrollment>(`/api/v1/courses/${courseId}/enrollments`, {\n method: 'POST',\n body: JSON.stringify(body),\n })\n }\n\n async remove(courseId: number, enrollmentId: number, task: string): Promise<CanvasEnrollment> {\n return this.client.request<CanvasEnrollment>(\n `/api/v1/courses/${courseId}/enrollments/${enrollmentId}?task=${encodeURIComponent(task)}`,\n { method: 'DELETE' },\n )\n }\n\n async listMyGrades(courseId?: number): Promise<CanvasEnrollment[]> {\n if (courseId !== undefined) {\n return this.client.paginate<CanvasEnrollment>(`/api/v1/courses/${courseId}/enrollments`, {\n user_id: 'self',\n 'include[]': 'grades',\n })\n }\n return this.client.paginate<CanvasEnrollment>('/api/v1/users/self/enrollments', {\n 'include[]': 'grades',\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasDiscussionTopic,\n CanvasDiscussionEntry,\n CanvasAnnouncement,\n CreateDiscussionParams,\n UpdateDiscussionParams,\n} from './types'\n\nexport class DiscussionsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasDiscussionTopic[]> {\n return this.client.paginate<CanvasDiscussionTopic>(\n `/api/v1/courses/${courseId}/discussion_topics`,\n )\n }\n\n async get(courseId: number, topicId: number): Promise<CanvasDiscussionTopic> {\n return this.client.request<CanvasDiscussionTopic>(\n `/api/v1/courses/${courseId}/discussion_topics/${topicId}?include[]=all_dates`,\n )\n }\n\n async listAnnouncements(courseId: number): Promise<CanvasAnnouncement[]> {\n return this.client.paginate<CanvasAnnouncement>(\n `/api/v1/courses/${courseId}/discussion_topics`,\n { only_announcements: 'true' },\n )\n }\n\n async postEntry(\n courseId: number,\n topicId: number,\n message: string,\n ): Promise<CanvasDiscussionEntry> {\n return this.client.request<CanvasDiscussionEntry>(\n `/api/v1/courses/${courseId}/discussion_topics/${topicId}/entries`,\n {\n method: 'POST',\n body: JSON.stringify({ message }),\n },\n )\n }\n\n async create(courseId: number, params: CreateDiscussionParams): Promise<CanvasDiscussionTopic> {\n return this.client.request<CanvasDiscussionTopic>(\n `/api/v1/courses/${courseId}/discussion_topics`,\n {\n method: 'POST',\n body: JSON.stringify(params),\n },\n )\n }\n\n async update(\n courseId: number,\n topicId: number,\n params: UpdateDiscussionParams,\n ): Promise<CanvasDiscussionTopic> {\n return this.client.request<CanvasDiscussionTopic>(\n `/api/v1/courses/${courseId}/discussion_topics/${topicId}`,\n {\n method: 'PUT',\n body: JSON.stringify(params),\n },\n )\n }\n\n async delete(courseId: number, topicId: number): Promise<void> {\n await this.client.request<void>(`/api/v1/courses/${courseId}/discussion_topics/${topicId}`, {\n method: 'DELETE',\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasModule, CanvasModuleItem } from './types'\n\nexport class ModulesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasModule[]> {\n return this.client.paginate<CanvasModule>(`/api/v1/courses/${courseId}/modules`)\n }\n\n async get(courseId: number, moduleId: number): Promise<CanvasModule> {\n return this.client.request<CanvasModule>(`/api/v1/courses/${courseId}/modules/${moduleId}`)\n }\n\n async listItems(courseId: number, moduleId: number): Promise<CanvasModuleItem[]> {\n return this.client.paginate<CanvasModuleItem>(\n `/api/v1/courses/${courseId}/modules/${moduleId}/items`,\n )\n }\n\n async create(\n courseId: number,\n params: {\n name: string\n position?: number\n unlock_at?: string\n prerequisite_module_ids?: number[]\n },\n ): Promise<CanvasModule> {\n return this.client.request<CanvasModule>(`/api/v1/courses/${courseId}/modules`, {\n method: 'POST',\n body: JSON.stringify({ module: params }),\n })\n }\n\n async update(\n courseId: number,\n moduleId: number,\n params: { name?: string; position?: number; published?: boolean },\n ): Promise<CanvasModule> {\n return this.client.request<CanvasModule>(`/api/v1/courses/${courseId}/modules/${moduleId}`, {\n method: 'PUT',\n body: JSON.stringify({ module: params }),\n })\n }\n\n async createItem(\n courseId: number,\n moduleId: number,\n params: {\n title: string\n type: string\n content_id?: number\n external_url?: string\n position?: number\n },\n ): Promise<CanvasModuleItem> {\n return this.client.request<CanvasModuleItem>(\n `/api/v1/courses/${courseId}/modules/${moduleId}/items`,\n {\n method: 'POST',\n body: JSON.stringify({ module_item: params }),\n },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasPage } from './types'\n\nexport class PagesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasPage[]> {\n return this.client.paginate<CanvasPage>(`/api/v1/courses/${courseId}/pages`)\n }\n\n async get(courseId: number, pageUrl: string): Promise<CanvasPage> {\n return this.client.request<CanvasPage>(\n `/api/v1/courses/${courseId}/pages/${encodeURIComponent(pageUrl)}`,\n )\n }\n\n async create(\n courseId: number,\n params: { title: string; body?: string; published?: boolean; editing_roles?: string },\n ): Promise<CanvasPage> {\n return this.client.request<CanvasPage>(`/api/v1/courses/${courseId}/pages`, {\n method: 'POST',\n body: JSON.stringify({ wiki_page: params }),\n })\n }\n\n async update(\n courseId: number,\n pageUrl: string,\n params: { title?: string; body?: string; published?: boolean; editing_roles?: string },\n ): Promise<CanvasPage> {\n return this.client.request<CanvasPage>(\n `/api/v1/courses/${courseId}/pages/${encodeURIComponent(pageUrl)}`,\n {\n method: 'PUT',\n body: JSON.stringify({ wiki_page: params }),\n },\n )\n }\n\n async delete(courseId: number, pageUrl: string): Promise<void> {\n await this.client.request<void>(\n `/api/v1/courses/${courseId}/pages/${encodeURIComponent(pageUrl)}`,\n { method: 'DELETE' },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasCalendarEvent } from './types'\n\nexport class CalendarModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number): Promise<CanvasCalendarEvent[]> {\n return this.client.paginate<CanvasCalendarEvent>('/api/v1/calendar_events', {\n 'context_codes[]': `course_${courseId}`,\n })\n }\n\n async createEvent(params: {\n context_code: string\n title: string\n start_at: string\n end_at?: string\n description?: string\n location_name?: string\n }): Promise<CanvasCalendarEvent> {\n return this.client.request<CanvasCalendarEvent>('/api/v1/calendar_events', {\n method: 'POST',\n body: JSON.stringify({ calendar_event: params }),\n })\n }\n\n async updateEvent(\n eventId: number,\n params: {\n title?: string\n start_at?: string\n end_at?: string\n description?: string\n location_name?: string\n },\n ): Promise<CanvasCalendarEvent> {\n return this.client.request<CanvasCalendarEvent>(`/api/v1/calendar_events/${eventId}`, {\n method: 'PUT',\n body: JSON.stringify({ calendar_event: params }),\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasConversation,\n CanvasConversationDetail,\n CanvasConversationUnreadCount,\n} from './types'\n\nexport class ConversationsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(): Promise<CanvasConversation[]> {\n return this.client.paginate<CanvasConversation>('/api/v1/conversations')\n }\n\n async get(conversationId: number): Promise<CanvasConversationDetail> {\n return this.client.request<CanvasConversationDetail>(`/api/v1/conversations/${conversationId}`)\n }\n\n async getUnreadCount(): Promise<CanvasConversationUnreadCount> {\n const raw = await this.client.request<{ unread_count: string }>(\n '/api/v1/conversations/unread_count',\n )\n const count = parseInt(raw.unread_count, 10)\n if (Number.isNaN(count)) {\n throw new Error(\n `Canvas returned unexpected unread_count value: ${JSON.stringify(raw.unread_count)}`,\n )\n }\n return { unread_count: count }\n }\n\n async send(recipients: string[], subject: string, body: string): Promise<CanvasConversation[]> {\n return this.client.request<CanvasConversation[]>('/api/v1/conversations', {\n method: 'POST',\n body: JSON.stringify({\n recipients,\n subject,\n body,\n }),\n })\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasPeerReview } from './types'\n\nexport class PeerReviewsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async listForAssignment(courseId: number, assignmentId: number): Promise<CanvasPeerReview[]> {\n return this.client.paginate<CanvasPeerReview>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/peer_reviews`,\n )\n }\n\n async listForSubmission(\n courseId: number,\n assignmentId: number,\n submissionId: number,\n ): Promise<CanvasPeerReview[]> {\n return this.client.paginate<CanvasPeerReview>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${submissionId}/peer_reviews`,\n )\n }\n\n async create(\n courseId: number,\n assignmentId: number,\n submissionId: number,\n userId: number,\n ): Promise<CanvasPeerReview> {\n return this.client.request<CanvasPeerReview>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${submissionId}/peer_reviews`,\n {\n method: 'POST',\n body: JSON.stringify({ user_id: userId }),\n },\n )\n }\n\n async delete(\n courseId: number,\n assignmentId: number,\n submissionId: number,\n userId: number,\n ): Promise<void> {\n await this.client.request<void>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${submissionId}/peer_reviews?user_id=${userId}`,\n { method: 'DELETE' },\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type { CanvasAccount, CanvasAccountReport, CanvasCourse, CanvasUser } from './types'\n\nexport class AccountsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async get(accountId: number): Promise<CanvasAccount> {\n return this.client.request<CanvasAccount>(`/api/v1/accounts/${accountId}`)\n }\n\n async list(): Promise<CanvasAccount[]> {\n return this.client.paginate<CanvasAccount>('/api/v1/accounts')\n }\n\n async listSubAccounts(accountId: number): Promise<CanvasAccount[]> {\n return this.client.paginate<CanvasAccount>(`/api/v1/accounts/${accountId}/sub_accounts`)\n }\n\n async listCourses(accountId: number, params?: { search_term?: string }): Promise<CanvasCourse[]> {\n const query: Record<string, string> = {}\n if (params?.search_term) query.search_term = params.search_term\n return this.client.paginate<CanvasCourse>(\n `/api/v1/accounts/${accountId}/courses`,\n Object.keys(query).length ? query : undefined,\n )\n }\n\n async listUsers(accountId: number, params?: { search_term?: string }): Promise<CanvasUser[]> {\n const query: Record<string, string> = {}\n if (params?.search_term) query.search_term = params.search_term\n return this.client.paginate<CanvasUser>(\n `/api/v1/accounts/${accountId}/users`,\n Object.keys(query).length ? query : undefined,\n )\n }\n\n async getReports(accountId: number): Promise<CanvasAccountReport[]> {\n return this.client.request<CanvasAccountReport[]>(`/api/v1/accounts/${accountId}/reports`)\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasCourseActivitySummary,\n CanvasStudentActivitySummary,\n CanvasActivityStreamItem,\n CourseSearchResult,\n} from './types'\n\nexport const SEARCH_CONTENT_TYPES = [\n 'pages',\n 'discussions',\n 'assignments',\n 'announcements',\n] as const\nexport type SearchContentType = (typeof SEARCH_CONTENT_TYPES)[number]\n\nexport class AnalyticsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async searchContentType(\n courseId: number,\n searchTerm: string,\n type: SearchContentType,\n ): Promise<CourseSearchResult[]> {\n switch (type) {\n case 'pages': {\n const pages = await this.client.paginate<{ page_id: number; title: string; url: string }>(\n `/api/v1/courses/${courseId}/pages`,\n { search_term: searchTerm },\n )\n return pages.map((p) => ({\n id: p.page_id,\n title: p.title,\n type: 'page' as const,\n url: p.url,\n course_id: courseId,\n }))\n }\n case 'assignments': {\n const assignments = await this.client.paginate<{ id: number; name: string }>(\n `/api/v1/courses/${courseId}/assignments`,\n { search_term: searchTerm },\n )\n return assignments.map((a) => ({\n id: a.id,\n title: a.name,\n type: 'assignment' as const,\n course_id: courseId,\n }))\n }\n case 'discussions': {\n const discussions = await this.client.paginate<{ id: number; title: string }>(\n `/api/v1/courses/${courseId}/discussion_topics`,\n { search_term: searchTerm },\n )\n return discussions.map((d) => ({\n id: d.id,\n title: d.title,\n type: 'discussion' as const,\n course_id: courseId,\n }))\n }\n case 'announcements': {\n const announcements = await this.client.paginate<{ id: number; title: string }>(\n `/api/v1/courses/${courseId}/discussion_topics`,\n { search_term: searchTerm, only_announcements: 'true' },\n )\n return announcements.map((a) => ({\n id: a.id,\n title: a.title,\n type: 'announcement' as const,\n course_id: courseId,\n }))\n }\n default: {\n const _exhaustive: never = type\n throw new Error(`Unknown content type: ${_exhaustive}`)\n }\n }\n }\n\n async getCourseActivity(courseId: number): Promise<CanvasCourseActivitySummary[]> {\n return this.client.request<CanvasCourseActivitySummary[]>(\n `/api/v1/courses/${courseId}/analytics/activity`,\n )\n }\n\n async getStudentActivity(\n courseId: number,\n studentId: number,\n ): Promise<CanvasStudentActivitySummary> {\n return this.client.request<CanvasStudentActivitySummary>(\n `/api/v1/courses/${courseId}/analytics/users/${studentId}/activity`,\n )\n }\n\n async getCourseActivityStream(courseId: number): Promise<CanvasActivityStreamItem[]> {\n return this.client.request<CanvasActivityStreamItem[]>(\n `/api/v1/courses/${courseId}/activity_stream/summary`,\n )\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasDashboardCard,\n CanvasTodoItem,\n CanvasUpcomingEvent,\n CanvasMissingSubmission,\n} from './types'\n\nexport class DashboardModule {\n constructor(private client: CanvasHttpClient) {}\n\n async getDashboardCards(): Promise<CanvasDashboardCard[]> {\n return this.client.request<CanvasDashboardCard[]>('/api/v1/dashboard/dashboard_cards')\n }\n\n async getTodoItems(): Promise<CanvasTodoItem[]> {\n return this.client.paginate<CanvasTodoItem>('/api/v1/users/self/todo')\n }\n\n async getUpcomingEvents(): Promise<CanvasUpcomingEvent[]> {\n return this.client.request<CanvasUpcomingEvent[]>('/api/v1/users/self/upcoming_events')\n }\n\n async getMissingSubmissions(): Promise<CanvasMissingSubmission[]> {\n return this.client.paginate<CanvasMissingSubmission>('/api/v1/users/self/missing_submissions')\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasOutcomeContextType,\n CanvasOutcome,\n CanvasOutcomeAlignment,\n CanvasOutcomeContributingScoresResponse,\n CanvasOutcomeGroup,\n CanvasOutcomeLink,\n CanvasOutcomeMasteryDistributionResponse,\n CanvasOutcomeResultsResponse,\n CanvasOutcomeRollupsResponse,\n} from './types'\n\nexport const OUTCOME_CONTEXT_TYPES = ['account', 'course'] as const\nexport const OUTCOME_DETAIL_LEVELS = ['abbrev', 'full'] as const\nexport const OUTCOME_ROLLUP_AGGREGATE_STATS = ['mean', 'median'] as const\nexport const OUTCOME_ROLLUP_SORT_BY = ['student', 'outcome'] as const\nexport const OUTCOME_SORT_ORDER = ['asc', 'desc'] as const\nexport const OUTCOME_EXCLUDE_OPTIONS = ['missing_user_rollups', 'missing_outcome_results'] as const\n\ntype ContextPathSuffix = 'outcome_groups' | 'outcome_group_links' | 'root_outcome_group'\n\nexport class OutcomesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async getRootOutcomeGroup(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n ): Promise<CanvasOutcomeGroup> {\n return this.client.request<CanvasOutcomeGroup>(this.buildContextPath(contextType, contextId))\n }\n\n async listOutcomeGroups(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n ): Promise<CanvasOutcomeGroup[]> {\n return this.client.paginate<CanvasOutcomeGroup>(\n this.buildContextPath(contextType, contextId, 'outcome_groups'),\n )\n }\n\n async listOutcomeGroupLinks(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n options?: { outcome_style?: 'abbrev' | 'full'; outcome_group_style?: 'abbrev' | 'full' },\n ): Promise<CanvasOutcomeLink[]> {\n return this.client.paginate<CanvasOutcomeLink>(\n this.withQuery(this.buildContextPath(contextType, contextId, 'outcome_group_links'), {\n outcome_style: options?.outcome_style,\n outcome_group_style: options?.outcome_group_style,\n }),\n )\n }\n\n async getOutcomeGroup(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n groupId: number,\n ): Promise<CanvasOutcomeGroup> {\n return this.client.request<CanvasOutcomeGroup>(\n `${this.buildContextPath(contextType, contextId, 'outcome_groups')}/${groupId}`,\n )\n }\n\n async listGroupOutcomes(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n groupId: number,\n options?: { outcome_style?: 'abbrev' | 'full' },\n ): Promise<CanvasOutcomeLink[]> {\n return this.client.paginate<CanvasOutcomeLink>(\n this.withQuery(\n `${this.buildContextPath(contextType, contextId, 'outcome_groups')}/${groupId}/outcomes`,\n { outcome_style: options?.outcome_style },\n ),\n )\n }\n\n async listGroupSubgroups(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n groupId: number,\n ): Promise<CanvasOutcomeGroup[]> {\n return this.client.paginate<CanvasOutcomeGroup>(\n `${this.buildContextPath(contextType, contextId, 'outcome_groups')}/${groupId}/subgroups`,\n )\n }\n\n async getOutcome(\n outcomeId: number,\n options?: { add_defaults?: boolean },\n ): Promise<CanvasOutcome> {\n return this.client.request<CanvasOutcome>(\n this.withQuery(`/api/v1/outcomes/${outcomeId}`, {\n add_defaults: options?.add_defaults,\n }),\n )\n }\n\n async getOutcomeAlignments(\n courseId: number,\n options?: { student_id?: number; assignment_id?: number },\n ): Promise<CanvasOutcomeAlignment[]> {\n return this.client.request<CanvasOutcomeAlignment[]>(\n this.withQuery(`/api/v1/courses/${courseId}/outcome_alignments`, {\n student_id: options?.student_id,\n assignment_id: options?.assignment_id,\n }),\n )\n }\n\n async getOutcomeResults(\n courseId: number,\n options?: {\n user_ids?: Array<number | string>\n outcome_ids?: number[]\n include_alignments?: boolean\n include_hidden?: boolean\n },\n ): Promise<CanvasOutcomeResultsResponse> {\n return this.client.request<CanvasOutcomeResultsResponse>(\n this.withQuery(`/api/v1/courses/${courseId}/outcome_results`, {\n 'user_ids[]': options?.user_ids,\n 'outcome_ids[]': options?.outcome_ids,\n 'include[]': options?.include_alignments ? ['alignments'] : undefined,\n include_hidden: options?.include_hidden,\n }),\n )\n }\n\n async getOutcomeRollups(\n courseId: number,\n options?: {\n aggregate?: 'course'\n aggregate_stat?: 'mean' | 'median'\n user_ids?: Array<number | string>\n outcome_ids?: number[]\n include_courses?: boolean\n exclude?: Array<'missing_user_rollups' | 'missing_outcome_results'>\n sort_by?: 'student' | 'outcome'\n sort_outcome_id?: number\n sort_order?: 'asc' | 'desc'\n add_defaults?: boolean\n },\n ): Promise<CanvasOutcomeRollupsResponse> {\n return this.client.request<CanvasOutcomeRollupsResponse>(\n this.withQuery(`/api/v1/courses/${courseId}/outcome_rollups`, {\n aggregate: options?.aggregate,\n aggregate_stat: options?.aggregate_stat,\n 'user_ids[]': options?.user_ids,\n 'outcome_ids[]': options?.outcome_ids,\n 'include[]': options?.include_courses ? ['courses'] : undefined,\n 'exclude[]': options?.exclude,\n sort_by: options?.sort_by,\n sort_outcome_id: options?.sort_outcome_id,\n sort_order: options?.sort_order,\n add_defaults: options?.add_defaults,\n }),\n )\n }\n\n async getOutcomeContributingScores(\n courseId: number,\n outcomeId: number,\n options?: {\n user_ids?: Array<number | string>\n only_assignment_alignments?: boolean\n show_unpublished_assignments?: boolean\n },\n ): Promise<CanvasOutcomeContributingScoresResponse> {\n return this.client.request<CanvasOutcomeContributingScoresResponse>(\n this.withQuery(`/api/v1/courses/${courseId}/outcomes/${outcomeId}/contributing_scores`, {\n 'user_ids[]': options?.user_ids,\n only_assignment_alignments: options?.only_assignment_alignments,\n show_unpublished_assignments: options?.show_unpublished_assignments,\n }),\n )\n }\n\n async getOutcomeMasteryDistribution(\n courseId: number,\n options?: {\n exclude?: Array<'missing_user_rollups' | 'missing_outcome_results'>\n outcome_ids?: number[]\n student_ids?: Array<number | string>\n include_alignment_distributions?: boolean\n only_assignment_alignments?: boolean\n show_unpublished_assignments?: boolean\n add_defaults?: boolean\n },\n ): Promise<CanvasOutcomeMasteryDistributionResponse> {\n return this.client.request<CanvasOutcomeMasteryDistributionResponse>(\n this.withQuery(`/api/v1/courses/${courseId}/outcome_mastery_distribution`, {\n 'exclude[]': options?.exclude,\n 'outcome_ids[]': options?.outcome_ids,\n 'student_ids[]': options?.student_ids,\n 'include[]': options?.include_alignment_distributions\n ? ['alignment_distributions']\n : undefined,\n only_assignment_alignments: options?.only_assignment_alignments,\n show_unpublished_assignments: options?.show_unpublished_assignments,\n add_defaults: options?.add_defaults,\n }),\n )\n }\n\n private buildContextPath(\n contextType: CanvasOutcomeContextType,\n contextId: number,\n suffix: ContextPathSuffix = 'root_outcome_group',\n ): string {\n const segment = contextType === 'account' ? 'accounts' : 'courses'\n return `/api/v1/${segment}/${contextId}/${suffix}`\n }\n\n private withQuery(\n endpoint: string,\n params: Record<string, string | number | boolean | Array<string | number> | undefined>,\n ): string {\n const searchParams = new URLSearchParams()\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n for (const item of value) searchParams.append(key, String(item))\n continue\n }\n searchParams.set(key, String(value))\n }\n\n const query = searchParams.toString()\n return query ? `${endpoint}?${query}` : endpoint\n }\n}\n","import type { CanvasHttpClient } from './client'\nimport type {\n CanvasGradebookHistoryDay,\n CanvasGradebookHistoryGrader,\n CanvasGradebookHistorySubmission,\n CanvasGradebookHistorySubmissionVersion,\n} from './types'\n\nexport interface GradebookHistoryFeedParams {\n assignment_id?: number\n user_id?: number\n ascending?: boolean\n}\n\nexport class GradebookHistoryModule {\n constructor(private client: CanvasHttpClient) {}\n\n async listDays(courseId: number): Promise<CanvasGradebookHistoryDay[]> {\n return this.client.request<CanvasGradebookHistoryDay[]>(\n `/api/v1/courses/${courseId}/gradebook_history/days`,\n )\n }\n\n async getDay(courseId: number, date: string): Promise<CanvasGradebookHistoryGrader[]> {\n return this.client.request<CanvasGradebookHistoryGrader[]>(\n `/api/v1/courses/${courseId}/gradebook_history/${encodeURIComponent(date)}`,\n )\n }\n\n async listSubmissions(\n courseId: number,\n date: string,\n graderId: number,\n assignmentId: number,\n ): Promise<CanvasGradebookHistorySubmission[]> {\n return this.client.request<CanvasGradebookHistorySubmission[]>(\n `/api/v1/courses/${courseId}/gradebook_history/${encodeURIComponent(date)}/graders/${graderId}/assignments/${assignmentId}/submissions`,\n )\n }\n\n async getFeed(\n courseId: number,\n params: GradebookHistoryFeedParams = {},\n ): Promise<CanvasGradebookHistorySubmissionVersion[]> {\n const searchParams = new URLSearchParams()\n\n if (params.assignment_id !== undefined) {\n searchParams.set('assignment_id', String(params.assignment_id))\n }\n if (params.user_id !== undefined) {\n searchParams.set('user_id', String(params.user_id))\n }\n if (params.ascending !== undefined) {\n searchParams.set('ascending', String(params.ascending))\n }\n\n const query = searchParams.toString()\n\n return this.client.paginate<CanvasGradebookHistorySubmissionVersion>(\n `/api/v1/courses/${courseId}/gradebook_history/feed${query ? `?${query}` : ''}`,\n )\n }\n}\n","import { CanvasHttpClient } from './client'\nimport type { CanvasClientConfig } from './types'\nimport { CoursesModule } from './courses'\nimport { AssignmentsModule } from './assignments'\nimport { SubmissionsModule } from './submissions'\nimport { RubricsModule } from './rubrics'\nimport { QuizzesModule } from './quizzes'\nimport { FilesModule } from './files'\nimport { UsersModule } from './users'\nimport { GroupsModule } from './groups'\nimport { EnrollmentsModule } from './enrollments'\nimport { DiscussionsModule } from './discussions'\nimport { ModulesModule } from './modules'\nimport { PagesModule } from './pages'\nimport { CalendarModule } from './calendar'\nimport { ConversationsModule } from './conversations'\nimport { PeerReviewsModule } from './peer-reviews'\nimport { AccountsModule } from './accounts'\nimport { AnalyticsModule } from './analytics'\nimport { DashboardModule } from './dashboard'\nimport { OutcomesModule } from './outcomes'\nimport { GradebookHistoryModule } from './gradebook-history'\n\nexport class CanvasClient {\n private client: CanvasHttpClient\n courses: CoursesModule\n assignments: AssignmentsModule\n submissions: SubmissionsModule\n rubrics: RubricsModule\n quizzes: QuizzesModule\n files: FilesModule\n users: UsersModule\n groups: GroupsModule\n enrollments: EnrollmentsModule\n discussions: DiscussionsModule\n modules: ModulesModule\n pages: PagesModule\n calendar: CalendarModule\n conversations: ConversationsModule\n peerReviews: PeerReviewsModule\n accounts: AccountsModule\n analytics: AnalyticsModule\n dashboard: DashboardModule\n outcomes: OutcomesModule\n gradebookHistory: GradebookHistoryModule\n\n constructor(config: CanvasClientConfig) {\n this.client = new CanvasHttpClient(config)\n this.courses = new CoursesModule(this.client)\n this.assignments = new AssignmentsModule(this.client)\n this.submissions = new SubmissionsModule(this.client)\n this.rubrics = new RubricsModule(this.client)\n this.quizzes = new QuizzesModule(this.client)\n this.files = new FilesModule(this.client)\n this.users = new UsersModule(this.client)\n this.groups = new GroupsModule(this.client)\n this.enrollments = new EnrollmentsModule(this.client)\n this.discussions = new DiscussionsModule(this.client)\n this.modules = new ModulesModule(this.client)\n this.pages = new PagesModule(this.client)\n this.calendar = new CalendarModule(this.client)\n this.conversations = new ConversationsModule(this.client)\n this.peerReviews = new PeerReviewsModule(this.client)\n this.accounts = new AccountsModule(this.client)\n this.analytics = new AnalyticsModule(this.client)\n this.dashboard = new DashboardModule(this.client)\n this.outcomes = new OutcomesModule(this.client)\n this.gradebookHistory = new GradebookHistoryModule(this.client)\n }\n}\n\nexport { CanvasHttpClient, CanvasApiError } from './client'\nexport type * from './types'\n"]}