canvas-design-mcp 0.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.
Files changed (118) hide show
  1. package/CLAUDE.md +200 -0
  2. package/DESIGN.md +288 -0
  3. package/PROFESSOR-INSTRUCTIONS.txt +131 -0
  4. package/README.md +250 -0
  5. package/dist/canvas-api.d.ts +24 -0
  6. package/dist/canvas-api.d.ts.map +1 -0
  7. package/dist/canvas-api.js +146 -0
  8. package/dist/canvas-api.js.map +1 -0
  9. package/dist/config.d.ts +5 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +22 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/design-engine.d.ts +5 -0
  14. package/dist/design-engine.d.ts.map +1 -0
  15. package/dist/design-engine.js +23 -0
  16. package/dist/design-engine.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +567 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/tools/accessibility.d.ts +7 -0
  22. package/dist/tools/accessibility.d.ts.map +1 -0
  23. package/dist/tools/accessibility.js +149 -0
  24. package/dist/tools/accessibility.js.map +1 -0
  25. package/dist/tools/contrast.d.ts +2 -0
  26. package/dist/tools/contrast.d.ts.map +1 -0
  27. package/dist/tools/contrast.js +7 -0
  28. package/dist/tools/contrast.js.map +1 -0
  29. package/dist/tools/critique.d.ts +23 -0
  30. package/dist/tools/critique.d.ts.map +1 -0
  31. package/dist/tools/critique.js +194 -0
  32. package/dist/tools/critique.js.map +1 -0
  33. package/dist/tools/generate.d.ts +18 -0
  34. package/dist/tools/generate.d.ts.map +1 -0
  35. package/dist/tools/generate.js +101 -0
  36. package/dist/tools/generate.js.map +1 -0
  37. package/dist/tools/gotchas.d.ts +7 -0
  38. package/dist/tools/gotchas.d.ts.map +1 -0
  39. package/dist/tools/gotchas.js +61 -0
  40. package/dist/tools/gotchas.js.map +1 -0
  41. package/dist/tools/ingest.d.ts +44 -0
  42. package/dist/tools/ingest.d.ts.map +1 -0
  43. package/dist/tools/ingest.js +211 -0
  44. package/dist/tools/ingest.js.map +1 -0
  45. package/dist/tools/list-courses.d.ts +16 -0
  46. package/dist/tools/list-courses.d.ts.map +1 -0
  47. package/dist/tools/list-courses.js +79 -0
  48. package/dist/tools/list-courses.js.map +1 -0
  49. package/dist/tools/page-io.d.ts +19 -0
  50. package/dist/tools/page-io.d.ts.map +1 -0
  51. package/dist/tools/page-io.js +77 -0
  52. package/dist/tools/page-io.js.map +1 -0
  53. package/dist/tools/panopto.d.ts +36 -0
  54. package/dist/tools/panopto.d.ts.map +1 -0
  55. package/dist/tools/panopto.js +188 -0
  56. package/dist/tools/panopto.js.map +1 -0
  57. package/dist/tools/personas.d.ts +21 -0
  58. package/dist/tools/personas.d.ts.map +1 -0
  59. package/dist/tools/personas.js +464 -0
  60. package/dist/tools/personas.js.map +1 -0
  61. package/dist/tools/philosophy.d.ts +21 -0
  62. package/dist/tools/philosophy.d.ts.map +1 -0
  63. package/dist/tools/philosophy.js +137 -0
  64. package/dist/tools/philosophy.js.map +1 -0
  65. package/dist/tools/publish.d.ts +31 -0
  66. package/dist/tools/publish.d.ts.map +1 -0
  67. package/dist/tools/publish.js +198 -0
  68. package/dist/tools/publish.js.map +1 -0
  69. package/dist/tools/redesign.d.ts +18 -0
  70. package/dist/tools/redesign.d.ts.map +1 -0
  71. package/dist/tools/redesign.js +68 -0
  72. package/dist/tools/redesign.js.map +1 -0
  73. package/dist/tools/update-kb.d.ts +10 -0
  74. package/dist/tools/update-kb.d.ts.map +1 -0
  75. package/dist/tools/update-kb.js +93 -0
  76. package/dist/tools/update-kb.js.map +1 -0
  77. package/dist/tools/validate.d.ts +10 -0
  78. package/dist/tools/validate.d.ts.map +1 -0
  79. package/dist/tools/validate.js +76 -0
  80. package/dist/tools/validate.js.map +1 -0
  81. package/dist/types.d.ts +65 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +2 -0
  84. package/dist/types.js.map +1 -0
  85. package/dist/wizard.d.ts +3 -0
  86. package/dist/wizard.d.ts.map +1 -0
  87. package/dist/wizard.js +229 -0
  88. package/dist/wizard.js.map +1 -0
  89. package/docs/canvas-design-kb/00-meta/Changelog.md +42 -0
  90. package/docs/canvas-design-kb/00-meta/Contributing.md +58 -0
  91. package/docs/canvas-design-kb/00-meta/KB-Overview.md +51 -0
  92. package/docs/canvas-design-kb/01-canvas-rce/CSS-Inline-Strategy.md +166 -0
  93. package/docs/canvas-design-kb/01-canvas-rce/Canvas-Built-In-CSS-Classes.md +243 -0
  94. package/docs/canvas-design-kb/01-canvas-rce/Canvas-Page-Types.md +59 -0
  95. package/docs/canvas-design-kb/01-canvas-rce/HTML-Allowlist.md +204 -0
  96. package/docs/canvas-design-kb/01-canvas-rce/RCE-Limitations-and-Workarounds.md +151 -0
  97. package/docs/canvas-design-kb/01-canvas-rce/RCE-Overview.md +92 -0
  98. package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Canvas-Template.md +323 -0
  99. package/docs/canvas-design-kb/02-design-md/DESIGN-MD-File-Structure.md +245 -0
  100. package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Overview.md +120 -0
  101. package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Toolchain.md +234 -0
  102. package/docs/canvas-design-kb/03-design-systems/Color-and-Typography.md +146 -0
  103. package/docs/canvas-design-kb/03-design-systems/Component-Library.md +299 -0
  104. package/docs/canvas-design-kb/03-design-systems/Design-System-Principles.md +99 -0
  105. package/docs/canvas-design-kb/04-tools/Canvas-Theme-Editor.md +40 -0
  106. package/docs/canvas-design-kb/04-tools/Other-Canvas-Design-Tools.md +47 -0
  107. package/docs/canvas-design-kb/05-patterns/Course-Home-Page.md +224 -0
  108. package/docs/canvas-design-kb/06-accessibility/Accessibility-Overview.md +128 -0
  109. package/docs/canvas-design-kb/07-resources/Inspiration-and-Showcases.md +121 -0
  110. package/docs/canvas-design-kb/07-resources/Official-Canvas-Links.md +54 -0
  111. package/docs/canvas-design-kb/README.md +58 -0
  112. package/docs/feature-roadmap.md +123 -0
  113. package/docs/installation.md +340 -0
  114. package/package.json +45 -0
  115. package/scripts/deploy-public.ps1 +68 -0
  116. package/src/kb/design-principles.md +45 -0
  117. package/src/templates/ignite-assignment-page.html +203 -0
  118. package/src/templates/two-column-dashboard.html +33 -0
@@ -0,0 +1,44 @@
1
+ import type { InstitutionConfig } from '../types.js';
2
+ export interface CourseInfo {
3
+ institution: string;
4
+ professor: string;
5
+ courseNumber: string;
6
+ courseName: string;
7
+ assignmentNumber: string;
8
+ semester: string;
9
+ }
10
+ export interface IngestAssignmentFolderInput {
11
+ folderPath?: string;
12
+ }
13
+ export interface IngestAssignmentFolderResult {
14
+ html: string;
15
+ filename: string;
16
+ heroImagePrompt?: string;
17
+ courseInfo: CourseInfo;
18
+ sources: {
19
+ brief: string;
20
+ rubric?: string;
21
+ shell?: string;
22
+ styleNotes?: string;
23
+ sourceMap: {
24
+ courseConfig: string;
25
+ brief: string;
26
+ rubric?: string;
27
+ shell?: string;
28
+ styleNotes?: string;
29
+ };
30
+ };
31
+ warnings: string[];
32
+ }
33
+ export declare function parseCourseConfig(content: string): Partial<CourseInfo>;
34
+ export declare function validateCourseInfo(info: CourseInfo): string[];
35
+ export declare function findFileWithInheritance(filename: string, folderPath: string): {
36
+ content: string;
37
+ resolvedPath: string;
38
+ } | null;
39
+ export declare function findCourseConfig(folderPath: string): {
40
+ merged: Partial<CourseInfo>;
41
+ resolvedPath: string;
42
+ };
43
+ export declare function ingestAssignmentFolder(input: IngestAssignmentFolderInput, config: InstitutionConfig): IngestAssignmentFolderResult;
44
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/tools/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE;YACT,YAAY,EAAE,MAAM,CAAC;YACrB,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAmBD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAatE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,CAW7D;AAuDD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAalD;AAWD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,GACjB;IAAE,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CA+BvD;AAID,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,EAClC,MAAM,EAAE,iBAAiB,GACxB,4BAA4B,CA0E9B"}
@@ -0,0 +1,211 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve, join, dirname, relative, sep, isAbsolute } from 'node:path';
3
+ import { generateCanvasPage } from './generate.js';
4
+ // ─── Course config field mapping ─────────────────────────────────────────────
5
+ const FIELD_MAP = {
6
+ 'institution': 'institution',
7
+ 'professor': 'professor',
8
+ 'course number': 'courseNumber',
9
+ 'course name': 'courseName',
10
+ 'assignment number': 'assignmentNumber',
11
+ 'semester': 'semester',
12
+ };
13
+ const REQUIRED_FIELDS = [
14
+ 'institution', 'professor', 'courseNumber', 'courseName', 'assignmentNumber', 'semester',
15
+ ];
16
+ // ─── Pure functions: parsing and validation ───────────────────────────────────
17
+ export function parseCourseConfig(content) {
18
+ const result = {};
19
+ for (const line of content.split('\n')) {
20
+ if (line.trimStart().startsWith('#'))
21
+ continue;
22
+ const colonIdx = line.indexOf(':');
23
+ if (colonIdx === -1)
24
+ continue;
25
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
26
+ const value = line.slice(colonIdx + 1).trim();
27
+ if (!value)
28
+ continue;
29
+ const field = FIELD_MAP[key];
30
+ if (field)
31
+ result[field] = value;
32
+ }
33
+ return result;
34
+ }
35
+ export function validateCourseInfo(info) {
36
+ const errors = [];
37
+ for (const field of REQUIRED_FIELDS) {
38
+ const value = info[field];
39
+ if (!value) {
40
+ errors.push(`Missing required field: ${field}`);
41
+ }
42
+ else if (/^\[.+\]$/.test(value)) {
43
+ errors.push(`Placeholder not filled in: ${field} = "${value}"`);
44
+ }
45
+ }
46
+ return errors;
47
+ }
48
+ // ─── Walk helpers ─────────────────────────────────────────────────────────────
49
+ function getWalkRoot(absoluteFolderPath) {
50
+ const rel = relative(process.cwd(), absoluteFolderPath);
51
+ if (rel.startsWith('..') || isAbsolute(rel)) {
52
+ throw new Error(`Path is outside the project directory: ${absoluteFolderPath}`);
53
+ }
54
+ const firstSegment = rel.split(sep)[0];
55
+ return resolve(process.cwd(), firstSegment);
56
+ }
57
+ function walkDirs(fromDir, rootDir) {
58
+ const dirs = [];
59
+ let current = resolve(fromDir);
60
+ const root = resolve(rootDir);
61
+ while (true) {
62
+ dirs.push(current);
63
+ if (current === root)
64
+ break;
65
+ const parent = dirname(current);
66
+ if (parent === current)
67
+ break;
68
+ current = parent;
69
+ }
70
+ return dirs;
71
+ }
72
+ // ─── File discovery ────────────────────────────────────────────────────────────
73
+ function resolveFolderPath(folderPath) {
74
+ const resolved = resolve(folderPath);
75
+ const rel = relative(process.cwd(), resolved);
76
+ if (rel.startsWith('..') || isAbsolute(rel)) {
77
+ throw new Error(`Folder path must be within the project directory: ${folderPath}`);
78
+ }
79
+ if (!existsSync(resolved)) {
80
+ throw new Error(`Folder not found: ${folderPath}`);
81
+ }
82
+ return resolved;
83
+ }
84
+ function findBrief(folderPath) {
85
+ const filePath = join(folderPath, 'assignment-brief.md');
86
+ if (!existsSync(filePath)) {
87
+ throw new Error(`assignment-brief.md not found in ${relative(process.cwd(), folderPath)}. ` +
88
+ `This file is required and must be in the target folder (it is not inherited).`);
89
+ }
90
+ return {
91
+ content: readFileSync(filePath, 'utf-8'),
92
+ resolvedPath: relative(process.cwd(), filePath),
93
+ };
94
+ }
95
+ export function findFileWithInheritance(filename, folderPath) {
96
+ const root = getWalkRoot(folderPath);
97
+ const dirs = walkDirs(folderPath, root);
98
+ for (const dir of dirs) {
99
+ const filePath = join(dir, filename);
100
+ if (existsSync(filePath)) {
101
+ return {
102
+ content: readFileSync(filePath, 'utf-8'),
103
+ resolvedPath: relative(process.cwd(), filePath),
104
+ };
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ function findStyleNotes(folderPath) {
110
+ const filePath = join(folderPath, 'style-notes.md');
111
+ if (!existsSync(filePath))
112
+ return null;
113
+ return {
114
+ content: readFileSync(filePath, 'utf-8'),
115
+ resolvedPath: relative(process.cwd(), filePath),
116
+ };
117
+ }
118
+ export function findCourseConfig(folderPath) {
119
+ const root = getWalkRoot(folderPath);
120
+ const dirs = walkDirs(folderPath, root);
121
+ const configs = [];
122
+ for (const dir of dirs) {
123
+ const filePath = join(dir, 'course-config.md');
124
+ if (existsSync(filePath)) {
125
+ configs.push({
126
+ path: relative(process.cwd(), filePath),
127
+ parsed: parseCourseConfig(readFileSync(filePath, 'utf-8')),
128
+ });
129
+ }
130
+ }
131
+ if (configs.length === 0) {
132
+ throw new Error(`course-config.md not found anywhere in the folder tree from ` +
133
+ `${relative(process.cwd(), folderPath)}`);
134
+ }
135
+ // Merge: closest (first in array, lowest in tree) wins on each field
136
+ const merged = {};
137
+ for (const cfg of configs) {
138
+ for (const [key, value] of Object.entries(cfg.parsed)) {
139
+ if (!(key in merged) && value)
140
+ merged[key] = value;
141
+ }
142
+ }
143
+ return { merged, resolvedPath: configs[0].path };
144
+ }
145
+ // ─── Main exported function ───────────────────────────────────────────────────
146
+ export function ingestAssignmentFolder(input, config) {
147
+ const raw = input.folderPath?.trim();
148
+ const folderPath = resolveFolderPath(raw && raw.length > 0 ? raw : 'ingest');
149
+ // Discover files — brief is required, others are optional or inherited
150
+ const brief = findBrief(folderPath);
151
+ const rubricResult = findFileWithInheritance('rubric.md', folderPath);
152
+ const shellResult = findFileWithInheritance('shell.md', folderPath);
153
+ const styleNotesResult = findStyleNotes(folderPath);
154
+ const courseConfigResult = findCourseConfig(folderPath);
155
+ // Validate merged course config — throws descriptive error if invalid
156
+ const configErrors = validateCourseInfo(courseConfigResult.merged);
157
+ if (configErrors.length > 0) {
158
+ throw new Error(`Course config errors in ${courseConfigResult.resolvedPath}:\n` +
159
+ `${configErrors.map(e => ` • ${e}`).join('\n')}`);
160
+ }
161
+ // validateCourseInfo guarantees all fields are populated and non-placeholder
162
+ const courseInfo = courseConfigResult.merged;
163
+ // Assemble sources object
164
+ const sources = {
165
+ brief: brief.content,
166
+ rubric: rubricResult?.content,
167
+ shell: shellResult?.content,
168
+ styleNotes: styleNotesResult?.content,
169
+ sourceMap: {
170
+ courseConfig: courseConfigResult.resolvedPath,
171
+ brief: brief.resolvedPath,
172
+ rubric: rubricResult?.resolvedPath,
173
+ shell: shellResult?.resolvedPath,
174
+ styleNotes: styleNotesResult?.resolvedPath,
175
+ },
176
+ };
177
+ // Combine style-notes with shell as generation context.
178
+ // Note: generateCanvasPage() currently does not use styleNotes in HTML output —
179
+ // it is passed for forward compatibility. The shell's primary value is being
180
+ // returned in sources.shell for Claude to use when reviewing the generated page.
181
+ const combinedStyleNotes = [
182
+ sources.styleNotes,
183
+ sources.shell
184
+ ? `Existing page structure (use as a guide):\n\n${sources.shell}`
185
+ : null,
186
+ ].filter(Boolean).join('\n\n') || undefined;
187
+ const generateInput = {
188
+ assignmentBrief: sources.brief,
189
+ courseName: courseInfo.courseName,
190
+ courseNumber: courseInfo.courseNumber,
191
+ assignmentNumber: courseInfo.assignmentNumber,
192
+ professorName: courseInfo.professor,
193
+ semester: courseInfo.semester,
194
+ styleNotes: combinedStyleNotes,
195
+ };
196
+ const generated = generateCanvasPage(generateInput, config);
197
+ // Carry forward generation warnings and add any brief-level warnings
198
+ const warnings = [...generated.warnings];
199
+ if (/\[[A-Z ]{3,}\]/.test(sources.brief)) {
200
+ warnings.push('assignment-brief.md contains unfilled placeholder text — review before publishing');
201
+ }
202
+ return {
203
+ html: generated.html,
204
+ filename: generated.filename,
205
+ heroImagePrompt: generated.heroImagePrompt,
206
+ courseInfo,
207
+ sources,
208
+ warnings,
209
+ };
210
+ }
211
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/tools/ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { CanvasCourse, InstitutionConfig, SemesterFilter } from '../types.js';
2
+ export interface ListCanvasCoursesInput {
3
+ semester?: SemesterFilter;
4
+ includeFavorites?: boolean;
5
+ }
6
+ export interface ListCanvasCoursesResult {
7
+ courses: CanvasCourse[];
8
+ text: string;
9
+ }
10
+ interface CourseApi {
11
+ listCourses(enrollmentWorkflowStates?: string | string[]): Promise<CanvasCourse[]>;
12
+ }
13
+ type SaveConfig = (config: InstitutionConfig) => void;
14
+ export declare function listCanvasCourses(input: ListCanvasCoursesInput, config: InstitutionConfig, api: CourseApi, saveConfig: SaveConfig): Promise<ListCanvasCoursesResult>;
15
+ export {};
16
+ //# sourceMappingURL=list-courses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-courses.d.ts","sourceRoot":"","sources":["../../src/tools/list-courses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,SAAS;IACjB,WAAW,CAAC,wBAAwB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACpF;AAED,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAkEtD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,sBAAsB,EAC7B,MAAM,EAAE,iBAAiB,EACzB,GAAG,EAAE,SAAS,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,uBAAuB,CAAC,CAqBlC"}
@@ -0,0 +1,79 @@
1
+ import { courseCoordinatorGotcha } from './gotchas.js';
2
+ const SEMESTER_TO_ENROLLMENT_WORKFLOW_STATE = {
3
+ current: 'active',
4
+ future: ['invited', 'pending', 'creation_pending'],
5
+ past: 'completed',
6
+ all: undefined,
7
+ };
8
+ function courseNickname(course) {
9
+ return course.nickname ?? course.friendly_name;
10
+ }
11
+ function teacherNames(course) {
12
+ const names = course.teachers?.map(teacher => teacher.display_name ?? teacher.name).filter(Boolean) ?? [];
13
+ return names.length > 0 ? names.join(', ') : 'Not provided by Canvas';
14
+ }
15
+ function studentCount(course) {
16
+ if (typeof course.total_students === 'number')
17
+ return `${course.total_students} enrolled`;
18
+ return 'Not provided by Canvas';
19
+ }
20
+ function courseBlock(course, favorite) {
21
+ const nickname = courseNickname(course);
22
+ const lines = [
23
+ favorite ? 'Favorite: yes' : undefined,
24
+ `Course ID: ${course.id}`,
25
+ `Name: ${course.name}`,
26
+ nickname ? `Nickname: ${nickname}` : undefined,
27
+ `Students: ${studentCount(course)}`,
28
+ `Teachers: ${teacherNames(course)}`,
29
+ `Term: ${course.term?.name ?? 'Not provided by Canvas'}`,
30
+ ].filter(Boolean);
31
+ const gotcha = courseCoordinatorGotcha(course);
32
+ if (gotcha)
33
+ lines.push(`Warning: ${gotcha}`);
34
+ return lines.join('\n');
35
+ }
36
+ function sortCourses(courses, favoriteIds, includeFavorites) {
37
+ if (!includeFavorites)
38
+ return [...courses];
39
+ return [...courses].sort((a, b) => {
40
+ const aFavorite = favoriteIds.includes(a.id) ? 0 : 1;
41
+ const bFavorite = favoriteIds.includes(b.id) ? 0 : 1;
42
+ if (aFavorite !== bFavorite)
43
+ return aFavorite - bFavorite;
44
+ return a.name.localeCompare(b.name);
45
+ });
46
+ }
47
+ function missingTokenText(canvasUrl) {
48
+ return [
49
+ 'No Canvas API token configured, so Canvas courses cannot be listed yet.',
50
+ 'You can still generate HTML and paste it into Canvas manually.',
51
+ `To enable course listing and direct publishing later, create a Canvas API token at ${canvasUrl.replace(/\/+$/, '')}/profile/settings and run setup_institution.`,
52
+ ].join('\n');
53
+ }
54
+ function namingConventionTip() {
55
+ return [
56
+ 'Tip: Canvas lets you set a course nickname visible only to you.',
57
+ 'A format like Sp26 | ITM 310-002 Business Intelligence (RANK) makes it easy to filter courses at a glance, especially when you teach multiple sections or coordinate courses you do not teach.',
58
+ ].join('\n');
59
+ }
60
+ export async function listCanvasCourses(input, config, api, saveConfig) {
61
+ if (!config.apiToken?.trim()) {
62
+ return { courses: [], text: missingTokenText(config.canvasUrl) };
63
+ }
64
+ const semester = input.semester ?? 'current';
65
+ const includeFavorites = input.includeFavorites ?? true;
66
+ const enrollmentWorkflowStates = SEMESTER_TO_ENROLLMENT_WORKFLOW_STATE[semester];
67
+ const favoriteIds = config.favoriteCourses ?? [];
68
+ const courses = sortCourses(await api.listCourses(enrollmentWorkflowStates), favoriteIds, includeFavorites);
69
+ const blocks = courses.map(course => courseBlock(course, includeFavorites && favoriteIds.includes(course.id)));
70
+ if (!config.kbTipShown) {
71
+ blocks.push(namingConventionTip());
72
+ saveConfig({ ...config, kbTipShown: true });
73
+ }
74
+ return {
75
+ courses,
76
+ text: blocks.join('\n\n'),
77
+ };
78
+ }
79
+ //# sourceMappingURL=list-courses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-courses.js","sourceRoot":"","sources":["../../src/tools/list-courses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface LoadCanvasPageInput {
2
+ filename?: string;
3
+ }
4
+ export interface LoadCanvasPageResult {
5
+ html: string;
6
+ filename: string;
7
+ }
8
+ export interface SaveCanvasPageInput {
9
+ html: string;
10
+ filename: string;
11
+ }
12
+ export interface SaveCanvasPageResult {
13
+ saved: string;
14
+ backup: string | null;
15
+ }
16
+ export declare const OUTPUT_DIR: string;
17
+ export declare function loadCanvasPage(input: LoadCanvasPageInput, outputDir?: string): LoadCanvasPageResult;
18
+ export declare function saveCanvasPage(input: SaveCanvasPageInput, outputDir?: string): SaveCanvasPageResult;
19
+ //# sourceMappingURL=page-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-io.d.ts","sourceRoot":"","sources":["../../src/tools/page-io.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAAG,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;AAC1D,MAAM,WAAW,oBAAoB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACxE,MAAM,WAAW,mBAAmB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AACvE,MAAM,WAAW,oBAAoB;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAG9E,eAAO,MAAM,UAAU,QAAgC,CAAC;AAIxD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,SAAa,GAAG,oBAAoB,CAwCvG;AAMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,SAAa,GAAG,oBAAoB,CA6BvG"}
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'node:fs';
2
+ import { join, resolve, sep } from 'node:path';
3
+ // Resolves relative to wherever the professor runs the server — same convention as ingest/.
4
+ export const OUTPUT_DIR = join(process.cwd(), 'output');
5
+ // outputDir is a parameter (not hardcoded) so tests can pass tmpdir() instead of polluting
6
+ // the real output/ directory. Same testability pattern as personas.ts (personasPath param).
7
+ export function loadCanvasPage(input, outputDir = OUTPUT_DIR) {
8
+ if (!existsSync(outputDir)) {
9
+ throw new Error('output/ directory not found. Generate a page first with generate_canvas_page.');
10
+ }
11
+ if (input.filename) {
12
+ const filePath = resolve(join(outputDir, input.filename));
13
+ if (!filePath.startsWith(resolve(outputDir) + sep)) {
14
+ throw new Error('Invalid filename: must be a plain filename, not a path.');
15
+ }
16
+ if (!existsSync(filePath)) {
17
+ throw new Error(`File not found: output/${input.filename}`);
18
+ }
19
+ try {
20
+ const html = readFileSync(filePath, 'utf-8');
21
+ return { html, filename: input.filename };
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ throw new Error(`Cannot read file: ${message}`);
26
+ }
27
+ }
28
+ // Auto-select: scan for .html files, sort by mtime descending, pick first.
29
+ const htmlFiles = readdirSync(outputDir).filter(f => f.endsWith('.html'));
30
+ if (htmlFiles.length === 0) {
31
+ throw new Error('No HTML files found in output/. Generate a page first with generate_canvas_page.');
32
+ }
33
+ const sorted = htmlFiles
34
+ .map(f => ({ name: f, mtime: statSync(join(outputDir, f)).mtimeMs }))
35
+ .sort((a, b) => b.mtime - a.mtime);
36
+ const filename = sorted[0].name;
37
+ try {
38
+ const html = readFileSync(join(outputDir, filename), 'utf-8');
39
+ return { html, filename };
40
+ }
41
+ catch (err) {
42
+ const message = err instanceof Error ? err.message : String(err);
43
+ throw new Error(`Cannot read file: ${message}`);
44
+ }
45
+ }
46
+ // outputDir parameter follows the same testability pattern as loadCanvasPage.
47
+ // mkdirSync({ recursive: true }) is used so the professor doesn't need to pre-create output/.
48
+ // Backup is written before the original is touched — the original is never clobbered
49
+ // unless copyFileSync succeeds. This protects against partial writes during errors.
50
+ export function saveCanvasPage(input, outputDir = OUTPUT_DIR) {
51
+ if (!input.html || !input.html.trim()) {
52
+ throw new Error('html must not be empty');
53
+ }
54
+ if (!input.filename || !input.filename.trim()) {
55
+ throw new Error('filename must not be empty');
56
+ }
57
+ mkdirSync(outputDir, { recursive: true });
58
+ const filePath = resolve(join(outputDir, input.filename));
59
+ if (!filePath.startsWith(resolve(outputDir) + sep)) {
60
+ throw new Error('Invalid filename: must be a plain filename, not a path.');
61
+ }
62
+ const bakPath = join(outputDir, `${input.filename}.bak`);
63
+ let backup = null;
64
+ if (existsSync(filePath)) {
65
+ try {
66
+ copyFileSync(filePath, bakPath);
67
+ backup = bakPath;
68
+ }
69
+ catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ throw new Error(`Failed to write backup: ${message}`);
72
+ }
73
+ }
74
+ writeFileSync(filePath, input.html, 'utf-8');
75
+ return { saved: filePath, backup };
76
+ }
77
+ //# sourceMappingURL=page-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-io.js","sourceRoot":"","sources":["../../src/tools/page-io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { PanoptoConfig } from '../types.js';
2
+ export interface PanoptoSearchResult {
3
+ id: string;
4
+ title: string;
5
+ duration: number;
6
+ hasCaptions: boolean;
7
+ }
8
+ export interface EmbedPanoptoResult {
9
+ html: string;
10
+ videoTitle: string;
11
+ hasCaptions: boolean | null;
12
+ captionWarning?: string;
13
+ iframeUsed: boolean;
14
+ }
15
+ export declare function buildEmbedUrl(domain: string, videoId: string): string;
16
+ export declare function buildViewerUrl(domain: string, videoId: string): string;
17
+ export declare function formatDuration(seconds: number): string;
18
+ export declare function formatSearchResults(results: PanoptoSearchResult[], query: string): string;
19
+ export declare function buildEmbedHtml(config: PanoptoConfig, videoId: string, title: string, placement: 'inline' | 'full-page'): string;
20
+ export declare function parseVttToText(vtt: string): string;
21
+ export declare function sanitizeFilename(title: string): string;
22
+ export declare function getPanoptoToken(config: PanoptoConfig): Promise<string>;
23
+ export declare function searchPanoptoVideos(input: {
24
+ query?: string;
25
+ limit?: number;
26
+ }, config: PanoptoConfig): Promise<string>;
27
+ export declare function fetchPanoptoCaptions(input: {
28
+ videoId: string;
29
+ title?: string;
30
+ }, config: PanoptoConfig): Promise<string>;
31
+ export declare function embedPanoptoVideo(input: {
32
+ videoId: string;
33
+ placement: 'inline' | 'full-page';
34
+ title?: string;
35
+ }, config: PanoptoConfig): Promise<EmbedPanoptoResult>;
36
+ //# sourceMappingURL=panopto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panopto.d.ts","sourceRoot":"","sources":["../../src/tools/panopto.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;CACrB;AAMD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBzF;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,QAAQ,GAAG,WAAW,GAChC,MAAM,CA+BR;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYlD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB5E;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC1C,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,CAAC,CAoCjB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,GAAG,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,kBAAkB,CAAC,CAyB7B"}