canvaslms-cli 1.3.3 → 1.4.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/commands/announcements.js +102 -80
- package/commands/assignments.js +100 -130
- package/commands/config.js +239 -242
- package/commands/grades.js +201 -205
- package/commands/list.js +68 -70
- package/commands/profile.js +35 -34
- package/commands/submit.js +98 -151
- package/lib/api-client.js +4 -8
- package/lib/config-validator.js +60 -63
- package/lib/config.js +103 -135
- package/lib/file-upload.js +73 -78
- package/lib/interactive.js +180 -132
- package/package.json +4 -2
- package/src/index.js +25 -47
|
@@ -1,80 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { makeCanvasRequest } from '../lib/api-client.js';
|
|
2
|
+
import { createReadlineInterface, askQuestion } from '../lib/interactive.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export async function showAnnouncements(courseId, options) {
|
|
6
|
+
const rl = createReadlineInterface();
|
|
7
|
+
try {
|
|
8
|
+
if (!courseId) {
|
|
9
|
+
console.log(chalk.cyan.bold('Loading your courses, please wait...\n'));
|
|
10
|
+
const courses = await makeCanvasRequest('get', 'courses', [
|
|
11
|
+
'enrollment_state=active',
|
|
12
|
+
'include[]=favorites'
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
if (!courses || courses.length === 0) {
|
|
16
|
+
console.log(chalk.red('Error: No courses found.'));
|
|
17
|
+
rl.close();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan.bold('\n' + '-'.repeat(60)));
|
|
22
|
+
console.log(chalk.cyan.bold('Select a course:'));
|
|
23
|
+
courses.forEach((course, index) => {
|
|
24
|
+
console.log(chalk.white(`${index + 1}.`) + ' ' + course.name);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const courseChoice = await askQuestion(rl, chalk.white('\nEnter course number: '));
|
|
28
|
+
if (!courseChoice.trim()) {
|
|
29
|
+
console.log(chalk.red('No course selected. Exiting...'));
|
|
30
|
+
rl.close();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const courseIndex = parseInt(courseChoice) - 1;
|
|
35
|
+
if (courseIndex < 0 || courseIndex >= courses.length) {
|
|
36
|
+
console.log(chalk.red('Invalid course selection.'));
|
|
37
|
+
rl.close();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
courseId = courses[courseIndex].id;
|
|
42
|
+
console.log(chalk.green(`Success: Selected ${courses[courseIndex].name}\n`));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ✅ FIXED: Use discussion_topics endpoint with only_announcements=true
|
|
46
|
+
const limit = parseInt(options.limit) || 5;
|
|
47
|
+
const announcements = await makeCanvasRequest(
|
|
48
|
+
'get',
|
|
49
|
+
`courses/${courseId}/discussion_topics`,
|
|
50
|
+
[`only_announcements=true`, `per_page=${limit}`]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!announcements || announcements.length === 0) {
|
|
54
|
+
console.log(chalk.yellow('No announcements found for this course.'));
|
|
55
|
+
rl.close();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.green('Success: Announcement loaded.'));
|
|
60
|
+
console.log(chalk.cyan.bold('\n' + '-'.repeat(60)));
|
|
61
|
+
console.log(chalk.cyan.bold('Announcements:'));
|
|
62
|
+
announcements.forEach((a, i) => {
|
|
63
|
+
const date = a.posted_at ? new Date(a.posted_at).toLocaleDateString() : '';
|
|
64
|
+
console.log(chalk.white(`${i + 1}.`) + ' ' + a.title + chalk.gray(date ? ` (${date})` : ''));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const annChoice = await askQuestion(rl, chalk.white('\nEnter announcement number to view details: '));
|
|
68
|
+
if (!annChoice.trim()) {
|
|
69
|
+
console.log(chalk.red('No announcement selected. Exiting...'));
|
|
70
|
+
rl.close();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const annIndex = parseInt(annChoice) - 1;
|
|
75
|
+
if (annIndex < 0 || annIndex >= announcements.length) {
|
|
76
|
+
console.log(chalk.red('Invalid announcement selection.'));
|
|
77
|
+
rl.close();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ann = announcements[annIndex];
|
|
82
|
+
const title = ann.title || 'Untitled';
|
|
83
|
+
const date = ann.posted_at ? new Date(ann.posted_at).toLocaleString() : 'N/A';
|
|
84
|
+
const author = ann.author?.display_name || 'Unknown';
|
|
85
|
+
const message = ann.message?.replace(/<[^>]+>/g, '').trim() || 'No content';
|
|
86
|
+
|
|
87
|
+
console.log('\n' + chalk.cyan('='.repeat(60)));
|
|
88
|
+
console.log(chalk.bold(' ' + title));
|
|
89
|
+
console.log(chalk.cyan('-'.repeat(60)));
|
|
90
|
+
console.log(chalk.gray(' Posted: ') + date);
|
|
91
|
+
console.log(chalk.gray(' Author: ') + author);
|
|
92
|
+
console.log('\n' + chalk.bold(' Message:') + '\n');
|
|
93
|
+
console.log(message);
|
|
94
|
+
console.log(chalk.cyan('='.repeat(60)) + '\n');
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(chalk.red('Error: Failed to fetch announcements: ') + error.message);
|
|
98
|
+
} finally {
|
|
99
|
+
rl.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
package/commands/assignments.js
CHANGED
|
@@ -1,130 +1,100 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Assignments command
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log(
|
|
31
|
-
console.log(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
} else
|
|
58
|
-
gradeDisplay = '
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
|
102
|
-
.replace(/\s+/g, ' ') // Replace multiple whitespace with single space
|
|
103
|
-
.trim() // Remove leading/trailing whitespace
|
|
104
|
-
.substring(0, 150); // Limit to 150 characters
|
|
105
|
-
console.log(` Description: ${cleanDescription}${cleanDescription.length === 150 ? '...' : ''}`);
|
|
106
|
-
} else {
|
|
107
|
-
console.log(` Description: N/A`);
|
|
108
|
-
}
|
|
109
|
-
console.log(` Submission Types: ${assignment.submission_types?.join(', ') || 'N/A'}`);
|
|
110
|
-
console.log(` Published: ${assignment.published ? 'Yes' : 'No'}`);
|
|
111
|
-
if (assignment.points_possible) {
|
|
112
|
-
console.log(` Points Possible: ${assignment.points_possible}`);
|
|
113
|
-
}
|
|
114
|
-
if (submission && submission.attempt) {
|
|
115
|
-
console.log(` Attempt: ${submission.attempt}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
console.log('');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error('Error fetching assignments:', error.message);
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = {
|
|
129
|
-
listAssignments
|
|
130
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Assignments command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { makeCanvasRequest } from '../lib/api-client.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
function pad(str, len) {
|
|
9
|
+
return str + ' '.repeat(Math.max(0, len - str.length));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function listAssignments(courseId, options) {
|
|
13
|
+
try {
|
|
14
|
+
const course = await makeCanvasRequest('get', `courses/${courseId}`);
|
|
15
|
+
const queryParams = ['include[]=submission', 'include[]=score_statistics', 'per_page=100'];
|
|
16
|
+
console.log(chalk.cyan.bold('\n' + '-'.repeat(60)));
|
|
17
|
+
console.log(chalk.cyan.bold('Loading assignments, please wait...'));
|
|
18
|
+
const assignments = await makeCanvasRequest('get', `courses/${courseId}/assignments`, queryParams);
|
|
19
|
+
if (!assignments || assignments.length === 0) {
|
|
20
|
+
console.log(chalk.red(`Error: No assignments found for course: ${course.name}`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let filteredAssignments = assignments;
|
|
24
|
+
if (options.submitted) {
|
|
25
|
+
filteredAssignments = assignments.filter(a => a.submission && a.submission.submitted_at);
|
|
26
|
+
} else if (options.pending) {
|
|
27
|
+
filteredAssignments = assignments.filter(a => !a.submission || !a.submission.submitted_at);
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk.cyan.bold('\n' + '-'.repeat(60)));
|
|
30
|
+
console.log(chalk.cyan.bold(`Assignments for: ${course.name}`));
|
|
31
|
+
console.log(chalk.cyan('-'.repeat(60)));
|
|
32
|
+
console.log(chalk.green(`Success: Found ${filteredAssignments.length} assignment(s).`));
|
|
33
|
+
console.log(chalk.cyan('-'.repeat(60)));
|
|
34
|
+
// Column headers
|
|
35
|
+
console.log(
|
|
36
|
+
pad(chalk.bold('No.'), 5) +
|
|
37
|
+
pad(chalk.bold('Assignment Name'), 35) +
|
|
38
|
+
pad(chalk.bold('ID'), 10) +
|
|
39
|
+
pad(chalk.bold('Grade'), 12) +
|
|
40
|
+
pad(chalk.bold('Due'), 22) +
|
|
41
|
+
(options.verbose ? pad(chalk.bold('Status'), 12) : '')
|
|
42
|
+
);
|
|
43
|
+
console.log(chalk.cyan('-'.repeat(60)));
|
|
44
|
+
filteredAssignments.forEach((assignment, index) => {
|
|
45
|
+
const submission = assignment.submission;
|
|
46
|
+
let gradeDisplay = '';
|
|
47
|
+
if (submission && submission.score !== null && submission.score !== undefined) {
|
|
48
|
+
const score = submission.score % 1 === 0 ? Math.round(submission.score) : submission.score;
|
|
49
|
+
const total = assignment.points_possible || 0;
|
|
50
|
+
gradeDisplay = `${score}/${total}`;
|
|
51
|
+
} else if (submission && submission.excused) {
|
|
52
|
+
gradeDisplay = 'Excused';
|
|
53
|
+
} else if (submission && submission.missing) {
|
|
54
|
+
gradeDisplay = 'Missing';
|
|
55
|
+
} else if (assignment.points_possible) {
|
|
56
|
+
gradeDisplay = `–/${assignment.points_possible}`;
|
|
57
|
+
} else {
|
|
58
|
+
gradeDisplay = 'N/A';
|
|
59
|
+
}
|
|
60
|
+
let line = pad(chalk.white((index + 1) + '.'), 5) +
|
|
61
|
+
pad(assignment.name, 35) +
|
|
62
|
+
pad(String(assignment.id), 10) +
|
|
63
|
+
pad(gradeDisplay, 12) +
|
|
64
|
+
pad(assignment.due_at ? new Date(assignment.due_at).toLocaleString() : 'No due date', 22);
|
|
65
|
+
if (options.verbose) {
|
|
66
|
+
let status = 'Not submitted';
|
|
67
|
+
if (submission && submission.submitted_at) {
|
|
68
|
+
status = 'Submitted';
|
|
69
|
+
}
|
|
70
|
+
line += pad(status, 12);
|
|
71
|
+
}
|
|
72
|
+
console.log(line);
|
|
73
|
+
if (options.verbose) {
|
|
74
|
+
if (assignment.description) {
|
|
75
|
+
const cleanDescription = assignment.description
|
|
76
|
+
.replace(/<[^>]*>/g, '')
|
|
77
|
+
.replace(/\s+/g, ' ')
|
|
78
|
+
.trim()
|
|
79
|
+
.substring(0, 150);
|
|
80
|
+
console.log(' ' + chalk.gray('Description: ') + cleanDescription + (cleanDescription.length === 150 ? '...' : ''));
|
|
81
|
+
} else {
|
|
82
|
+
console.log(' ' + chalk.gray('Description: N/A'));
|
|
83
|
+
}
|
|
84
|
+
console.log(' ' + chalk.gray('Submission Types: ') + (assignment.submission_types?.join(', ') || 'N/A'));
|
|
85
|
+
console.log(' ' + chalk.gray('Published: ') + (assignment.published ? 'Yes' : 'No'));
|
|
86
|
+
if (assignment.points_possible) {
|
|
87
|
+
console.log(' ' + chalk.gray('Points Possible: ') + assignment.points_possible);
|
|
88
|
+
}
|
|
89
|
+
if (submission && submission.attempt) {
|
|
90
|
+
console.log(' ' + chalk.gray('Attempt: ') + submission.attempt);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log('');
|
|
94
|
+
});
|
|
95
|
+
console.log(chalk.cyan('-'.repeat(60)));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(chalk.red('Error fetching assignments:'), error.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|