@mtgibbs/canvas-lms-mcp 0.1.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +539 -0
  3. package/esm/_dnt.shims.d.ts +6 -0
  4. package/esm/_dnt.shims.d.ts.map +1 -0
  5. package/esm/_dnt.shims.js +61 -0
  6. package/esm/agent.d.ts +10 -0
  7. package/esm/agent.d.ts.map +1 -0
  8. package/esm/agent.js +17 -0
  9. package/esm/cli.js +6 -0
  10. package/esm/package.json +3 -0
  11. package/esm/src/api/assignments.d.ts +40 -0
  12. package/esm/src/api/assignments.d.ts.map +1 -0
  13. package/esm/src/api/assignments.js +133 -0
  14. package/esm/src/api/client.d.ts +61 -0
  15. package/esm/src/api/client.d.ts.map +1 -0
  16. package/esm/src/api/client.js +179 -0
  17. package/esm/src/api/courses.d.ts +32 -0
  18. package/esm/src/api/courses.d.ts.map +1 -0
  19. package/esm/src/api/courses.js +85 -0
  20. package/esm/src/api/stats.d.ts +15 -0
  21. package/esm/src/api/stats.d.ts.map +1 -0
  22. package/esm/src/api/stats.js +58 -0
  23. package/esm/src/api/submissions.d.ts +33 -0
  24. package/esm/src/api/submissions.d.ts.map +1 -0
  25. package/esm/src/api/submissions.js +103 -0
  26. package/esm/src/api/users.d.ts +87 -0
  27. package/esm/src/api/users.d.ts.map +1 -0
  28. package/esm/src/api/users.js +139 -0
  29. package/esm/src/mcp/prompts/course-analysis.d.ts +7 -0
  30. package/esm/src/mcp/prompts/course-analysis.d.ts.map +1 -0
  31. package/esm/src/mcp/prompts/course-analysis.js +36 -0
  32. package/esm/src/mcp/prompts/daily-checkin.d.ts +7 -0
  33. package/esm/src/mcp/prompts/daily-checkin.d.ts.map +1 -0
  34. package/esm/src/mcp/prompts/daily-checkin.js +31 -0
  35. package/esm/src/mcp/prompts/grade-recovery.d.ts +7 -0
  36. package/esm/src/mcp/prompts/grade-recovery.d.ts.map +1 -0
  37. package/esm/src/mcp/prompts/grade-recovery.js +35 -0
  38. package/esm/src/mcp/prompts/index.d.ts +15 -0
  39. package/esm/src/mcp/prompts/index.d.ts.map +1 -0
  40. package/esm/src/mcp/prompts/index.js +20 -0
  41. package/esm/src/mcp/prompts/missing-work-audit.d.ts +7 -0
  42. package/esm/src/mcp/prompts/missing-work-audit.d.ts.map +1 -0
  43. package/esm/src/mcp/prompts/missing-work-audit.js +36 -0
  44. package/esm/src/mcp/prompts/week-planning.d.ts +7 -0
  45. package/esm/src/mcp/prompts/week-planning.d.ts.map +1 -0
  46. package/esm/src/mcp/prompts/week-planning.js +34 -0
  47. package/esm/src/mcp/server.d.ts +15 -0
  48. package/esm/src/mcp/server.d.ts.map +1 -0
  49. package/esm/src/mcp/server.js +51 -0
  50. package/esm/src/mcp/tools/get-courses.d.ts +11 -0
  51. package/esm/src/mcp/tools/get-courses.d.ts.map +1 -0
  52. package/esm/src/mcp/tools/get-courses.js +29 -0
  53. package/esm/src/mcp/tools/get-due-this-week.d.ts +13 -0
  54. package/esm/src/mcp/tools/get-due-this-week.d.ts.map +1 -0
  55. package/esm/src/mcp/tools/get-due-this-week.js +75 -0
  56. package/esm/src/mcp/tools/get-missing-assignments.d.ts +12 -0
  57. package/esm/src/mcp/tools/get-missing-assignments.d.ts.map +1 -0
  58. package/esm/src/mcp/tools/get-missing-assignments.js +33 -0
  59. package/esm/src/mcp/tools/get-stats.d.ts +12 -0
  60. package/esm/src/mcp/tools/get-stats.d.ts.map +1 -0
  61. package/esm/src/mcp/tools/get-stats.js +28 -0
  62. package/esm/src/mcp/tools/get-todo.d.ts +13 -0
  63. package/esm/src/mcp/tools/get-todo.d.ts.map +1 -0
  64. package/esm/src/mcp/tools/get-todo.js +55 -0
  65. package/esm/src/mcp/tools/get-unsubmitted-past-due.d.ts +12 -0
  66. package/esm/src/mcp/tools/get-unsubmitted-past-due.d.ts.map +1 -0
  67. package/esm/src/mcp/tools/get-unsubmitted-past-due.js +64 -0
  68. package/esm/src/mcp/tools/get-upcoming-assignments.d.ts +12 -0
  69. package/esm/src/mcp/tools/get-upcoming-assignments.d.ts.map +1 -0
  70. package/esm/src/mcp/tools/get-upcoming-assignments.js +29 -0
  71. package/esm/src/mcp/tools/index.d.ts +18 -0
  72. package/esm/src/mcp/tools/index.d.ts.map +1 -0
  73. package/esm/src/mcp/tools/index.js +26 -0
  74. package/esm/src/mcp/tools/list-assignments.d.ts +13 -0
  75. package/esm/src/mcp/tools/list-assignments.d.ts.map +1 -0
  76. package/esm/src/mcp/tools/list-assignments.js +40 -0
  77. package/esm/src/mcp/types.d.ts +83 -0
  78. package/esm/src/mcp/types.d.ts.map +1 -0
  79. package/esm/src/mcp/types.js +20 -0
  80. package/esm/src/types/canvas.d.ts +288 -0
  81. package/esm/src/types/canvas.d.ts.map +1 -0
  82. package/esm/src/types/canvas.js +5 -0
  83. package/esm/src/utils/config.d.ts +19 -0
  84. package/esm/src/utils/config.d.ts.map +1 -0
  85. package/esm/src/utils/config.js +54 -0
  86. package/esm/src/utils/init.d.ts +9 -0
  87. package/esm/src/utils/init.d.ts.map +1 -0
  88. package/esm/src/utils/init.js +20 -0
  89. package/manifest.json +91 -0
  90. package/package.json +49 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Canvas Assignments API
3
+ */
4
+ import type { Assignment, ListAssignmentsOptions } from "../types/canvas.js";
5
+ /**
6
+ * List assignments for a course
7
+ */
8
+ export declare function listAssignments(options: ListAssignmentsOptions): Promise<Assignment[]>;
9
+ /**
10
+ * Get a single assignment by ID
11
+ */
12
+ export declare function getAssignment(courseId: number, assignmentId: number, include?: Array<"submission" | "assignment_visibility" | "all_dates" | "overrides">): Promise<Assignment>;
13
+ /**
14
+ * List assignments due within a date range
15
+ */
16
+ export declare function listAssignmentsDueInRange(courseId: number, startDate: Date, endDate: Date): Promise<Assignment[]>;
17
+ /**
18
+ * List assignments due this week
19
+ */
20
+ export declare function listAssignmentsDueThisWeek(courseId: number): Promise<Assignment[]>;
21
+ /**
22
+ * List upcoming assignments (next N days)
23
+ */
24
+ export declare function listUpcomingAssignments(courseId: number, days?: number): Promise<Assignment[]>;
25
+ /**
26
+ * List overdue assignments
27
+ */
28
+ export declare function listOverdueAssignments(courseId: number): Promise<Assignment[]>;
29
+ /**
30
+ * List unsubmitted assignments that are past their due date
31
+ * This catches items that Canvas hasn't flagged as "missing" yet
32
+ */
33
+ export declare function listUnsubmittedPastDue(courseId: number): Promise<Assignment[]>;
34
+ /**
35
+ * List unsubmitted past-due assignments across multiple courses
36
+ */
37
+ export declare function listUnsubmittedPastDueForCourses(courseIds: number[]): Promise<(Assignment & {
38
+ course_name?: string;
39
+ })[]>;
40
+ //# sourceMappingURL=assignments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignments.d.ts","sourceRoot":"","sources":["../../../src/src/api/assignments.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE7E;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAoB5F;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,uBAAuB,GAAG,WAAW,GAAG,WAAW,CAAC,GAClF,OAAO,CAAC,UAAU,CAAC,CASrB;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,IAAI,EACf,OAAO,EAAE,IAAI,GACZ,OAAO,CAAC,UAAU,EAAE,CAAC,CAYvB;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAWxF;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,MAAU,GACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAMvB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAMpF;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwBpF;AAED;;GAEG;AACH,wBAAsB,gCAAgC,CACpD,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,CAAC,UAAU,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAAC,CAiBpD"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Canvas Assignments API
3
+ */
4
+ import { getClient } from "./client.js";
5
+ /**
6
+ * List assignments for a course
7
+ */
8
+ export async function listAssignments(options) {
9
+ const client = getClient();
10
+ const { course_id, ...rest } = options;
11
+ const params = {};
12
+ if (rest.bucket) {
13
+ params.bucket = rest.bucket;
14
+ }
15
+ if (rest.order_by) {
16
+ params.order_by = rest.order_by;
17
+ }
18
+ if (rest.include) {
19
+ params.include = rest.include;
20
+ }
21
+ if (rest.search_term) {
22
+ params.search_term = rest.search_term;
23
+ }
24
+ return client.getAll(`/courses/${course_id}/assignments`, params);
25
+ }
26
+ /**
27
+ * Get a single assignment by ID
28
+ */
29
+ export async function getAssignment(courseId, assignmentId, include) {
30
+ const client = getClient();
31
+ const params = {};
32
+ if (include) {
33
+ params.include = include;
34
+ }
35
+ return client.get(`/courses/${courseId}/assignments/${assignmentId}`, params);
36
+ }
37
+ /**
38
+ * List assignments due within a date range
39
+ */
40
+ export async function listAssignmentsDueInRange(courseId, startDate, endDate) {
41
+ const assignments = await listAssignments({
42
+ course_id: courseId,
43
+ include: ["submission"],
44
+ order_by: "due_at",
45
+ });
46
+ return assignments.filter((assignment) => {
47
+ if (!assignment.due_at)
48
+ return false;
49
+ const dueDate = new Date(assignment.due_at);
50
+ return dueDate >= startDate && dueDate <= endDate;
51
+ });
52
+ }
53
+ /**
54
+ * List assignments due this week
55
+ */
56
+ export async function listAssignmentsDueThisWeek(courseId) {
57
+ const now = new Date();
58
+ const startOfWeek = new Date(now);
59
+ startOfWeek.setDate(now.getDate() - now.getDay());
60
+ startOfWeek.setHours(0, 0, 0, 0);
61
+ const endOfWeek = new Date(startOfWeek);
62
+ endOfWeek.setDate(startOfWeek.getDate() + 7);
63
+ endOfWeek.setHours(23, 59, 59, 999);
64
+ return listAssignmentsDueInRange(courseId, startOfWeek, endOfWeek);
65
+ }
66
+ /**
67
+ * List upcoming assignments (next N days)
68
+ */
69
+ export async function listUpcomingAssignments(courseId, days = 7) {
70
+ const now = new Date();
71
+ const endDate = new Date(now);
72
+ endDate.setDate(now.getDate() + days);
73
+ return listAssignmentsDueInRange(courseId, now, endDate);
74
+ }
75
+ /**
76
+ * List overdue assignments
77
+ */
78
+ export async function listOverdueAssignments(courseId) {
79
+ return listAssignments({
80
+ course_id: courseId,
81
+ bucket: "overdue",
82
+ include: ["submission"],
83
+ });
84
+ }
85
+ /**
86
+ * List unsubmitted assignments that are past their due date
87
+ * This catches items that Canvas hasn't flagged as "missing" yet
88
+ */
89
+ export async function listUnsubmittedPastDue(courseId) {
90
+ const assignments = await listAssignments({
91
+ course_id: courseId,
92
+ include: ["submission"],
93
+ order_by: "due_at",
94
+ });
95
+ const now = new Date();
96
+ return assignments.filter((assignment) => {
97
+ // Must have a due date
98
+ if (!assignment.due_at)
99
+ return false;
100
+ // Due date must be in the past
101
+ const dueDate = new Date(assignment.due_at);
102
+ if (dueDate >= now)
103
+ return false;
104
+ // Must not be submitted
105
+ const submission = assignment.submission;
106
+ if (!submission)
107
+ return true; // No submission record at all
108
+ if (!submission.submitted_at)
109
+ return true; // Has record but not submitted
110
+ return false;
111
+ });
112
+ }
113
+ /**
114
+ * List unsubmitted past-due assignments across multiple courses
115
+ */
116
+ export async function listUnsubmittedPastDueForCourses(courseIds) {
117
+ const results = [];
118
+ for (const courseId of courseIds) {
119
+ try {
120
+ const assignments = await listUnsubmittedPastDue(courseId);
121
+ results.push(...assignments);
122
+ }
123
+ catch {
124
+ // Skip courses we can't access
125
+ }
126
+ }
127
+ // Sort by due date (most recent first)
128
+ return results.sort((a, b) => {
129
+ if (!a.due_at || !b.due_at)
130
+ return 0;
131
+ return new Date(b.due_at).getTime() - new Date(a.due_at).getTime();
132
+ });
133
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Canvas API Client
3
+ * Handles authentication, pagination, and HTTP requests
4
+ */
5
+ import type { PaginationLinks } from "../types/canvas.js";
6
+ export interface ClientOptions {
7
+ baseUrl: string;
8
+ apiToken: string;
9
+ /** Items per page for paginated requests (default: 100) */
10
+ perPage?: number;
11
+ }
12
+ export declare class CanvasClient {
13
+ private baseUrl;
14
+ private apiToken;
15
+ private perPage;
16
+ constructor(options: ClientOptions);
17
+ /**
18
+ * Build the full URL for an API endpoint
19
+ */
20
+ private buildUrl;
21
+ /**
22
+ * Parse Link header for pagination
23
+ */
24
+ private parseLinkHeader;
25
+ /**
26
+ * Make an authenticated request to the Canvas API
27
+ */
28
+ fetch<T>(path: string, options?: {
29
+ method?: string;
30
+ params?: Record<string, string | string[] | number | boolean | undefined>;
31
+ body?: unknown;
32
+ }): Promise<{
33
+ data: T;
34
+ links: PaginationLinks;
35
+ }>;
36
+ /**
37
+ * Fetch all pages of a paginated endpoint
38
+ */
39
+ fetchAll<T>(path: string, options?: {
40
+ params?: Record<string, string | string[] | number | boolean | undefined>;
41
+ /** Maximum number of items to fetch (default: unlimited) */
42
+ limit?: number;
43
+ }): Promise<T[]>;
44
+ /**
45
+ * Convenience method for GET requests
46
+ */
47
+ get<T>(path: string, params?: Record<string, string | string[] | number | boolean | undefined>): Promise<T>;
48
+ /**
49
+ * Convenience method for GET requests that return arrays (with pagination)
50
+ */
51
+ getAll<T>(path: string, params?: Record<string, string | string[] | number | boolean | undefined>): Promise<T[]>;
52
+ }
53
+ /**
54
+ * Initialize the singleton client (call once at startup)
55
+ */
56
+ export declare function initClient(options: ClientOptions): CanvasClient;
57
+ /**
58
+ * Get the singleton client instance
59
+ */
60
+ export declare function getClient(): CanvasClient;
61
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/api/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAY,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,aAAa;IAMlC;;OAEG;IACH,OAAO,CAAC,QAAQ;IAwBhB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACG,KAAK,CAAC,CAAC,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;QAC1E,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,GACA,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,eAAe,CAAA;KAAE,CAAC;IA4C/C;;OAEG;IACG,QAAQ,CAAC,CAAC,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;QAC1E,4DAA4D;QAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,OAAO,CAAC,CAAC,EAAE,CAAC;IA6Cf;;OAEG;IACG,GAAG,CAAC,CAAC,EACT,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GACxE,OAAO,CAAC,CAAC,CAAC;IAKb;;OAEG;IACG,MAAM,CAAC,CAAC,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GACxE,OAAO,CAAC,CAAC,EAAE,CAAC;CAGhB;AAKD;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAG/D;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAKxC"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Canvas API Client
3
+ * Handles authentication, pagination, and HTTP requests
4
+ */
5
+ export class CanvasClient {
6
+ constructor(options) {
7
+ Object.defineProperty(this, "baseUrl", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: void 0
12
+ });
13
+ Object.defineProperty(this, "apiToken", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: void 0
18
+ });
19
+ Object.defineProperty(this, "perPage", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: void 0
24
+ });
25
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
26
+ this.apiToken = options.apiToken;
27
+ this.perPage = options.perPage || 100;
28
+ }
29
+ /**
30
+ * Build the full URL for an API endpoint
31
+ */
32
+ buildUrl(path, params) {
33
+ const url = new URL(`${this.baseUrl}/api/v1${path}`);
34
+ // Add per_page by default for list endpoints
35
+ url.searchParams.set("per_page", String(this.perPage));
36
+ if (params) {
37
+ for (const [key, value] of Object.entries(params)) {
38
+ if (value === undefined)
39
+ continue;
40
+ if (Array.isArray(value)) {
41
+ // Handle array params like include[]=foo&include[]=bar
42
+ for (const v of value) {
43
+ url.searchParams.append(`${key}[]`, v);
44
+ }
45
+ }
46
+ else {
47
+ url.searchParams.set(key, String(value));
48
+ }
49
+ }
50
+ }
51
+ return url.toString();
52
+ }
53
+ /**
54
+ * Parse Link header for pagination
55
+ */
56
+ parseLinkHeader(linkHeader) {
57
+ if (!linkHeader)
58
+ return {};
59
+ const links = {};
60
+ const parts = linkHeader.split(",");
61
+ for (const part of parts) {
62
+ const match = part.match(/<([^>]+)>;\s*rel="([^"]+)"/);
63
+ if (match) {
64
+ const [, url, rel] = match;
65
+ links[rel] = url;
66
+ }
67
+ }
68
+ return links;
69
+ }
70
+ /**
71
+ * Make an authenticated request to the Canvas API
72
+ */
73
+ async fetch(path, options) {
74
+ const { method = "GET", params, body } = options || {};
75
+ const url = this.buildUrl(path, params);
76
+ const headers = {
77
+ Authorization: `Bearer ${this.apiToken}`,
78
+ Accept: "application/json",
79
+ };
80
+ const fetchOptions = {
81
+ method,
82
+ headers,
83
+ };
84
+ if (body) {
85
+ headers["Content-Type"] = "application/json";
86
+ fetchOptions.body = JSON.stringify(body);
87
+ }
88
+ const response = await fetch(url, fetchOptions);
89
+ if (!response.ok) {
90
+ let errorMessage = `Canvas API error: ${response.status} ${response.statusText}`;
91
+ try {
92
+ const errorData = await response.json();
93
+ if (errorData.message) {
94
+ errorMessage = errorData.message;
95
+ }
96
+ else if (errorData.errors?.length) {
97
+ errorMessage = errorData.errors.map((e) => e.message).join(", ");
98
+ }
99
+ }
100
+ catch {
101
+ // Couldn't parse error body, use default message
102
+ }
103
+ throw new Error(errorMessage);
104
+ }
105
+ const data = await response.json();
106
+ const links = this.parseLinkHeader(response.headers.get("Link"));
107
+ return { data, links };
108
+ }
109
+ /**
110
+ * Fetch all pages of a paginated endpoint
111
+ */
112
+ async fetchAll(path, options) {
113
+ const { params, limit } = options || {};
114
+ const results = [];
115
+ let url = this.buildUrl(path, params);
116
+ while (url) {
117
+ const response = await fetch(url, {
118
+ headers: {
119
+ Authorization: `Bearer ${this.apiToken}`,
120
+ Accept: "application/json",
121
+ },
122
+ });
123
+ if (!response.ok) {
124
+ let errorMessage = `Canvas API error: ${response.status} ${response.statusText}`;
125
+ try {
126
+ const errorData = await response.json();
127
+ if (errorData.message) {
128
+ errorMessage = errorData.message;
129
+ }
130
+ }
131
+ catch {
132
+ // Couldn't parse error body
133
+ }
134
+ throw new Error(errorMessage);
135
+ }
136
+ const data = await response.json();
137
+ results.push(...data);
138
+ // Check if we've hit the limit
139
+ if (limit && results.length >= limit) {
140
+ return results.slice(0, limit);
141
+ }
142
+ // Get next page URL from Link header
143
+ const links = this.parseLinkHeader(response.headers.get("Link"));
144
+ url = links.next;
145
+ }
146
+ return results;
147
+ }
148
+ /**
149
+ * Convenience method for GET requests
150
+ */
151
+ async get(path, params) {
152
+ const { data } = await this.fetch(path, { params });
153
+ return data;
154
+ }
155
+ /**
156
+ * Convenience method for GET requests that return arrays (with pagination)
157
+ */
158
+ async getAll(path, params) {
159
+ return this.fetchAll(path, { params });
160
+ }
161
+ }
162
+ // Singleton instance for CLI usage
163
+ let clientInstance = null;
164
+ /**
165
+ * Initialize the singleton client (call once at startup)
166
+ */
167
+ export function initClient(options) {
168
+ clientInstance = new CanvasClient(options);
169
+ return clientInstance;
170
+ }
171
+ /**
172
+ * Get the singleton client instance
173
+ */
174
+ export function getClient() {
175
+ if (!clientInstance) {
176
+ throw new Error("Canvas client not initialized. Call initClient() first.");
177
+ }
178
+ return clientInstance;
179
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canvas Courses API
3
+ */
4
+ import type { Course, Enrollment, ListCoursesOptions } from "../types/canvas.js";
5
+ /**
6
+ * List all courses for the authenticated user
7
+ */
8
+ export declare function listCourses(options?: ListCoursesOptions): Promise<Course[]>;
9
+ /**
10
+ * Get a single course by ID
11
+ */
12
+ export declare function getCourse(courseId: number): Promise<Course>;
13
+ /**
14
+ * List enrollments for a course (includes grade info)
15
+ */
16
+ export declare function listCourseEnrollments(courseId: number, options?: {
17
+ type?: string[];
18
+ state?: string[];
19
+ userId?: number | string;
20
+ }): Promise<Enrollment[]>;
21
+ /**
22
+ * Get enrollment with grades for a specific user in a course
23
+ */
24
+ export declare function getUserEnrollment(courseId: number, userId: number | string): Promise<Enrollment | null>;
25
+ /**
26
+ * List courses with grades for a user (observer or self)
27
+ * Enriches course data with enrollment/grade information
28
+ */
29
+ export declare function listCoursesWithGrades(userId?: string | number): Promise<(Course & {
30
+ enrollment?: Enrollment;
31
+ })[]>;
32
+ //# sourceMappingURL=courses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"courses.d.ts","sourceRoot":"","sources":["../../../src/src/api/courses.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEjF;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBjF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGjE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,UAAU,EAAE,CAAC,CAgBvB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,GACtB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAO5B;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,GAAE,MAAM,GAAG,MAAe,GAC/B,OAAO,CAAC,CAAC,MAAM,GAAG;IAAE,UAAU,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC,EAAE,CAAC,CAyBnD"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Canvas Courses API
3
+ */
4
+ import { getClient } from "./client.js";
5
+ /**
6
+ * List all courses for the authenticated user
7
+ */
8
+ export async function listCourses(options) {
9
+ const client = getClient();
10
+ const params = {};
11
+ if (options?.enrollment_type) {
12
+ params.enrollment_type = options.enrollment_type;
13
+ }
14
+ if (options?.enrollment_state) {
15
+ params.enrollment_state = options.enrollment_state;
16
+ }
17
+ if (options?.include) {
18
+ params.include = options.include;
19
+ }
20
+ if (options?.state) {
21
+ params.state = options.state;
22
+ }
23
+ return client.getAll("/courses", params);
24
+ }
25
+ /**
26
+ * Get a single course by ID
27
+ */
28
+ export async function getCourse(courseId) {
29
+ const client = getClient();
30
+ return client.get(`/courses/${courseId}`);
31
+ }
32
+ /**
33
+ * List enrollments for a course (includes grade info)
34
+ */
35
+ export async function listCourseEnrollments(courseId, options) {
36
+ const client = getClient();
37
+ const params = {};
38
+ if (options?.type) {
39
+ params.type = options.type;
40
+ }
41
+ if (options?.state) {
42
+ params.state = options.state;
43
+ }
44
+ if (options?.userId) {
45
+ params.user_id = options.userId;
46
+ }
47
+ return client.getAll(`/courses/${courseId}/enrollments`, params);
48
+ }
49
+ /**
50
+ * Get enrollment with grades for a specific user in a course
51
+ */
52
+ export async function getUserEnrollment(courseId, userId) {
53
+ const enrollments = await listCourseEnrollments(courseId, {
54
+ userId,
55
+ type: ["StudentEnrollment"],
56
+ });
57
+ return enrollments[0] || null;
58
+ }
59
+ /**
60
+ * List courses with grades for a user (observer or self)
61
+ * Enriches course data with enrollment/grade information
62
+ */
63
+ export async function listCoursesWithGrades(userId = "self") {
64
+ // Get all active courses with enrollments included
65
+ const courses = await listCourses({
66
+ enrollment_state: "active",
67
+ include: ["enrollments", "term"],
68
+ state: ["available"],
69
+ });
70
+ // Filter and enrich with the specific user's enrollment data
71
+ const coursesWithGrades = courses.map((course) => {
72
+ // Find the enrollment for this user (or observed user)
73
+ const enrollment = course.enrollments?.find((e) => {
74
+ if (userId === "self") {
75
+ return e.type === "StudentEnrollment" || e.type === "ObserverEnrollment";
76
+ }
77
+ return e.user_id === Number(userId) || e.observed_user?.id === Number(userId);
78
+ });
79
+ return {
80
+ ...course,
81
+ enrollment,
82
+ };
83
+ });
84
+ return coursesWithGrades;
85
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Student statistics API
3
+ * Calculates late/missing assignment statistics by course
4
+ */
5
+ export interface CourseStats {
6
+ course_id: number;
7
+ course_name: string;
8
+ total: number;
9
+ late: number;
10
+ missing: number;
11
+ late_pct: number;
12
+ missing_pct: number;
13
+ }
14
+ export declare function getStudentStats(studentId: string | number): Promise<CourseStats[]>;
15
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../../src/src/api/stats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAyDxF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Student statistics API
3
+ * Calculates late/missing assignment statistics by course
4
+ */
5
+ import { listCourses } from "./courses.js";
6
+ import { listSubmissions } from "./submissions.js";
7
+ import { getMissingSubmissions } from "./users.js";
8
+ export async function getStudentStats(studentId) {
9
+ const courses = await listCourses({
10
+ enrollment_state: "active",
11
+ state: ["available"],
12
+ });
13
+ // Get missing assignments from Canvas
14
+ const missing = await getMissingSubmissions({
15
+ studentId,
16
+ include: ["course"],
17
+ });
18
+ // Count missing by course
19
+ const missingByCourse = new Map();
20
+ for (const m of missing) {
21
+ missingByCourse.set(m.course_id, (missingByCourse.get(m.course_id) || 0) + 1);
22
+ }
23
+ const stats = [];
24
+ for (const course of courses) {
25
+ try {
26
+ const subs = await listSubmissions({
27
+ course_id: course.id,
28
+ student_ids: [Number(studentId)],
29
+ include: ["assignment"],
30
+ });
31
+ let total = 0;
32
+ let late = 0;
33
+ for (const sub of subs) {
34
+ if (sub.assignment === null || sub.assignment === undefined)
35
+ continue;
36
+ total++;
37
+ if (sub.late)
38
+ late++;
39
+ }
40
+ const missingCount = missingByCourse.get(course.id) || 0;
41
+ stats.push({
42
+ course_id: course.id,
43
+ course_name: course.name,
44
+ total,
45
+ late,
46
+ missing: missingCount,
47
+ late_pct: total > 0 ? Math.round((late / total) * 1000) / 10 : 0,
48
+ missing_pct: total > 0 ? Math.round((missingCount / total) * 1000) / 10 : 0,
49
+ });
50
+ }
51
+ catch {
52
+ // Skip courses we can't access
53
+ }
54
+ }
55
+ // Sort by missing percentage descending (worst first)
56
+ stats.sort((a, b) => b.missing_pct - a.missing_pct || b.late_pct - a.late_pct);
57
+ return stats;
58
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Canvas Submissions API
3
+ */
4
+ import type { Submission, ListSubmissionsOptions } from "../types/canvas.js";
5
+ /**
6
+ * List submissions for a course (all students or specific students)
7
+ */
8
+ export declare function listSubmissions(options: ListSubmissionsOptions): Promise<Submission[]>;
9
+ /**
10
+ * Get submission for a specific assignment and user
11
+ */
12
+ export declare function getSubmission(courseId: number, assignmentId: number, userId: number | string, include?: Array<"submission_history" | "submission_comments" | "rubric_assessment">): Promise<Submission>;
13
+ /**
14
+ * List all submissions for a specific user in a course
15
+ */
16
+ export declare function listUserSubmissions(courseId: number, userId: number | string, options?: {
17
+ include?: Array<"submission_history" | "submission_comments" | "assignment" | "user">;
18
+ workflowState?: "submitted" | "unsubmitted" | "graded" | "pending_review";
19
+ }): Promise<Submission[]>;
20
+ /**
21
+ * List graded submissions for a user
22
+ */
23
+ export declare function listGradedSubmissions(courseId: number, userId: number | string): Promise<Submission[]>;
24
+ /**
25
+ * List submissions with grades below a threshold
26
+ */
27
+ export declare function listSubmissionsBelowThreshold(courseId: number, userId: number | string, threshold: number): Promise<Submission[]>;
28
+ /**
29
+ * List unsubmitted past-due assignments for a student
30
+ * Uses the submissions endpoint to get the student's actual submission status
31
+ */
32
+ export declare function listUnsubmittedPastDueForStudent(courseId: number, studentId: number | string): Promise<Submission[]>;
33
+ //# sourceMappingURL=submissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissions.d.ts","sourceRoot":"","sources":["../../../src/src/api/submissions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE7E;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAuB5F;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,CAAC,EAAE,KAAK,CAAC,oBAAoB,GAAG,qBAAqB,GAAG,mBAAmB,CAAC,GAClF,OAAO,CAAC,UAAU,CAAC,CAYrB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,KAAK,CAAC,oBAAoB,GAAG,qBAAqB,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;IACtF,aAAa,CAAC,EAAE,WAAW,GAAG,aAAa,GAAG,QAAQ,GAAG,gBAAgB,CAAC;CAC3E,GACA,OAAO,CAAC,UAAU,EAAE,CAAC,CAOvB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,GACtB,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED;;GAEG;AACH,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAWvB;AAED;;;GAGG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,MAAM,GACzB,OAAO,CAAC,UAAU,EAAE,CAAC,CAwBvB"}