canvas-lms-mcp 1.8.1 → 1.9.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
@@ -158,6 +158,22 @@ The MCP endpoint is `http://localhost:3001/mcp`. Per-request credentials can be
158
158
 
159
159
  </details>
160
160
 
161
+ ## Agent Skills
162
+
163
+ Install reusable Canvas workflows into Claude Code, Cursor, GitHub Copilot, Cline, and 40+ other AI agents:
164
+
165
+ ```bash
166
+ npx skills add bruchris/canvas-lms-mcp
167
+ ```
168
+
169
+ | Skill | Description |
170
+ |-------|-------------|
171
+ | `canvas-at-risk-students` | Surface students with missing assignments or declining grades and send targeted outreach |
172
+ | `canvas-gradebook-audit` | Inspect the full grade-change audit trail — who changed what grade, when, and by how much |
173
+ | `canvas-outcome-tracker` | Track learning outcome mastery and class-wide proficiency for accreditation and program review |
174
+
175
+ Skills are markdown workflow files (no extra dependencies). They work with the MCP server you already have installed. See the [`skills/` directory](./skills/) for the full list.
176
+
161
177
  ## Example Prompts
162
178
 
163
179
  Once configured, try these prompts with your AI client:
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkFMOGHWSXcjs = require('../chunk-FMOGHWSX.cjs');
8
+ var _chunk3JL6Z5IDcjs = require('../chunk-3JL6Z5ID.cjs');
9
9
 
10
10
 
11
11
 
@@ -13,5 +13,5 @@ var _chunkFMOGHWSXcjs = require('../chunk-FMOGHWSX.cjs');
13
13
 
14
14
 
15
15
 
16
- exports.CanvasApiError = _chunkFMOGHWSXcjs.CanvasApiError; exports.CanvasClient = _chunkFMOGHWSXcjs.CanvasClient; exports.CanvasHttpClient = _chunkFMOGHWSXcjs.CanvasHttpClient; exports.GeneratedUsersModule = _chunkFMOGHWSXcjs.GeneratedUsersModule; exports.appendCanvasQuery = _chunkFMOGHWSXcjs.appendCanvasQuery; exports.toCanvasQuery = _chunkFMOGHWSXcjs.toCanvasQuery;
16
+ exports.CanvasApiError = _chunk3JL6Z5IDcjs.CanvasApiError; exports.CanvasClient = _chunk3JL6Z5IDcjs.CanvasClient; exports.CanvasHttpClient = _chunk3JL6Z5IDcjs.CanvasHttpClient; exports.GeneratedUsersModule = _chunk3JL6Z5IDcjs.GeneratedUsersModule; exports.appendCanvasQuery = _chunk3JL6Z5IDcjs.appendCanvasQuery; exports.toCanvasQuery = _chunk3JL6Z5IDcjs.toCanvasQuery;
17
17
  //# sourceMappingURL=index.cjs.map
@@ -5,7 +5,7 @@ import {
5
5
  GeneratedUsersModule,
6
6
  appendCanvasQuery,
7
7
  toCanvasQuery
8
- } from "../chunk-NGP4KCMC.js";
8
+ } from "../chunk-A6YFE3W7.js";
9
9
  export {
10
10
  CanvasApiError,
11
11
  CanvasClient,
@@ -1,5 +1,5 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// package.json
2
- var version = "1.8.1";
2
+ var version = "1.9.0";
3
3
 
4
4
  // src/canvas/query.ts
5
5
  function appendCanvasQuery(target, params) {
@@ -1383,4 +1383,4 @@ function isGeneratedEnabled(flag, domain) {
1383
1383
 
1384
1384
 
1385
1385
  exports.version = version; exports.appendCanvasQuery = appendCanvasQuery; exports.toCanvasQuery = toCanvasQuery; exports.CanvasApiError = CanvasApiError; exports.CanvasHttpClient = CanvasHttpClient; exports.GeneratedUsersModule = GeneratedUsersModule; 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;
1386
- //# sourceMappingURL=chunk-FMOGHWSX.cjs.map
1386
+ //# sourceMappingURL=chunk-3JL6Z5ID.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-FMOGHWSX.cjs","../package.json","../src/canvas/query.ts","../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/generated/users-client.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":["USER_AGENT"],"mappings":"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","file":"/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-FMOGHWSX.cjs","sourcesContent":[null,"{\n \"name\": \"canvas-lms-mcp\",\n \"version\": \"1.8.1\",\n \"mcpName\": \"io.github.bruchris/canvas-lms-mcp\",\n \"description\": \"TypeScript MCP 1.x server for Canvas LMS — 104 tools across Canvas courses, assignments, grades, gradebook history, quizzes, outcomes, discussions, admin workflows, and more.\",\n \"type\": \"module\",\n \"types\": \"./dist/server.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\",\n \"require\": \"./dist/server.cjs\"\n },\n \"./stdio\": {\n \"types\": \"./dist/stdio.d.ts\",\n \"import\": \"./dist/stdio.js\",\n \"require\": \"./dist/stdio.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/http.d.ts\",\n \"import\": \"./dist/http.js\",\n \"require\": \"./dist/http.cjs\"\n },\n \"./canvas\": {\n \"types\": \"./dist/canvas/index.d.ts\",\n \"import\": \"./dist/canvas/index.js\",\n \"require\": \"./dist/canvas/index.cjs\"\n }\n },\n \"bin\": {\n \"canvas-lms-mcp\": \"bin/canvas-lms-mcp.js\"\n },\n \"files\": [\n \"bin/\",\n \"dist/\"\n ],\n \"scripts\": {\n \"dev\": \"tsup --watch\",\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"generate:manifests\": \"tsx scripts/generate-manifests.ts\",\n \"canvas:spec:generate\": \"tsx scripts/canvas-spec/generate.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"eslint src/ tests/ && prettier --check src/ tests/\",\n \"lint:fix\": \"eslint src/ tests/ --fix && prettier --write src/ tests/\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"canvas\",\n \"canvas-lms\",\n \"lms\",\n \"mcp\",\n \"model-context-protocol\",\n \"education\",\n \"grading\",\n \"ai-agent\",\n \"ai-tools\",\n \"claude-desktop\",\n \"cursor\",\n \"vscode\",\n \"instructure\",\n \"education-technology\",\n \"edtech\",\n \"teacher\",\n \"student\",\n \"grading-automation\",\n \"typescript\"\n ],\n \"author\": \"Christian Bru\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/bruchris/canvas-lms-mcp.git\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@10.11.0\",\n \"pnpm\": {\n \"overrides\": {\n \"hono\": \"4.12.14\"\n }\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"openapi-fetch\": \"^0.17.0\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@types/node\": \"^25.6.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.59.0\",\n \"@typescript-eslint/parser\": \"^8.59.0\",\n \"@vitest/coverage-v8\": \"^4.1.5\",\n \"eslint\": \"^10.2.1\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-prettier\": \"^5.5.5\",\n \"openapi-typescript\": \"^7.13.0\",\n \"prettier\": \"^3.8.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.20.6\",\n \"typescript\": \"^6.0.3\",\n \"typescript-eslint\": \"^8.59.0\",\n \"vitest\": \"^4.1.5\",\n \"yaml\": \"^2.8.3\"\n }\n}\n","export type CanvasQueryPrimitive = string | number | boolean\n\nexport type CanvasQueryValue = CanvasQueryPrimitive | ReadonlyArray<CanvasQueryPrimitive>\n\nexport type CanvasQueryParams = Record<string, CanvasQueryValue | undefined | null>\n\nexport function appendCanvasQuery(target: URLSearchParams, params?: CanvasQueryParams): void {\n if (!params) return\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const arrayKey = key.endsWith('[]') ? key : `${key}[]`\n for (const item of value) {\n if (item === undefined || item === null) continue\n target.append(arrayKey, String(item))\n }\n } else {\n target.set(key, String(value))\n }\n }\n}\n\nexport function toCanvasQuery(params?: CanvasQueryParams): URLSearchParams {\n const search = new URLSearchParams()\n appendCanvasQuery(search, params)\n return search\n}\n","import { version } from '../../package.json'\nimport { appendCanvasQuery, type CanvasQueryParams } from './query'\nimport 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/${version}`\n\nexport interface CanvasRequestOptions extends RequestInit {\n /**\n * Query params to append to the endpoint URL. Arrays produce repeated\n * `key[]=v1&key[]=v2` entries (the convention Canvas uses for `include[]`).\n * Preserves any query string already present on the endpoint.\n */\n query?: CanvasQueryParams\n}\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: CanvasRequestOptions = {}): Promise<T> {\n const { query, ...init } = options\n let url = endpoint.startsWith('http') ? endpoint : `${this._baseUrl}${endpoint}`\n if (query) {\n const parsed = new URL(url)\n appendCanvasQuery(parsed.searchParams, query)\n url = parsed.toString()\n }\n\n const method = (init.method ?? 'GET').toUpperCase()\n if (init.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 (init.body) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await fetch(url, {\n ...init,\n headers: {\n ...headers,\n ...init.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?: CanvasQueryParams): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\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?: CanvasQueryParams,\n ): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\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 { CanvasQueryParams } from './query'\nimport type { CanvasCourse, CreateCourseParams, UpdateCourseParams } from './types'\n\nexport type CourseEnrollmentState = 'active' | 'invited_or_pending' | 'completed'\n\nexport type CourseWorkflowState = 'unpublished' | 'available' | 'completed' | 'deleted'\n\nexport type CourseListInclude =\n | 'needs_grading_count'\n | 'syllabus_body'\n | 'public_description'\n | 'total_scores'\n | 'current_grading_period_scores'\n | 'grading_periods'\n | 'term'\n | 'account'\n | 'course_progress'\n | 'sections'\n | 'storage_quota_used_mb'\n | 'total_students'\n | 'passback_status'\n | 'favorites'\n | 'teachers'\n | 'observed_users'\n | 'tabs'\n | 'course_image'\n | 'banner_image'\n | 'concluded'\n | 'lti_context_id'\n | 'post_manually'\n\nexport type CourseGetInclude =\n | CourseListInclude\n | 'all_courses'\n | 'permissions'\n | 'public_description'\n\nexport interface ListCoursesOptions {\n enrollment_state?: CourseEnrollmentState\n state?: ReadonlyArray<CourseWorkflowState>\n enrollment_role_id?: number\n include?: ReadonlyArray<CourseListInclude>\n exclude_blueprint_courses?: boolean\n}\n\nexport interface GetCourseOptions {\n include?: ReadonlyArray<CourseGetInclude>\n teacher_limit?: number\n}\n\nconst DEFAULT_LIST_INCLUDE: ReadonlyArray<CourseListInclude> = ['term']\nconst DEFAULT_GET_INCLUDE: ReadonlyArray<CourseGetInclude> = ['term', 'total_students']\n\nexport class CoursesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(opts: ListCoursesOptions = {}): Promise<CanvasCourse[]> {\n const params: CanvasQueryParams = {}\n params.include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_LIST_INCLUDE\n if (opts.enrollment_state) params.enrollment_state = opts.enrollment_state\n if (opts.state && opts.state.length > 0) params.state = opts.state\n if (opts.enrollment_role_id !== undefined) params.enrollment_role_id = opts.enrollment_role_id\n if (opts.exclude_blueprint_courses !== undefined) {\n params.exclude_blueprint_courses = opts.exclude_blueprint_courses\n }\n return this.client.paginate<CanvasCourse>('/api/v1/courses', params)\n }\n\n async get(courseId: number, opts: GetCourseOptions = {}): Promise<CanvasCourse> {\n const include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_GET_INCLUDE\n const query: CanvasQueryParams = { include }\n if (opts.teacher_limit !== undefined) query.teacher_limit = opts.teacher_limit\n return this.client.request<CanvasCourse>(`/api/v1/courses/${courseId}`, { query })\n }\n\n async getSyllabus(courseId: number): Promise<string | null> {\n const course = await this.client.request<CanvasCourse>(`/api/v1/courses/${courseId}`, {\n query: { 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 { CanvasQueryParams } from './query'\nimport type {\n CanvasAssignment,\n CanvasAssignmentGroup,\n CreateAssignmentParams,\n UpdateAssignmentParams,\n} from './types'\n\nexport type AssignmentListInclude =\n | 'submission'\n | 'assignment_visibility'\n | 'all_dates'\n | 'overrides'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n\nexport type AssignmentGetInclude =\n | 'submission'\n | 'assignment_visibility'\n | 'overrides'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n | 'all_dates'\n\nexport type AssignmentBucket =\n | 'past'\n | 'overdue'\n | 'undated'\n | 'ungraded'\n | 'unsubmitted'\n | 'upcoming'\n | 'future'\n\nexport type AssignmentOrderBy = 'position' | 'name' | 'due_at'\n\nexport type AssignmentGroupInclude =\n | 'assignments'\n | 'discussion_topic'\n | 'all_dates'\n | 'assignment_visibility'\n | 'overrides'\n | 'submission'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n\nexport interface ListAssignmentsOptions {\n include?: ReadonlyArray<AssignmentListInclude>\n search_term?: string\n override_assignment_dates?: boolean\n needs_grading_count_by_section?: boolean\n bucket?: AssignmentBucket\n assignment_ids?: ReadonlyArray<number>\n order_by?: AssignmentOrderBy\n post_to_sis?: boolean\n}\n\nexport interface GetAssignmentOptions {\n include?: ReadonlyArray<AssignmentGetInclude>\n override_assignment_dates?: boolean\n needs_grading_count_by_section?: boolean\n all_dates?: boolean\n}\n\nexport interface ListAssignmentGroupsOptions {\n include?: ReadonlyArray<AssignmentGroupInclude>\n assignment_ids?: ReadonlyArray<number>\n exclude_assignment_submission_types?: ReadonlyArray<string>\n override_assignment_dates?: boolean\n grading_period_id?: number\n scope_assignments_to_student?: boolean\n}\n\nfunction toQuery(opts: object): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n for (const [key, value] of Object.entries(opts)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n params[key] = value as ReadonlyArray<string | number | boolean>\n } else if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n params[key] = value\n }\n }\n return params\n}\n\nexport class AssignmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number, opts: ListAssignmentsOptions = {}): Promise<CanvasAssignment[]> {\n return this.client.paginate<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments`,\n toQuery(opts),\n )\n }\n\n async get(\n courseId: number,\n assignmentId: number,\n opts: GetAssignmentOptions = {},\n ): Promise<CanvasAssignment> {\n return this.client.request<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}`,\n { query: toQuery(opts) },\n )\n }\n\n async listGroups(\n courseId: number,\n opts: ListAssignmentGroupsOptions = {},\n ): Promise<CanvasAssignmentGroup[]> {\n return this.client.paginate<CanvasAssignmentGroup>(\n `/api/v1/courses/${courseId}/assignment_groups`,\n toQuery(opts),\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 { CanvasQueryParams } from './query'\nimport type { CanvasSubmission } from './types'\n\nexport type SubmissionListInclude =\n | 'submission_history'\n | 'submission_comments'\n | 'rubric_assessment'\n | 'assignment'\n | 'visibility'\n | 'course'\n | 'user'\n | 'group'\n | 'read_status'\n | 'sub_assignment_submissions'\n\nexport type SubmissionGetInclude =\n | 'submission_history'\n | 'submission_comments'\n | 'rubric_assessment'\n | 'visibility'\n | 'course'\n | 'user'\n | 'read_status'\n\nexport type SubmissionWorkflowState = 'submitted' | 'unsubmitted' | 'graded' | 'pending_review'\n\nexport interface ListSubmissionsOptions {\n include?: ReadonlyArray<SubmissionListInclude>\n student_ids?: ReadonlyArray<number | string>\n assignment_ids?: ReadonlyArray<number>\n section_ids?: ReadonlyArray<number>\n grouped?: boolean\n workflow_state?: SubmissionWorkflowState\n grading_period_id?: number\n post_to_sis?: boolean\n submitted_since?: string\n graded_since?: string\n}\n\nexport interface GetSubmissionOptions {\n include?: ReadonlyArray<SubmissionGetInclude>\n}\n\nconst DEFAULT_LIST_INCLUDE: ReadonlyArray<SubmissionListInclude> = ['submission_comments']\nconst DEFAULT_GET_INCLUDE: ReadonlyArray<SubmissionGetInclude> = ['submission_comments']\n\nfunction buildListParams(opts: ListSubmissionsOptions): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n params.include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_LIST_INCLUDE\n if (opts.student_ids && opts.student_ids.length > 0) params.student_ids = opts.student_ids\n if (opts.assignment_ids && opts.assignment_ids.length > 0)\n params.assignment_ids = opts.assignment_ids\n if (opts.section_ids && opts.section_ids.length > 0) params.section_ids = opts.section_ids\n if (opts.grouped !== undefined) params.grouped = opts.grouped\n if (opts.workflow_state) params.workflow_state = opts.workflow_state\n if (opts.grading_period_id !== undefined) params.grading_period_id = opts.grading_period_id\n if (opts.post_to_sis !== undefined) params.post_to_sis = opts.post_to_sis\n if (opts.submitted_since) params.submitted_since = opts.submitted_since\n if (opts.graded_since) params.graded_since = opts.graded_since\n return params\n}\n\nexport class SubmissionsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(\n courseId: number,\n assignmentId: number,\n opts: ListSubmissionsOptions = {},\n ): Promise<CanvasSubmission[]> {\n return this.client.paginate<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions`,\n buildListParams(opts),\n )\n }\n\n async get(\n courseId: number,\n assignmentId: number,\n userId: number,\n opts: GetSubmissionOptions = {},\n ): Promise<CanvasSubmission> {\n const include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_GET_INCLUDE\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`,\n { query: { include } },\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 { CanvasQueryParams } from './query'\nimport type { CanvasUpcomingEvent, CanvasUser, CanvasUserProfile } from './types'\n\nexport type CourseUserEnrollmentType = 'student' | 'teacher' | 'ta' | 'observer' | 'designer'\n\nexport type CourseUserEnrollmentState = 'active' | 'invited' | 'rejected' | 'completed' | 'inactive'\n\nexport type CourseUserInclude =\n | 'email'\n | 'enrollments'\n | 'locked'\n | 'avatar_url'\n | 'test_student'\n | 'bio'\n | 'custom_links'\n | 'current_grading_period_scores'\n | 'uuid'\n\nexport type UserSort = 'username' | 'email' | 'sis_id' | 'integration_id' | 'last_login'\n\nexport type SearchUserInclude = 'email' | 'last_login' | 'avatar_url' | 'time_zone' | 'uuid'\n\nexport interface ListCourseUsersOptions {\n enrollment_type?: CourseUserEnrollmentType | ReadonlyArray<CourseUserEnrollmentType>\n enrollment_state?: ReadonlyArray<CourseUserEnrollmentState>\n enrollment_role_id?: number\n include?: ReadonlyArray<CourseUserInclude>\n user_ids?: ReadonlyArray<number | string>\n user_id?: number | string\n search_term?: string\n sort?: UserSort\n order?: 'asc' | 'desc'\n}\n\nexport interface SearchUsersOptions {\n sort?: UserSort\n order?: 'asc' | 'desc'\n include?: ReadonlyArray<SearchUserInclude>\n}\n\n/**\n * Public interface for the users module. Lets `CanvasClient` swap between\n * the hand-written `UsersModule` and the prototype generated-client\n * implementation (see GitHub issue #78) without tool callers caring.\n */\nexport interface UsersModuleApi {\n listStudents(courseId: number): Promise<CanvasUser[]>\n get(userId: number): Promise<CanvasUser>\n getProfile(): Promise<CanvasUserProfile>\n searchUsers(\n accountId: number,\n searchTerm: string,\n opts?: SearchUsersOptions,\n ): Promise<CanvasUser[]>\n listCourseUsers(courseId: number, opts?: ListCourseUsersOptions): Promise<CanvasUser[]>\n getUpcomingAssignments(): Promise<CanvasUpcomingEvent[]>\n}\n\nexport class UsersModule implements UsersModuleApi {\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 opts: SearchUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const params: CanvasQueryParams = { search_term: searchTerm }\n if (opts.sort) params.sort = opts.sort\n if (opts.order) params.order = opts.order\n if (opts.include && opts.include.length > 0) params.include = opts.include\n return this.client.paginate<CanvasUser>(`/api/v1/accounts/${accountId}/users`, params)\n }\n\n async listCourseUsers(\n courseId: number,\n opts: ListCourseUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const params: CanvasQueryParams = {}\n if (opts.enrollment_type !== undefined) {\n params.enrollment_type = Array.isArray(opts.enrollment_type)\n ? opts.enrollment_type\n : [opts.enrollment_type]\n }\n if (opts.enrollment_state && opts.enrollment_state.length > 0) {\n params.enrollment_state = opts.enrollment_state\n }\n if (opts.enrollment_role_id !== undefined) params.enrollment_role_id = opts.enrollment_role_id\n if (opts.include && opts.include.length > 0) params.include = opts.include\n if (opts.user_ids && opts.user_ids.length > 0) params.user_ids = opts.user_ids\n if (opts.user_id !== undefined) params.user_id = opts.user_id\n if (opts.search_term) params.search_term = opts.search_term\n if (opts.sort) params.sort = opts.sort\n if (opts.order) params.order = opts.order\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","/**\n * Generated-client users module (prototype for GitHub issue #78).\n *\n * Demonstrates swapping the hand-written `UsersModule` with a thin layer\n * on top of `openapi-fetch` + generated types. The public shape matches\n * `UsersModule` exactly so that `CanvasClient` can select either\n * implementation behind a feature flag without downstream tools knowing.\n *\n * Two things that cannot live in the OpenAPI spec (per issue #78) are\n * handled here:\n *\n * 1. Bearer auth injection -- a one-line openapi-fetch middleware.\n * 2. Link-header pagination -- a small manual loop. openapi-fetch drives\n * the first request with full type-checking; subsequent pages are\n * fetched directly since Canvas returns absolute URLs in the Link\n * header that don't fit openapi-fetch's path-templated API surface.\n */\nimport createClient, { type Middleware } from 'openapi-fetch'\nimport { CanvasApiError } from '../client'\nimport type {\n CanvasClientConfig,\n CanvasErrorResponse,\n CanvasUpcomingEvent,\n CanvasUser,\n CanvasUserProfile,\n} from '../types'\nimport type {\n CourseUserEnrollmentType,\n CourseUserEnrollmentState,\n CourseUserInclude,\n ListCourseUsersOptions,\n SearchUserInclude,\n SearchUsersOptions,\n UserSort,\n UsersModuleApi,\n} from '../users'\nimport type { paths } from './types'\n\nconst DEFAULT_MAX_PAGINATION_PAGES = 1000\nconst USER_AGENT = 'canvas-lms-mcp/1.0'\nconst DEFAULT_PER_PAGE = 100\n\ntype OpenApiClient = ReturnType<typeof createClient<paths>>\n\nexport class GeneratedUsersModule implements UsersModuleApi {\n private client: OpenApiClient\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 const token = this.token\n const authMiddleware: Middleware = {\n onRequest({ request }) {\n request.headers.set('Authorization', `Bearer ${token}`)\n request.headers.set('User-Agent', USER_AGENT)\n return request\n },\n }\n\n this.client = createClient<paths>({ baseUrl: this.baseUrl })\n this.client.use(authMiddleware)\n }\n\n async listStudents(courseId: number): Promise<CanvasUser[]> {\n return this.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, {\n 'enrollment_type[]': ['student'],\n })\n }\n\n async get(userId: number): Promise<CanvasUser> {\n const endpoint = `/api/v1/users/${userId}`\n const { data, error, response } = await this.client.GET('/api/v1/users/{id}', {\n params: { path: { id: userId } },\n })\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return data as CanvasUser\n }\n\n async getProfile(): Promise<CanvasUserProfile> {\n const endpoint = '/api/v1/users/self/profile'\n const { data, error, response } = await this.client.GET('/api/v1/users/self/profile')\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return data as CanvasUserProfile\n }\n\n async searchUsers(\n accountId: number,\n searchTerm: string,\n opts: SearchUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const query: Record<string, unknown> = { search_term: searchTerm }\n if (opts.sort) query.sort = opts.sort\n if (opts.order) query.order = opts.order\n if (opts.include && opts.include.length > 0) query['include[]'] = opts.include\n return this.paginate<CanvasUser>(`/api/v1/accounts/${accountId}/users`, query)\n }\n\n async listCourseUsers(\n courseId: number,\n opts: ListCourseUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const query: Record<string, unknown> = {}\n if (opts.enrollment_type !== undefined) {\n query['enrollment_type[]'] = Array.isArray(opts.enrollment_type)\n ? opts.enrollment_type\n : [opts.enrollment_type]\n }\n if (opts.enrollment_state && opts.enrollment_state.length > 0) {\n query['enrollment_state[]'] = opts.enrollment_state\n }\n if (opts.include && opts.include.length > 0) query['include[]'] = opts.include\n if (opts.user_ids && opts.user_ids.length > 0) query['user_ids[]'] = opts.user_ids\n if (opts.enrollment_role_id !== undefined) query.enrollment_role_id = opts.enrollment_role_id\n if (opts.user_id !== undefined) query.user_id = opts.user_id\n if (opts.search_term) query.search_term = opts.search_term\n if (opts.sort) query.sort = opts.sort\n if (opts.order) query.order = opts.order\n return this.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, query)\n }\n\n async getUpcomingAssignments(): Promise<CanvasUpcomingEvent[]> {\n const endpoint = '/api/v1/users/self/upcoming_events'\n const { data, error, response } = await this.client.GET('/api/v1/users/self/upcoming_events', {\n params: { query: { type: 'Assignment' } },\n })\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return (data as CanvasUpcomingEvent[]) ?? []\n }\n\n /**\n * Paginated GET with manual Link-header handling. The first page uses\n * openapi-fetch's typed surface; subsequent pages use raw fetch since\n * Canvas returns absolute URLs in the Link header that don't map onto\n * path templates.\n */\n private async paginate<T>(endpoint: string, query: Record<string, unknown>): Promise<T[]> {\n const firstUrl = buildUrl(this.baseUrl, endpoint, query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n }\n\n const results: T[] = []\n let nextUrl: string | null = firstUrl\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, { headers })\n if (!response.ok) {\n throw await toCanvasError(response, endpoint, undefined)\n }\n const page = (await response.json()) as T[]\n results.push(...page)\n pages++\n nextUrl = parseNextLink(response.headers.get('Link'))\n }\n return results\n }\n}\n\nfunction buildUrl(baseUrl: string, endpoint: string, query: Record<string, unknown>): string {\n const url = new URL(`${baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', String(DEFAULT_PER_PAGE))\n }\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue\n url.searchParams.append(key, String(item))\n }\n } else {\n url.searchParams.set(key, String(value))\n }\n }\n return url.toString()\n}\n\nfunction 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\nasync function toCanvasError(\n response: Response,\n endpoint: string,\n openapiError: unknown,\n): Promise<CanvasApiError> {\n let body: CanvasErrorResponse = {}\n if (openapiError && typeof openapiError === 'object') {\n body = openapiError as CanvasErrorResponse\n } else if (!response.bodyUsed) {\n try {\n body = (await response.clone().json()) as CanvasErrorResponse\n } catch {\n body = {}\n }\n }\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n return new CanvasApiError(message, response.status, endpoint)\n}\n\n// Re-export the types already defined in src/canvas/users.ts so that\n// consumers of the generated module get an identical surface.\nexport type {\n CourseUserEnrollmentType,\n CourseUserEnrollmentState,\n CourseUserInclude,\n ListCourseUsersOptions,\n SearchUserInclude,\n SearchUsersOptions,\n UserSort,\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 { CanvasQueryParams } from './query'\nimport type { CanvasEnrollment } from './types'\n\nexport type EnrollmentType =\n | 'StudentEnrollment'\n | 'TeacherEnrollment'\n | 'TaEnrollment'\n | 'DesignerEnrollment'\n | 'ObserverEnrollment'\n\nexport type EnrollmentState =\n | 'active'\n | 'invited'\n | 'creation_pending'\n | 'deleted'\n | 'rejected'\n | 'completed'\n | 'inactive'\n | 'current_and_invited'\n | 'current_and_future'\n | 'current_and_concluded'\n\nexport type EnrollmentInclude =\n | 'avatar_url'\n | 'group_ids'\n | 'locked'\n | 'observed_users'\n | 'can_be_removed'\n | 'uuid'\n | 'current_points'\n | 'grades'\n\nexport interface ListCourseEnrollmentsOptions {\n type?: ReadonlyArray<EnrollmentType>\n role?: ReadonlyArray<string>\n state?: ReadonlyArray<EnrollmentState>\n include?: ReadonlyArray<EnrollmentInclude>\n user_id?: number | string\n grading_period_id?: number\n enrollment_term_id?: number\n sis_account_id?: ReadonlyArray<string>\n sis_course_id?: ReadonlyArray<string>\n sis_section_id?: ReadonlyArray<string>\n sis_user_id?: ReadonlyArray<string>\n created_for_sis_id?: ReadonlyArray<boolean>\n}\n\nexport interface ListUserEnrollmentsOptions {\n type?: ReadonlyArray<EnrollmentType>\n role?: ReadonlyArray<string>\n state?: ReadonlyArray<EnrollmentState>\n include?: ReadonlyArray<EnrollmentInclude>\n grading_period_id?: number\n enrollment_term_id?: number\n}\n\nfunction buildParams(opts: object): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n for (const [key, value] of Object.entries(opts)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n params[key] = value as ReadonlyArray<string | number | boolean>\n } else if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n params[key] = value\n }\n }\n return params\n}\n\nexport class EnrollmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(opts: ListUserEnrollmentsOptions = {}): Promise<CanvasEnrollment[]> {\n return this.client.paginate<CanvasEnrollment>(\n '/api/v1/users/self/enrollments',\n buildParams(opts),\n )\n }\n\n async listForCourse(\n courseId: number,\n opts: ListCourseEnrollmentsOptions = {},\n ): Promise<CanvasEnrollment[]> {\n return this.client.paginate<CanvasEnrollment>(\n `/api/v1/courses/${courseId}/enrollments`,\n buildParams(opts),\n )\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, GeneratedClientDomain } 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, type UsersModuleApi } from './users'\nimport { GeneratedUsersModule } from './generated/users-client'\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: UsersModuleApi\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 = isGeneratedEnabled(config.useGeneratedClient, 'users')\n ? new GeneratedUsersModule(config)\n : 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\nfunction isGeneratedEnabled(\n flag: CanvasClientConfig['useGeneratedClient'],\n domain: GeneratedClientDomain,\n): boolean {\n if (flag === true) return true\n if (Array.isArray(flag)) return (flag as readonly GeneratedClientDomain[]).includes(domain)\n return false\n}\n\nexport { GeneratedUsersModule } from './generated/users-client'\nexport { CanvasHttpClient, CanvasApiError } from './client'\nexport type { CanvasRequestOptions } from './client'\nexport { appendCanvasQuery, toCanvasQuery } from './query'\nexport type { CanvasQueryParams, CanvasQueryPrimitive, CanvasQueryValue } from './query'\nexport type * from './types'\n"]}
1
+ {"version":3,"sources":["/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-3JL6Z5ID.cjs","../package.json","../src/canvas/query.ts","../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/generated/users-client.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":["USER_AGENT"],"mappings":"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","file":"/home/runner/work/canvas-lms-mcp/canvas-lms-mcp/dist/chunk-3JL6Z5ID.cjs","sourcesContent":[null,"{\n \"name\": \"canvas-lms-mcp\",\n \"version\": \"1.9.0\",\n \"mcpName\": \"io.github.bruchris/canvas-lms-mcp\",\n \"description\": \"TypeScript MCP 1.x server for Canvas LMS — 104 tools across Canvas courses, assignments, grades, gradebook history, quizzes, outcomes, discussions, admin workflows, and more.\",\n \"type\": \"module\",\n \"types\": \"./dist/server.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\",\n \"require\": \"./dist/server.cjs\"\n },\n \"./stdio\": {\n \"types\": \"./dist/stdio.d.ts\",\n \"import\": \"./dist/stdio.js\",\n \"require\": \"./dist/stdio.cjs\"\n },\n \"./http\": {\n \"types\": \"./dist/http.d.ts\",\n \"import\": \"./dist/http.js\",\n \"require\": \"./dist/http.cjs\"\n },\n \"./canvas\": {\n \"types\": \"./dist/canvas/index.d.ts\",\n \"import\": \"./dist/canvas/index.js\",\n \"require\": \"./dist/canvas/index.cjs\"\n }\n },\n \"bin\": {\n \"canvas-lms-mcp\": \"bin/canvas-lms-mcp.js\"\n },\n \"files\": [\n \"bin/\",\n \"dist/\"\n ],\n \"scripts\": {\n \"dev\": \"tsup --watch\",\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"generate:manifests\": \"tsx scripts/generate-manifests.ts\",\n \"canvas:spec:generate\": \"tsx scripts/canvas-spec/generate.ts\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"lint\": \"eslint src/ tests/ && prettier --check src/ tests/\",\n \"lint:fix\": \"eslint src/ tests/ --fix && prettier --write src/ tests/\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"keywords\": [\n \"canvas\",\n \"canvas-lms\",\n \"lms\",\n \"mcp\",\n \"model-context-protocol\",\n \"education\",\n \"grading\",\n \"ai-agent\",\n \"ai-tools\",\n \"claude-desktop\",\n \"cursor\",\n \"vscode\",\n \"instructure\",\n \"education-technology\",\n \"edtech\",\n \"teacher\",\n \"student\",\n \"grading-automation\",\n \"typescript\"\n ],\n \"author\": \"Christian Bru\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/bruchris/canvas-lms-mcp.git\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@10.11.0\",\n \"pnpm\": {\n \"overrides\": {\n \"hono\": \"4.12.14\"\n }\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"openapi-fetch\": \"^0.17.0\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@types/node\": \"^25.6.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.59.0\",\n \"@typescript-eslint/parser\": \"^8.59.0\",\n \"@vitest/coverage-v8\": \"^4.1.5\",\n \"eslint\": \"^10.2.1\",\n \"eslint-config-prettier\": \"^10.1.8\",\n \"eslint-plugin-prettier\": \"^5.5.5\",\n \"openapi-typescript\": \"^7.13.0\",\n \"prettier\": \"^3.8.3\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.20.6\",\n \"typescript\": \"^6.0.3\",\n \"typescript-eslint\": \"^8.59.0\",\n \"vitest\": \"^4.1.5\",\n \"yaml\": \"^2.8.3\"\n }\n}\n","export type CanvasQueryPrimitive = string | number | boolean\n\nexport type CanvasQueryValue = CanvasQueryPrimitive | ReadonlyArray<CanvasQueryPrimitive>\n\nexport type CanvasQueryParams = Record<string, CanvasQueryValue | undefined | null>\n\nexport function appendCanvasQuery(target: URLSearchParams, params?: CanvasQueryParams): void {\n if (!params) return\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n const arrayKey = key.endsWith('[]') ? key : `${key}[]`\n for (const item of value) {\n if (item === undefined || item === null) continue\n target.append(arrayKey, String(item))\n }\n } else {\n target.set(key, String(value))\n }\n }\n}\n\nexport function toCanvasQuery(params?: CanvasQueryParams): URLSearchParams {\n const search = new URLSearchParams()\n appendCanvasQuery(search, params)\n return search\n}\n","import { version } from '../../package.json'\nimport { appendCanvasQuery, type CanvasQueryParams } from './query'\nimport 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/${version}`\n\nexport interface CanvasRequestOptions extends RequestInit {\n /**\n * Query params to append to the endpoint URL. Arrays produce repeated\n * `key[]=v1&key[]=v2` entries (the convention Canvas uses for `include[]`).\n * Preserves any query string already present on the endpoint.\n */\n query?: CanvasQueryParams\n}\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: CanvasRequestOptions = {}): Promise<T> {\n const { query, ...init } = options\n let url = endpoint.startsWith('http') ? endpoint : `${this._baseUrl}${endpoint}`\n if (query) {\n const parsed = new URL(url)\n appendCanvasQuery(parsed.searchParams, query)\n url = parsed.toString()\n }\n\n const method = (init.method ?? 'GET').toUpperCase()\n if (init.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 (init.body) {\n headers['Content-Type'] = 'application/json'\n }\n\n const response = await fetch(url, {\n ...init,\n headers: {\n ...headers,\n ...init.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?: CanvasQueryParams): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\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?: CanvasQueryParams,\n ): Promise<T[]> {\n const url = new URL(`${this._baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', '100')\n }\n appendCanvasQuery(url.searchParams, params)\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 { CanvasQueryParams } from './query'\nimport type { CanvasCourse, CreateCourseParams, UpdateCourseParams } from './types'\n\nexport type CourseEnrollmentState = 'active' | 'invited_or_pending' | 'completed'\n\nexport type CourseWorkflowState = 'unpublished' | 'available' | 'completed' | 'deleted'\n\nexport type CourseListInclude =\n | 'needs_grading_count'\n | 'syllabus_body'\n | 'public_description'\n | 'total_scores'\n | 'current_grading_period_scores'\n | 'grading_periods'\n | 'term'\n | 'account'\n | 'course_progress'\n | 'sections'\n | 'storage_quota_used_mb'\n | 'total_students'\n | 'passback_status'\n | 'favorites'\n | 'teachers'\n | 'observed_users'\n | 'tabs'\n | 'course_image'\n | 'banner_image'\n | 'concluded'\n | 'lti_context_id'\n | 'post_manually'\n\nexport type CourseGetInclude =\n | CourseListInclude\n | 'all_courses'\n | 'permissions'\n | 'public_description'\n\nexport interface ListCoursesOptions {\n enrollment_state?: CourseEnrollmentState\n state?: ReadonlyArray<CourseWorkflowState>\n enrollment_role_id?: number\n include?: ReadonlyArray<CourseListInclude>\n exclude_blueprint_courses?: boolean\n}\n\nexport interface GetCourseOptions {\n include?: ReadonlyArray<CourseGetInclude>\n teacher_limit?: number\n}\n\nconst DEFAULT_LIST_INCLUDE: ReadonlyArray<CourseListInclude> = ['term']\nconst DEFAULT_GET_INCLUDE: ReadonlyArray<CourseGetInclude> = ['term', 'total_students']\n\nexport class CoursesModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(opts: ListCoursesOptions = {}): Promise<CanvasCourse[]> {\n const params: CanvasQueryParams = {}\n params.include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_LIST_INCLUDE\n if (opts.enrollment_state) params.enrollment_state = opts.enrollment_state\n if (opts.state && opts.state.length > 0) params.state = opts.state\n if (opts.enrollment_role_id !== undefined) params.enrollment_role_id = opts.enrollment_role_id\n if (opts.exclude_blueprint_courses !== undefined) {\n params.exclude_blueprint_courses = opts.exclude_blueprint_courses\n }\n return this.client.paginate<CanvasCourse>('/api/v1/courses', params)\n }\n\n async get(courseId: number, opts: GetCourseOptions = {}): Promise<CanvasCourse> {\n const include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_GET_INCLUDE\n const query: CanvasQueryParams = { include }\n if (opts.teacher_limit !== undefined) query.teacher_limit = opts.teacher_limit\n return this.client.request<CanvasCourse>(`/api/v1/courses/${courseId}`, { query })\n }\n\n async getSyllabus(courseId: number): Promise<string | null> {\n const course = await this.client.request<CanvasCourse>(`/api/v1/courses/${courseId}`, {\n query: { 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 { CanvasQueryParams } from './query'\nimport type {\n CanvasAssignment,\n CanvasAssignmentGroup,\n CreateAssignmentParams,\n UpdateAssignmentParams,\n} from './types'\n\nexport type AssignmentListInclude =\n | 'submission'\n | 'assignment_visibility'\n | 'all_dates'\n | 'overrides'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n\nexport type AssignmentGetInclude =\n | 'submission'\n | 'assignment_visibility'\n | 'overrides'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n | 'all_dates'\n\nexport type AssignmentBucket =\n | 'past'\n | 'overdue'\n | 'undated'\n | 'ungraded'\n | 'unsubmitted'\n | 'upcoming'\n | 'future'\n\nexport type AssignmentOrderBy = 'position' | 'name' | 'due_at'\n\nexport type AssignmentGroupInclude =\n | 'assignments'\n | 'discussion_topic'\n | 'all_dates'\n | 'assignment_visibility'\n | 'overrides'\n | 'submission'\n | 'observed_users'\n | 'can_edit'\n | 'score_statistics'\n\nexport interface ListAssignmentsOptions {\n include?: ReadonlyArray<AssignmentListInclude>\n search_term?: string\n override_assignment_dates?: boolean\n needs_grading_count_by_section?: boolean\n bucket?: AssignmentBucket\n assignment_ids?: ReadonlyArray<number>\n order_by?: AssignmentOrderBy\n post_to_sis?: boolean\n}\n\nexport interface GetAssignmentOptions {\n include?: ReadonlyArray<AssignmentGetInclude>\n override_assignment_dates?: boolean\n needs_grading_count_by_section?: boolean\n all_dates?: boolean\n}\n\nexport interface ListAssignmentGroupsOptions {\n include?: ReadonlyArray<AssignmentGroupInclude>\n assignment_ids?: ReadonlyArray<number>\n exclude_assignment_submission_types?: ReadonlyArray<string>\n override_assignment_dates?: boolean\n grading_period_id?: number\n scope_assignments_to_student?: boolean\n}\n\nfunction toQuery(opts: object): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n for (const [key, value] of Object.entries(opts)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n params[key] = value as ReadonlyArray<string | number | boolean>\n } else if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n params[key] = value\n }\n }\n return params\n}\n\nexport class AssignmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(courseId: number, opts: ListAssignmentsOptions = {}): Promise<CanvasAssignment[]> {\n return this.client.paginate<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments`,\n toQuery(opts),\n )\n }\n\n async get(\n courseId: number,\n assignmentId: number,\n opts: GetAssignmentOptions = {},\n ): Promise<CanvasAssignment> {\n return this.client.request<CanvasAssignment>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}`,\n { query: toQuery(opts) },\n )\n }\n\n async listGroups(\n courseId: number,\n opts: ListAssignmentGroupsOptions = {},\n ): Promise<CanvasAssignmentGroup[]> {\n return this.client.paginate<CanvasAssignmentGroup>(\n `/api/v1/courses/${courseId}/assignment_groups`,\n toQuery(opts),\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 { CanvasQueryParams } from './query'\nimport type { CanvasSubmission } from './types'\n\nexport type SubmissionListInclude =\n | 'submission_history'\n | 'submission_comments'\n | 'rubric_assessment'\n | 'assignment'\n | 'visibility'\n | 'course'\n | 'user'\n | 'group'\n | 'read_status'\n | 'sub_assignment_submissions'\n\nexport type SubmissionGetInclude =\n | 'submission_history'\n | 'submission_comments'\n | 'rubric_assessment'\n | 'visibility'\n | 'course'\n | 'user'\n | 'read_status'\n\nexport type SubmissionWorkflowState = 'submitted' | 'unsubmitted' | 'graded' | 'pending_review'\n\nexport interface ListSubmissionsOptions {\n include?: ReadonlyArray<SubmissionListInclude>\n student_ids?: ReadonlyArray<number | string>\n assignment_ids?: ReadonlyArray<number>\n section_ids?: ReadonlyArray<number>\n grouped?: boolean\n workflow_state?: SubmissionWorkflowState\n grading_period_id?: number\n post_to_sis?: boolean\n submitted_since?: string\n graded_since?: string\n}\n\nexport interface GetSubmissionOptions {\n include?: ReadonlyArray<SubmissionGetInclude>\n}\n\nconst DEFAULT_LIST_INCLUDE: ReadonlyArray<SubmissionListInclude> = ['submission_comments']\nconst DEFAULT_GET_INCLUDE: ReadonlyArray<SubmissionGetInclude> = ['submission_comments']\n\nfunction buildListParams(opts: ListSubmissionsOptions): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n params.include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_LIST_INCLUDE\n if (opts.student_ids && opts.student_ids.length > 0) params.student_ids = opts.student_ids\n if (opts.assignment_ids && opts.assignment_ids.length > 0)\n params.assignment_ids = opts.assignment_ids\n if (opts.section_ids && opts.section_ids.length > 0) params.section_ids = opts.section_ids\n if (opts.grouped !== undefined) params.grouped = opts.grouped\n if (opts.workflow_state) params.workflow_state = opts.workflow_state\n if (opts.grading_period_id !== undefined) params.grading_period_id = opts.grading_period_id\n if (opts.post_to_sis !== undefined) params.post_to_sis = opts.post_to_sis\n if (opts.submitted_since) params.submitted_since = opts.submitted_since\n if (opts.graded_since) params.graded_since = opts.graded_since\n return params\n}\n\nexport class SubmissionsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(\n courseId: number,\n assignmentId: number,\n opts: ListSubmissionsOptions = {},\n ): Promise<CanvasSubmission[]> {\n return this.client.paginate<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions`,\n buildListParams(opts),\n )\n }\n\n async get(\n courseId: number,\n assignmentId: number,\n userId: number,\n opts: GetSubmissionOptions = {},\n ): Promise<CanvasSubmission> {\n const include = opts.include && opts.include.length > 0 ? opts.include : DEFAULT_GET_INCLUDE\n return this.client.request<CanvasSubmission>(\n `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`,\n { query: { include } },\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 { CanvasQueryParams } from './query'\nimport type { CanvasUpcomingEvent, CanvasUser, CanvasUserProfile } from './types'\n\nexport type CourseUserEnrollmentType = 'student' | 'teacher' | 'ta' | 'observer' | 'designer'\n\nexport type CourseUserEnrollmentState = 'active' | 'invited' | 'rejected' | 'completed' | 'inactive'\n\nexport type CourseUserInclude =\n | 'email'\n | 'enrollments'\n | 'locked'\n | 'avatar_url'\n | 'test_student'\n | 'bio'\n | 'custom_links'\n | 'current_grading_period_scores'\n | 'uuid'\n\nexport type UserSort = 'username' | 'email' | 'sis_id' | 'integration_id' | 'last_login'\n\nexport type SearchUserInclude = 'email' | 'last_login' | 'avatar_url' | 'time_zone' | 'uuid'\n\nexport interface ListCourseUsersOptions {\n enrollment_type?: CourseUserEnrollmentType | ReadonlyArray<CourseUserEnrollmentType>\n enrollment_state?: ReadonlyArray<CourseUserEnrollmentState>\n enrollment_role_id?: number\n include?: ReadonlyArray<CourseUserInclude>\n user_ids?: ReadonlyArray<number | string>\n user_id?: number | string\n search_term?: string\n sort?: UserSort\n order?: 'asc' | 'desc'\n}\n\nexport interface SearchUsersOptions {\n sort?: UserSort\n order?: 'asc' | 'desc'\n include?: ReadonlyArray<SearchUserInclude>\n}\n\n/**\n * Public interface for the users module. Lets `CanvasClient` swap between\n * the hand-written `UsersModule` and the prototype generated-client\n * implementation (see GitHub issue #78) without tool callers caring.\n */\nexport interface UsersModuleApi {\n listStudents(courseId: number): Promise<CanvasUser[]>\n get(userId: number): Promise<CanvasUser>\n getProfile(): Promise<CanvasUserProfile>\n searchUsers(\n accountId: number,\n searchTerm: string,\n opts?: SearchUsersOptions,\n ): Promise<CanvasUser[]>\n listCourseUsers(courseId: number, opts?: ListCourseUsersOptions): Promise<CanvasUser[]>\n getUpcomingAssignments(): Promise<CanvasUpcomingEvent[]>\n}\n\nexport class UsersModule implements UsersModuleApi {\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 opts: SearchUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const params: CanvasQueryParams = { search_term: searchTerm }\n if (opts.sort) params.sort = opts.sort\n if (opts.order) params.order = opts.order\n if (opts.include && opts.include.length > 0) params.include = opts.include\n return this.client.paginate<CanvasUser>(`/api/v1/accounts/${accountId}/users`, params)\n }\n\n async listCourseUsers(\n courseId: number,\n opts: ListCourseUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const params: CanvasQueryParams = {}\n if (opts.enrollment_type !== undefined) {\n params.enrollment_type = Array.isArray(opts.enrollment_type)\n ? opts.enrollment_type\n : [opts.enrollment_type]\n }\n if (opts.enrollment_state && opts.enrollment_state.length > 0) {\n params.enrollment_state = opts.enrollment_state\n }\n if (opts.enrollment_role_id !== undefined) params.enrollment_role_id = opts.enrollment_role_id\n if (opts.include && opts.include.length > 0) params.include = opts.include\n if (opts.user_ids && opts.user_ids.length > 0) params.user_ids = opts.user_ids\n if (opts.user_id !== undefined) params.user_id = opts.user_id\n if (opts.search_term) params.search_term = opts.search_term\n if (opts.sort) params.sort = opts.sort\n if (opts.order) params.order = opts.order\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","/**\n * Generated-client users module (prototype for GitHub issue #78).\n *\n * Demonstrates swapping the hand-written `UsersModule` with a thin layer\n * on top of `openapi-fetch` + generated types. The public shape matches\n * `UsersModule` exactly so that `CanvasClient` can select either\n * implementation behind a feature flag without downstream tools knowing.\n *\n * Two things that cannot live in the OpenAPI spec (per issue #78) are\n * handled here:\n *\n * 1. Bearer auth injection -- a one-line openapi-fetch middleware.\n * 2. Link-header pagination -- a small manual loop. openapi-fetch drives\n * the first request with full type-checking; subsequent pages are\n * fetched directly since Canvas returns absolute URLs in the Link\n * header that don't fit openapi-fetch's path-templated API surface.\n */\nimport createClient, { type Middleware } from 'openapi-fetch'\nimport { CanvasApiError } from '../client'\nimport type {\n CanvasClientConfig,\n CanvasErrorResponse,\n CanvasUpcomingEvent,\n CanvasUser,\n CanvasUserProfile,\n} from '../types'\nimport type {\n CourseUserEnrollmentType,\n CourseUserEnrollmentState,\n CourseUserInclude,\n ListCourseUsersOptions,\n SearchUserInclude,\n SearchUsersOptions,\n UserSort,\n UsersModuleApi,\n} from '../users'\nimport type { paths } from './types'\n\nconst DEFAULT_MAX_PAGINATION_PAGES = 1000\nconst USER_AGENT = 'canvas-lms-mcp/1.0'\nconst DEFAULT_PER_PAGE = 100\n\ntype OpenApiClient = ReturnType<typeof createClient<paths>>\n\nexport class GeneratedUsersModule implements UsersModuleApi {\n private client: OpenApiClient\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 const token = this.token\n const authMiddleware: Middleware = {\n onRequest({ request }) {\n request.headers.set('Authorization', `Bearer ${token}`)\n request.headers.set('User-Agent', USER_AGENT)\n return request\n },\n }\n\n this.client = createClient<paths>({ baseUrl: this.baseUrl })\n this.client.use(authMiddleware)\n }\n\n async listStudents(courseId: number): Promise<CanvasUser[]> {\n return this.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, {\n 'enrollment_type[]': ['student'],\n })\n }\n\n async get(userId: number): Promise<CanvasUser> {\n const endpoint = `/api/v1/users/${userId}`\n const { data, error, response } = await this.client.GET('/api/v1/users/{id}', {\n params: { path: { id: userId } },\n })\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return data as CanvasUser\n }\n\n async getProfile(): Promise<CanvasUserProfile> {\n const endpoint = '/api/v1/users/self/profile'\n const { data, error, response } = await this.client.GET('/api/v1/users/self/profile')\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return data as CanvasUserProfile\n }\n\n async searchUsers(\n accountId: number,\n searchTerm: string,\n opts: SearchUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const query: Record<string, unknown> = { search_term: searchTerm }\n if (opts.sort) query.sort = opts.sort\n if (opts.order) query.order = opts.order\n if (opts.include && opts.include.length > 0) query['include[]'] = opts.include\n return this.paginate<CanvasUser>(`/api/v1/accounts/${accountId}/users`, query)\n }\n\n async listCourseUsers(\n courseId: number,\n opts: ListCourseUsersOptions = {},\n ): Promise<CanvasUser[]> {\n const query: Record<string, unknown> = {}\n if (opts.enrollment_type !== undefined) {\n query['enrollment_type[]'] = Array.isArray(opts.enrollment_type)\n ? opts.enrollment_type\n : [opts.enrollment_type]\n }\n if (opts.enrollment_state && opts.enrollment_state.length > 0) {\n query['enrollment_state[]'] = opts.enrollment_state\n }\n if (opts.include && opts.include.length > 0) query['include[]'] = opts.include\n if (opts.user_ids && opts.user_ids.length > 0) query['user_ids[]'] = opts.user_ids\n if (opts.enrollment_role_id !== undefined) query.enrollment_role_id = opts.enrollment_role_id\n if (opts.user_id !== undefined) query.user_id = opts.user_id\n if (opts.search_term) query.search_term = opts.search_term\n if (opts.sort) query.sort = opts.sort\n if (opts.order) query.order = opts.order\n return this.paginate<CanvasUser>(`/api/v1/courses/${courseId}/users`, query)\n }\n\n async getUpcomingAssignments(): Promise<CanvasUpcomingEvent[]> {\n const endpoint = '/api/v1/users/self/upcoming_events'\n const { data, error, response } = await this.client.GET('/api/v1/users/self/upcoming_events', {\n params: { query: { type: 'Assignment' } },\n })\n if (error || !response.ok) {\n throw await toCanvasError(response, endpoint, error)\n }\n return (data as CanvasUpcomingEvent[]) ?? []\n }\n\n /**\n * Paginated GET with manual Link-header handling. The first page uses\n * openapi-fetch's typed surface; subsequent pages use raw fetch since\n * Canvas returns absolute URLs in the Link header that don't map onto\n * path templates.\n */\n private async paginate<T>(endpoint: string, query: Record<string, unknown>): Promise<T[]> {\n const firstUrl = buildUrl(this.baseUrl, endpoint, query)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': USER_AGENT,\n }\n\n const results: T[] = []\n let nextUrl: string | null = firstUrl\n let pages = 0\n\n while (nextUrl && pages < this.maxPaginationPages) {\n const response = await fetch(nextUrl, { headers })\n if (!response.ok) {\n throw await toCanvasError(response, endpoint, undefined)\n }\n const page = (await response.json()) as T[]\n results.push(...page)\n pages++\n nextUrl = parseNextLink(response.headers.get('Link'))\n }\n return results\n }\n}\n\nfunction buildUrl(baseUrl: string, endpoint: string, query: Record<string, unknown>): string {\n const url = new URL(`${baseUrl}${endpoint}`)\n if (!url.searchParams.has('per_page')) {\n url.searchParams.set('per_page', String(DEFAULT_PER_PAGE))\n }\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue\n url.searchParams.append(key, String(item))\n }\n } else {\n url.searchParams.set(key, String(value))\n }\n }\n return url.toString()\n}\n\nfunction 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\nasync function toCanvasError(\n response: Response,\n endpoint: string,\n openapiError: unknown,\n): Promise<CanvasApiError> {\n let body: CanvasErrorResponse = {}\n if (openapiError && typeof openapiError === 'object') {\n body = openapiError as CanvasErrorResponse\n } else if (!response.bodyUsed) {\n try {\n body = (await response.clone().json()) as CanvasErrorResponse\n } catch {\n body = {}\n }\n }\n const message =\n body.errors?.[0]?.message ?? body.message ?? `Canvas API error: ${response.status}`\n return new CanvasApiError(message, response.status, endpoint)\n}\n\n// Re-export the types already defined in src/canvas/users.ts so that\n// consumers of the generated module get an identical surface.\nexport type {\n CourseUserEnrollmentType,\n CourseUserEnrollmentState,\n CourseUserInclude,\n ListCourseUsersOptions,\n SearchUserInclude,\n SearchUsersOptions,\n UserSort,\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 { CanvasQueryParams } from './query'\nimport type { CanvasEnrollment } from './types'\n\nexport type EnrollmentType =\n | 'StudentEnrollment'\n | 'TeacherEnrollment'\n | 'TaEnrollment'\n | 'DesignerEnrollment'\n | 'ObserverEnrollment'\n\nexport type EnrollmentState =\n | 'active'\n | 'invited'\n | 'creation_pending'\n | 'deleted'\n | 'rejected'\n | 'completed'\n | 'inactive'\n | 'current_and_invited'\n | 'current_and_future'\n | 'current_and_concluded'\n\nexport type EnrollmentInclude =\n | 'avatar_url'\n | 'group_ids'\n | 'locked'\n | 'observed_users'\n | 'can_be_removed'\n | 'uuid'\n | 'current_points'\n | 'grades'\n\nexport interface ListCourseEnrollmentsOptions {\n type?: ReadonlyArray<EnrollmentType>\n role?: ReadonlyArray<string>\n state?: ReadonlyArray<EnrollmentState>\n include?: ReadonlyArray<EnrollmentInclude>\n user_id?: number | string\n grading_period_id?: number\n enrollment_term_id?: number\n sis_account_id?: ReadonlyArray<string>\n sis_course_id?: ReadonlyArray<string>\n sis_section_id?: ReadonlyArray<string>\n sis_user_id?: ReadonlyArray<string>\n created_for_sis_id?: ReadonlyArray<boolean>\n}\n\nexport interface ListUserEnrollmentsOptions {\n type?: ReadonlyArray<EnrollmentType>\n role?: ReadonlyArray<string>\n state?: ReadonlyArray<EnrollmentState>\n include?: ReadonlyArray<EnrollmentInclude>\n grading_period_id?: number\n enrollment_term_id?: number\n}\n\nfunction buildParams(opts: object): CanvasQueryParams {\n const params: CanvasQueryParams = {}\n for (const [key, value] of Object.entries(opts)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n params[key] = value as ReadonlyArray<string | number | boolean>\n } else if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n params[key] = value\n }\n }\n return params\n}\n\nexport class EnrollmentsModule {\n constructor(private client: CanvasHttpClient) {}\n\n async list(opts: ListUserEnrollmentsOptions = {}): Promise<CanvasEnrollment[]> {\n return this.client.paginate<CanvasEnrollment>(\n '/api/v1/users/self/enrollments',\n buildParams(opts),\n )\n }\n\n async listForCourse(\n courseId: number,\n opts: ListCourseEnrollmentsOptions = {},\n ): Promise<CanvasEnrollment[]> {\n return this.client.paginate<CanvasEnrollment>(\n `/api/v1/courses/${courseId}/enrollments`,\n buildParams(opts),\n )\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, GeneratedClientDomain } 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, type UsersModuleApi } from './users'\nimport { GeneratedUsersModule } from './generated/users-client'\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: UsersModuleApi\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 = isGeneratedEnabled(config.useGeneratedClient, 'users')\n ? new GeneratedUsersModule(config)\n : 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\nfunction isGeneratedEnabled(\n flag: CanvasClientConfig['useGeneratedClient'],\n domain: GeneratedClientDomain,\n): boolean {\n if (flag === true) return true\n if (Array.isArray(flag)) return (flag as readonly GeneratedClientDomain[]).includes(domain)\n return false\n}\n\nexport { GeneratedUsersModule } from './generated/users-client'\nexport { CanvasHttpClient, CanvasApiError } from './client'\nexport type { CanvasRequestOptions } from './client'\nexport { appendCanvasQuery, toCanvasQuery } from './query'\nexport type { CanvasQueryParams, CanvasQueryPrimitive, CanvasQueryValue } from './query'\nexport type * from './types'\n"]}
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "1.8.1";
2
+ var version = "1.9.0";
3
3
 
4
4
  // src/canvas/query.ts
5
5
  function appendCanvasQuery(target, params) {
@@ -1383,4 +1383,4 @@ export {
1383
1383
  OUTCOME_EXCLUDE_OPTIONS,
1384
1384
  CanvasClient
1385
1385
  };
1386
- //# sourceMappingURL=chunk-NGP4KCMC.js.map
1386
+ //# sourceMappingURL=chunk-A6YFE3W7.js.map