canvaslms-cli 1.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.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Config command
3
+ */
4
+
5
+ const {
6
+ configExists,
7
+ readConfig,
8
+ saveConfig,
9
+ deleteConfig,
10
+ getConfigPath
11
+ } = require('../lib/config');
12
+ const { createReadlineInterface, askQuestion } = require('../lib/interactive');
13
+
14
+ async function showConfig() {
15
+ console.log('šŸ“‹ Canvas CLI Configuration\n');
16
+
17
+ const configPath = getConfigPath();
18
+ const hasConfig = configExists();
19
+
20
+ console.log(`Configuration file: ${configPath}`);
21
+ console.log(`Status: ${hasConfig ? 'āœ… Found' : 'āŒ Not found'}\n`);
22
+
23
+ if (hasConfig) {
24
+ const config = readConfig();
25
+ if (config) {
26
+ console.log('Current configuration:');
27
+ console.log(` 🌐 Canvas Domain: ${config.domain || 'Not set'}`);
28
+ console.log(` šŸ”‘ API Token: ${config.token ? config.token.substring(0, 10) + '...' : 'Not set'}`);
29
+ console.log(` šŸ“… Created: ${config.createdAt ? new Date(config.createdAt).toLocaleString() : 'Unknown'}`);
30
+ console.log(` šŸ”„ Last Updated: ${config.lastUpdated ? new Date(config.lastUpdated).toLocaleString() : 'Unknown'}`);
31
+ } } else {
32
+ console.log('āŒ No configuration found.');
33
+ }
34
+
35
+ console.log('\nšŸ“š Available commands:');
36
+ console.log(' canvas config setup # Interactive setup wizard');
37
+ console.log(' canvas config edit # Edit existing configuration');
38
+ console.log(' canvas config show # Show current configuration');
39
+ console.log(' canvas config delete # Delete configuration file');
40
+ console.log(' canvas config path # Show configuration file path');
41
+
42
+ console.log('\nšŸ”§ Manual setup:');
43
+ console.log('1. Get your Canvas API token:');
44
+ console.log(' - Log into your Canvas instance');
45
+ console.log(' - Go to Account → Settings');
46
+ console.log(' - Scroll down to "Approved Integrations"');
47
+ console.log(' - Click "+ New Access Token"');
48
+ console.log(' - Copy the generated token');
49
+ console.log('2. Run "canvas config setup" to configure');
50
+
51
+ console.log('\nšŸ“– Example usage after setup:');
52
+ console.log(' canvas list # List starred courses');
53
+ console.log(' canvas list -a # List all enrolled courses');
54
+ console.log(' canvas submit # Interactive assignment submission');
55
+ console.log(' canvas profile # Show user profile');
56
+ console.log(' canvas assignments 12345 # Show assignments for course');
57
+ console.log(' canvas grades # Show grades for all courses');
58
+ }
59
+
60
+ async function setupConfig() {
61
+ const rl = createReadlineInterface();
62
+
63
+ try {
64
+ console.log('šŸš€ Canvas CLI Configuration Setup\n');
65
+
66
+ // Check if config already exists
67
+ if (configExists()) {
68
+ const config = readConfig();
69
+ console.log('šŸ“‹ Existing configuration found:');
70
+ console.log(` Domain: ${config.domain || 'Not set'}`);
71
+ console.log(` Token: ${config.token ? 'Set (hidden)' : 'Not set'}\n`);
72
+
73
+ const overwrite = await askQuestion(rl, 'Do you want to overwrite the existing configuration? (y/N): ');
74
+ if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
75
+ console.log('Setup cancelled.');
76
+ return;
77
+ }
78
+ console.log('');
79
+ }
80
+ // Get Canvas domain
81
+ const currentConfig = readConfig();
82
+ let domain = await askQuestion(rl, `Enter your Canvas domain${currentConfig?.domain ? ` (${currentConfig.domain})` : ''}: `);
83
+ if (!domain && currentConfig?.domain) {
84
+ domain = currentConfig.domain;
85
+ }
86
+
87
+ if (!domain) {
88
+ console.log('āŒ Canvas domain is required.');
89
+ return;
90
+ }
91
+
92
+ // Validate and clean domain
93
+ domain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
94
+ if (!domain.includes('.')) {
95
+ console.log('āŒ Invalid domain format. Please enter a valid domain (e.g., school.instructure.com)');
96
+ return;
97
+ }
98
+
99
+ console.log(`āœ… Domain set to: ${domain}\n`);
100
+ // Get API token
101
+ const defaultToken = currentConfig?.token || '';
102
+ let token = await askQuestion(rl, `Enter your Canvas API token${defaultToken ? ' (press Enter to keep current)' : ''}: `);
103
+ if (!token && defaultToken) {
104
+ token = defaultToken;
105
+ }
106
+
107
+ if (!token) {
108
+ console.log('āŒ Canvas API token is required.');
109
+ console.log('\nšŸ”‘ To get your API token:');
110
+ console.log('1. Log into your Canvas instance');
111
+ console.log('2. Go to Account → Settings');
112
+ console.log('3. Scroll down to "Approved Integrations"');
113
+ console.log('4. Click "+ New Access Token"');
114
+ console.log('5. Copy the generated token');
115
+ return;
116
+ }
117
+
118
+ // Validate token format (basic check)
119
+ if (token.length < 10) {
120
+ console.log('āŒ API token seems too short. Please check your token.');
121
+ return;
122
+ }
123
+
124
+ console.log('āœ… Token received\n');
125
+
126
+ // Save configuration
127
+ const saved = saveConfig(domain, token);
128
+ if (saved) {
129
+ console.log('šŸŽ‰ Configuration setup completed successfully!');
130
+ console.log('\nšŸ“ Next steps:');
131
+ console.log(' canvas list # Test your setup by listing courses');
132
+ console.log(' canvas profile # View your profile');
133
+ console.log(' canvas config show # View your configuration');
134
+ }
135
+
136
+ } catch (error) {
137
+ console.error(`āŒ Setup failed: ${error.message}`);
138
+ } finally {
139
+ rl.close();
140
+ }
141
+ }
142
+
143
+ async function editConfig() {
144
+ const rl = createReadlineInterface();
145
+
146
+ try {
147
+ if (!configExists()) {
148
+ console.log('āŒ No configuration file found. Run "canvas config setup" first.');
149
+ return;
150
+ }
151
+
152
+ const config = readConfig();
153
+ console.log('āœļø Edit Canvas CLI Configuration\n');
154
+ console.log('Current values:');
155
+ console.log(` Domain: ${config.domain}`);
156
+ console.log(` Token: ${config.token ? config.token.substring(0, 10) + '...' : 'Not set'}\n`);
157
+
158
+ // Edit domain
159
+ const newDomain = await askQuestion(rl, `New Canvas domain (${config.domain}): `);
160
+ const domain = newDomain.trim() || config.domain;
161
+
162
+ // Edit token
163
+ const changeToken = await askQuestion(rl, 'Change API token? (y/N): ');
164
+ let token = config.token;
165
+
166
+ if (changeToken.toLowerCase() === 'y' || changeToken.toLowerCase() === 'yes') {
167
+ const newToken = await askQuestion(rl, 'New API token: ');
168
+ if (newToken.trim()) {
169
+ token = newToken.trim();
170
+ }
171
+ }
172
+
173
+ // Confirm changes
174
+ console.log('\nšŸ“‹ New configuration:');
175
+ console.log(` Domain: ${domain}`);
176
+ console.log(` Token: ${token ? token.substring(0, 10) + '...' : 'Not set'}`);
177
+
178
+ const confirm = await askQuestion(rl, '\nSave changes? (Y/n): ');
179
+ if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
180
+ console.log('Changes cancelled.');
181
+ return;
182
+ }
183
+
184
+ const saved = saveConfig(domain, token);
185
+ if (saved) {
186
+ console.log('āœ… Configuration updated successfully!');
187
+ }
188
+
189
+ } catch (error) {
190
+ console.error(`āŒ Edit failed: ${error.message}`);
191
+ } finally {
192
+ rl.close();
193
+ }
194
+ }
195
+
196
+ function showConfigPath() {
197
+ console.log(`šŸ“ Configuration file location: ${getConfigPath()}`);
198
+ console.log(`šŸ“Š Exists: ${configExists() ? 'Yes' : 'No'}`);
199
+ }
200
+
201
+ function deleteConfigFile() {
202
+ console.log('šŸ—‘ļø Delete Configuration\n');
203
+
204
+ if (!configExists()) {
205
+ console.log('āŒ No configuration file found.');
206
+ return;
207
+ }
208
+
209
+ const config = readConfig();
210
+ console.log('Current configuration:');
211
+ console.log(` Domain: ${config.domain}`);
212
+ console.log(` Token: ${config.token ? 'Set (hidden)' : 'Not set'}`);
213
+ console.log(` File: ${getConfigPath()}\n`);
214
+
215
+ // For safety, require explicit confirmation
216
+ console.log('āš ļø This will permanently delete your Canvas CLI configuration.');
217
+ console.log('You will need to run "canvas config setup" again to use the CLI.');
218
+ console.log('\nTo confirm deletion, type: DELETE');
219
+
220
+ const readline = require('readline');
221
+ const rl = readline.createInterface({
222
+ input: process.stdin,
223
+ output: process.stdout
224
+ });
225
+
226
+ rl.question('Confirmation: ', (answer) => {
227
+ if (answer === 'DELETE') {
228
+ deleteConfig();
229
+ } else {
230
+ console.log('Deletion cancelled.');
231
+ }
232
+ rl.close();
233
+ });
234
+ }
235
+
236
+ module.exports = {
237
+ showConfig,
238
+ setupConfig,
239
+ editConfig,
240
+ showConfigPath,
241
+ deleteConfigFile
242
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Grades command
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ async function showGrades(courseId, options) {
8
+ try {
9
+ if (courseId) {
10
+ // Get grades for specific course
11
+ const enrollments = await makeCanvasRequest('get', `courses/${courseId}/enrollments`, ['user_id=self', 'include[]=grades']);
12
+
13
+ if (!enrollments || enrollments.length === 0) {
14
+ console.log('No enrollment found for this course.');
15
+ return;
16
+ }
17
+
18
+ const enrollment = enrollments[0];
19
+ console.log(`Grades for course: ${enrollment.course_id}`);
20
+ console.log(`Current Score: ${enrollment.grades?.current_score || 'N/A'}%`);
21
+ console.log(`Final Score: ${enrollment.grades?.final_score || 'N/A'}%`);
22
+ console.log(`Current Grade: ${enrollment.grades?.current_grade || 'N/A'}`);
23
+ console.log(`Final Grade: ${enrollment.grades?.final_grade || 'N/A'}`);
24
+ } else {
25
+ // Get grades for all courses
26
+ const courses = await makeCanvasRequest('get', 'courses', [
27
+ 'enrollment_state=active',
28
+ 'include[]=enrollments',
29
+ 'include[]=total_scores'
30
+ ]);
31
+
32
+ if (!courses || courses.length === 0) {
33
+ console.log('No courses found.');
34
+ return;
35
+ }
36
+
37
+ console.log('Grades Summary:\n');
38
+
39
+ courses.forEach((course, index) => {
40
+ console.log(`${index + 1}. ${course.name}`);
41
+ console.log(` ID: ${course.id}`);
42
+
43
+ if (course.enrollments && course.enrollments.length > 0) {
44
+ const enrollment = course.enrollments[0];
45
+ if (enrollment.grades) {
46
+ console.log(` Current Score: ${enrollment.grades.current_score || 'N/A'}%`);
47
+ console.log(` Final Score: ${enrollment.grades.final_score || 'N/A'}%`);
48
+ console.log(` Current Grade: ${enrollment.grades.current_grade || 'N/A'}`);
49
+ } else {
50
+ console.log(` Grades: Not available`);
51
+ }
52
+ } else {
53
+ console.log(` Enrollment: Not found`);
54
+ }
55
+
56
+ console.log('');
57
+ });
58
+ }
59
+
60
+ } catch (error) {
61
+ console.error('Error fetching grades:', error.message);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ module.exports = {
67
+ showGrades
68
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * List courses command
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ async function listCourses(options) {
8
+ try {
9
+ const queryParams = [];
10
+
11
+ // Always show only active courses
12
+ queryParams.push('enrollment_state=active');
13
+
14
+ // Include additional course information
15
+ queryParams.push('include[]=term');
16
+ queryParams.push('include[]=course_progress');
17
+ queryParams.push('include[]=total_students');
18
+ queryParams.push('include[]=favorites'); // Include favorite status
19
+
20
+ const courses = await makeCanvasRequest('get', 'courses', queryParams);
21
+
22
+ if (!courses || courses.length === 0) {
23
+ console.log('No courses found.');
24
+ return;
25
+ }
26
+
27
+ // By default, show only starred courses unless -a flag is used
28
+ let filteredCourses = courses;
29
+ if (!options.all) {
30
+ filteredCourses = courses.filter(course => course.is_favorite);
31
+
32
+ if (filteredCourses.length === 0) {
33
+ console.log('No starred courses found. Use -a to see all courses.');
34
+ return;
35
+ }
36
+ }
37
+
38
+ const courseLabel = options.all ? 'enrolled course(s)' : 'starred course(s)';
39
+ console.log(`Found ${filteredCourses.length} ${courseLabel}:\n`);
40
+
41
+ filteredCourses.forEach((course, index) => {
42
+ const starIcon = course.is_favorite ? '⭐ ' : '';
43
+ console.log(`${index + 1}. ${starIcon}${course.name}`);
44
+ console.log(` ID: ${course.id}`);
45
+ console.log(` Code: ${course.course_code || 'N/A'}`);
46
+
47
+ if (options.verbose) {
48
+ console.log(` Term: ${course.term?.name || 'N/A'}`);
49
+ console.log(` Students: ${course.total_students || 'N/A'}`);
50
+ console.log(` Start Date: ${course.start_at ? new Date(course.start_at).toLocaleDateString() : 'N/A'}`);
51
+ console.log(` End Date: ${course.end_at ? new Date(course.end_at).toLocaleDateString() : 'N/A'}`);
52
+ console.log(` Workflow State: ${course.workflow_state}`);
53
+
54
+ if (course.course_progress) {
55
+ console.log(` Progress: ${course.course_progress.requirement_completed_count || 0}/${course.course_progress.requirement_count || 0} requirements`);
56
+ }
57
+ }
58
+
59
+ console.log(''); // Empty line between courses
60
+ });
61
+
62
+ } catch (error) {
63
+ console.error('Error fetching courses:', error.message);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ module.exports = {
69
+ listCourses
70
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Profile command
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ async function showProfile(options) {
8
+ try {
9
+ const user = await makeCanvasRequest('get', 'users/self', ['include[]=email', 'include[]=locale']);
10
+
11
+ console.log('User Profile:\n');
12
+ console.log(`Name: ${user.name}`);
13
+ console.log(`ID: ${user.id}`);
14
+ console.log(`Email: ${user.email || 'N/A'}`);
15
+ console.log(`Login ID: ${user.login_id || 'N/A'}`);
16
+
17
+ if (options.verbose) {
18
+ console.log(`Short Name: ${user.short_name || 'N/A'}`);
19
+ console.log(`Sortable Name: ${user.sortable_name || 'N/A'}`);
20
+ console.log(`Locale: ${user.locale || 'N/A'}`);
21
+ console.log(`Time Zone: ${user.time_zone || 'N/A'}`);
22
+ console.log(`Avatar URL: ${user.avatar_url || 'N/A'}`);
23
+ console.log(`Created: ${user.created_at ? new Date(user.created_at).toLocaleString() : 'N/A'}`);
24
+ }
25
+
26
+ } catch (error) {
27
+ console.error('Error fetching profile:', error.message);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ module.exports = {
33
+ showProfile
34
+ };