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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.0] - 2025-07-03
9
+
10
+ ### Added
11
+
12
+ - Home directory configuration system (~/.canvaslms-cli-config.json)
13
+ - Interactive configuration setup wizard (`canvas config setup`)
14
+ - Configuration management subcommands:
15
+ - `canvas config show` - Display current configuration
16
+ - `canvas config edit` - Edit existing configuration
17
+ - `canvas config path` - Show config file location
18
+ - `canvas config delete` - Remove configuration file
19
+ - Automatic configuration validation for all commands
20
+ - Improved error handling and user guidance
21
+
22
+ ### Changed
23
+
24
+ - **BREAKING**: Removed environment variable support (.env files)
25
+ - Configuration now stored in user's home directory instead of project directory
26
+ - Enhanced configuration validation with better error messages
27
+ - Improved user onboarding with guided setup process
28
+
29
+ ### Removed
30
+
31
+ - dotenv dependency (no longer needed)
32
+ - Environment variable fallback support
33
+
34
+ ## [1.0.0] - 2025-07-03
35
+
36
+ ### Added
37
+
38
+ - Initial release of Canvas CLI Tool
39
+ - Modular architecture with separate command handlers
40
+ - Interactive assignment submission with file upload
41
+ - Course management (list starred/all courses)
42
+ - Assignment operations (view, filter by status)
43
+ - Grade viewing for all courses or specific course
44
+ - Announcements viewing
45
+ - User profile management
46
+ - Raw API access for all HTTP methods (GET, POST, PUT, DELETE)
47
+ - Comprehensive configuration management
48
+ - Color-coded output for better readability
49
+ - Support for multiple file uploads
50
+ - File selection from current directory
51
+ - Detailed verbose modes for all commands
52
+
53
+ ### Features
54
+
55
+ - 📚 Course listing with favorites support
56
+ - 📝 Assignment management with status indicators
57
+ - 🚀 Interactive file submission workflow
58
+ - 📢 Announcement viewing
59
+ - 👤 Profile information display
60
+ - 🔧 Direct Canvas API access
61
+ - ⚡ Command aliases for faster usage
62
+ - 🎨 Color-coded grade display
63
+ - 📁 Smart file selection interface
64
+
65
+ ### Technical
66
+
67
+ - Modular command structure in separate files
68
+ - Reusable API client library
69
+ - Interactive prompt utilities
70
+ - File upload handling with progress indicators
71
+ - Error handling and user-friendly messages
72
+ - Cross-platform compatibility
73
+
74
+ ## [Unreleased]
75
+
76
+ ### Planned Features
77
+
78
+ - Assignment creation and editing
79
+ - Bulk operations
80
+ - Plugin system
81
+ - Advanced filtering options
82
+ - Export functionality
83
+ - Offline mode support
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Canvas CLI Tool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Canvas CLI Tool
2
+
3
+ A powerful command-line interface for interacting with Canvas LMS API. This tool allows you to manage courses, assignments, submissions, and more directly from your terminal.
4
+
5
+ ## Features
6
+
7
+ - 📚 **Course Management**: List starred and enrolled courses
8
+ - 📝 **Assignment Operations**: View assignments, grades, and submission status
9
+ - 🚀 **File Submission**: Interactive file upload for assignments (single or multiple files)
10
+ - 📢 **Announcements**: View course announcements
11
+ - 👤 **Profile Management**: View user profile information
12
+ - 🔧 **Raw API Access**: Direct access to Canvas API endpoints
13
+
14
+ ## Installation
15
+
16
+ ### Global Installation (Recommended)
17
+
18
+ ```bash
19
+ npm install -g canvas-cli-tool
20
+ ```
21
+
22
+ ### Local Installation
23
+
24
+ ```bash
25
+ npm install canvas-cli-tool
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ 1. **Get your Canvas API token**:
31
+ - Log into your Canvas instance
32
+ - Go to Account → Settings
33
+ - Scroll down to "Approved Integrations"
34
+ - Click "+ New Access Token"
35
+ - Copy the generated token
36
+
37
+ 2. **Configure the CLI**:
38
+ ```bash
39
+ canvas config
40
+ ```
41
+
42
+ 3. **Create a `.env` file** in your project root:
43
+ ```env
44
+ CANVAS_DOMAIN=your-canvas-domain.instructure.com
45
+ CANVAS_API_TOKEN=your-api-token
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Basic Commands
51
+
52
+ ```bash
53
+ # Show configuration help
54
+ canvas config
55
+
56
+ # List starred courses (default)
57
+ canvas list
58
+
59
+ # List all enrolled courses
60
+ canvas list -a
61
+
62
+ # List courses with detailed information
63
+ canvas list -v
64
+
65
+ # Show user profile
66
+ canvas profile
67
+
68
+ # Show detailed profile information
69
+ canvas profile -v
70
+ ```
71
+
72
+ ### Assignment Operations
73
+
74
+ ```bash
75
+ # List assignments for a course
76
+ canvas assignments 12345
77
+
78
+ # Show detailed assignment information
79
+ canvas assignments 12345 -v
80
+
81
+ # Show only submitted assignments
82
+ canvas assignments 12345 -s
83
+
84
+ # Show only pending assignments
85
+ canvas assignments 12345 -p
86
+ ```
87
+
88
+ ### File Submission
89
+
90
+ ```bash
91
+ # Interactive assignment submission
92
+ canvas submit
93
+
94
+ # Submit with specific course ID
95
+ canvas submit -c 12345
96
+
97
+ # Submit with specific assignment ID
98
+ canvas submit -a 67890
99
+
100
+ # Submit specific file
101
+ canvas submit -f myfile.pdf
102
+ ```
103
+
104
+ ### Grades and Announcements
105
+
106
+ ```bash
107
+ # Show grades for all courses
108
+ canvas grades
109
+
110
+ # Show grades for specific course
111
+ canvas grades 12345
112
+
113
+ # Show recent announcements
114
+ canvas announcements
115
+
116
+ # Show announcements for specific course
117
+ canvas announcements 12345
118
+ ```
119
+
120
+ ### Raw API Access
121
+
122
+ ```bash
123
+ # GET request
124
+ canvas get users/self
125
+
126
+ # GET with query parameters
127
+ canvas get courses -q enrollment_state=active
128
+
129
+ # POST request with data
130
+ canvas post courses/123/assignments -d '{"assignment": {"name": "Test"}}'
131
+
132
+ # POST with data from file
133
+ canvas post courses/123/assignments -d @assignment.json
134
+ ```
135
+
136
+ ## Command Reference
137
+
138
+ | Command | Alias | Description |
139
+ |---------|-------|-------------|
140
+ | `list` | `l` | List courses |
141
+ | `assignments` | `assign` | List assignments |
142
+ | `submit` | `sub` | Submit assignment files |
143
+ | `grades` | `grade` | Show grades |
144
+ | `announcements` | `announce` | Show announcements |
145
+ | `profile` | `me` | Show user profile |
146
+ | `config` | - | Show configuration |
147
+ | `get` | `g` | GET API request |
148
+ | `post` | `p` | POST API request |
149
+ | `put` | - | PUT API request |
150
+ | `delete` | `d` | DELETE API request |
151
+
152
+ ## File Structure
153
+
154
+ ```
155
+ canvas-cli-tool/
156
+ ├── src/
157
+ │ └── index.js # Main CLI entry point
158
+ ├── lib/
159
+ │ ├── api-client.js # Canvas API client
160
+ │ ├── config.js # Configuration management
161
+ │ ├── file-upload.js # File upload utilities
162
+ │ └── interactive.js # Interactive prompt utilities
163
+ ├── commands/
164
+ │ ├── list.js # List courses command
165
+ │ ├── assignments.js # Assignments command
166
+ │ ├── submit.js # Submit command
167
+ │ ├── grades.js # Grades command
168
+ │ ├── announcements.js # Announcements command
169
+ │ ├── profile.js # Profile command
170
+ │ ├── config.js # Config command
171
+ │ └── api.js # Raw API commands
172
+ └── package.json
173
+ ```
174
+
175
+ ## Requirements
176
+
177
+ - Node.js >= 14.0.0
178
+ - npm >= 6.0.0
179
+ - Valid Canvas LMS access with API token
180
+
181
+ ## Contributing
182
+
183
+ 1. Fork the repository
184
+ 2. Create a feature branch: `git checkout -b feature-name`
185
+ 3. Make your changes
186
+ 4. Run tests: `npm test`
187
+ 5. Commit your changes: `git commit -am 'Add feature'`
188
+ 6. Push to the branch: `git push origin feature-name`
189
+ 7. Submit a pull request
190
+
191
+ ## License
192
+
193
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Announcements command
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ async function showAnnouncements(courseId, options) {
8
+ try {
9
+ if (courseId) {
10
+ // Get announcements for specific course
11
+ const announcements = await makeCanvasRequest('get', `courses/${courseId}/discussion_topics`, [
12
+ 'only_announcements=true',
13
+ `per_page=${options.limit}`
14
+ ]);
15
+
16
+ if (!announcements || announcements.length === 0) {
17
+ console.log('No announcements found for this course.');
18
+ return;
19
+ }
20
+
21
+ console.log(`Recent ${announcements.length} announcement(s) for course ${courseId}:\n`);
22
+
23
+ announcements.forEach((announcement, index) => {
24
+ console.log(`${index + 1}. ${announcement.title}`);
25
+ console.log(` Posted: ${announcement.posted_at ? new Date(announcement.posted_at).toLocaleString() : 'N/A'}`);
26
+ console.log(` Author: ${announcement.author?.display_name || 'Unknown'}`);
27
+
28
+ if (announcement.message) {
29
+ // Remove HTML tags and truncate
30
+ const message = announcement.message.replace(/<[^>]*>/g, '').substring(0, 200);
31
+ console.log(` Message: ${message}${announcement.message.length > 200 ? '...' : ''}`);
32
+ }
33
+
34
+ console.log('');
35
+ });
36
+
37
+ } else {
38
+ // Get announcements for all enrolled courses
39
+ const courses = await makeCanvasRequest('get', 'courses', ['enrollment_state=active']);
40
+
41
+ if (!courses || courses.length === 0) {
42
+ console.log('No courses found.');
43
+ return;
44
+ }
45
+
46
+ console.log('Getting announcements from all enrolled courses...\n');
47
+
48
+ for (const course of courses.slice(0, 3)) { // Limit to first 3 courses to avoid too many requests
49
+ try {
50
+ const announcements = await makeCanvasRequest('get', `courses/${course.id}/discussion_topics`, [
51
+ 'only_announcements=true',
52
+ 'per_page=2'
53
+ ]);
54
+
55
+ if (announcements && announcements.length > 0) {
56
+ console.log(`📢 ${course.name}:`);
57
+ announcements.forEach((announcement) => {
58
+ console.log(` • ${announcement.title} (${announcement.posted_at ? new Date(announcement.posted_at).toLocaleDateString() : 'No date'})`);
59
+ });
60
+ console.log('');
61
+ }
62
+ } catch (error) {
63
+ // Skip courses that don't allow access to announcements
64
+ continue;
65
+ }
66
+ }
67
+ }
68
+
69
+ } catch (error) {
70
+ console.error('Error fetching announcements:', error.message);
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ showAnnouncements
77
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Raw API commands (get, post, put, delete, query)
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ /**
8
+ * Create query command handler
9
+ */
10
+ function createQueryHandler(method) {
11
+ return async function(endpoint, options) {
12
+ const data = await makeCanvasRequest(method, endpoint, options.query, options.data);
13
+ console.log(JSON.stringify(data, null, 2));
14
+ };
15
+ }
16
+
17
+ module.exports = {
18
+ createQueryHandler
19
+ };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Assignments command
3
+ */
4
+
5
+ const { makeCanvasRequest } = require('../lib/api-client');
6
+
7
+ async function listAssignments(courseId, options) {
8
+ try {
9
+ const queryParams = ['include[]=submission', 'include[]=score_statistics', 'per_page=100'];
10
+
11
+ const assignments = await makeCanvasRequest('get', `courses/${courseId}/assignments`, queryParams);
12
+
13
+ if (!assignments || assignments.length === 0) {
14
+ console.log('No assignments found for this course.');
15
+ return;
16
+ }
17
+
18
+ // Filter assignments based on options
19
+ let filteredAssignments = assignments;
20
+ if (options.submitted) {
21
+ filteredAssignments = assignments.filter(a => a.submission && a.submission.submitted_at);
22
+ } else if (options.pending) {
23
+ filteredAssignments = assignments.filter(a => !a.submission || !a.submission.submitted_at);
24
+ }
25
+
26
+ console.log(`Found ${filteredAssignments.length} assignment(s):\n`);
27
+
28
+ filteredAssignments.forEach((assignment, index) => {
29
+ const submission = assignment.submission;
30
+ const isSubmitted = submission && submission.submitted_at;
31
+ const submissionStatus = isSubmitted ? '✅' : '❌';
32
+
33
+ // Format grade like Canvas web interface with enhanced formatting
34
+ let gradeDisplay = '';
35
+ let gradeColor = '';
36
+
37
+ if (submission && submission.score !== null && submission.score !== undefined) {
38
+ // Format score to remove unnecessary decimals (e.g., 3.0 becomes 3)
39
+ const score = submission.score % 1 === 0 ? Math.round(submission.score) : submission.score;
40
+ const total = assignment.points_possible || 0;
41
+ gradeDisplay = `${score}/${total}`;
42
+
43
+ // Add color coding based on score percentage
44
+ if (total > 0) {
45
+ const percentage = (submission.score / total) * 100;
46
+ if (percentage >= 90) gradeColor = '\x1b[32m'; // Green for A
47
+ else if (percentage >= 80) gradeColor = '\x1b[36m'; // Cyan for B
48
+ else if (percentage >= 70) gradeColor = '\x1b[33m'; // Yellow for C
49
+ else if (percentage >= 60) gradeColor = '\x1b[35m'; // Magenta for D
50
+ else gradeColor = '\x1b[31m'; // Red for F
51
+ }
52
+ } else if (submission && submission.excused) {
53
+ gradeDisplay = 'Excused';
54
+ gradeColor = '\x1b[34m'; // Blue for excused
55
+ } else if (submission && submission.missing) {
56
+ gradeDisplay = 'Missing';
57
+ gradeColor = '\x1b[31m'; // Red for missing
58
+ } else if (assignment.points_possible) {
59
+ gradeDisplay = `–/${assignment.points_possible}`;
60
+ gradeColor = '\x1b[90m'; // Gray for not graded yet
61
+ } else {
62
+ gradeDisplay = 'N/A';
63
+ gradeColor = '\x1b[90m'; // Gray for N/A
64
+ }
65
+
66
+ // Handle letter grades if present
67
+ if (submission && submission.grade && isNaN(submission.grade)) {
68
+ gradeDisplay = submission.grade + (assignment.points_possible ? ` (${gradeDisplay})` : '');
69
+ }
70
+
71
+ console.log(`${index + 1}. ${submissionStatus} ${assignment.name}`);
72
+ console.log(` ID: ${assignment.id}`);
73
+ console.log(` Grade: ${gradeColor}${gradeDisplay} pts\x1b[0m`);
74
+ console.log(` Due: ${assignment.due_at ? new Date(assignment.due_at).toLocaleString() : 'No due date'}`);
75
+
76
+ if (isSubmitted) {
77
+ console.log(` Submitted: ${new Date(submission.submitted_at).toLocaleString()}`);
78
+ if (submission.workflow_state) {
79
+ // Color code submission status
80
+ let statusColor = '';
81
+ switch(submission.workflow_state) {
82
+ case 'graded': statusColor = '\x1b[32m'; break; // Green
83
+ case 'submitted': statusColor = '\x1b[33m'; break; // Yellow
84
+ case 'pending_review': statusColor = '\x1b[36m'; break; // Cyan
85
+ default: statusColor = '\x1b[37m'; // White
86
+ }
87
+ console.log(` Status: ${statusColor}${submission.workflow_state}\x1b[0m`);
88
+ }
89
+ } else {
90
+ console.log(` Status: \x1b[31mNot submitted\x1b[0m`); // Red for not submitted
91
+ }
92
+
93
+ if (options.verbose) {
94
+ if (assignment.description) {
95
+ // Strip HTML tags and clean up description
96
+ const cleanDescription = assignment.description
97
+ .replace(/<[^>]*>/g, '') // Remove HTML tags
98
+ .replace(/\s+/g, ' ') // Replace multiple whitespace with single space
99
+ .trim() // Remove leading/trailing whitespace
100
+ .substring(0, 150); // Limit to 150 characters
101
+ console.log(` Description: ${cleanDescription}${cleanDescription.length === 150 ? '...' : ''}`);
102
+ } else {
103
+ console.log(` Description: N/A`);
104
+ }
105
+ console.log(` Submission Types: ${assignment.submission_types?.join(', ') || 'N/A'}`);
106
+ console.log(` Published: ${assignment.published ? 'Yes' : 'No'}`);
107
+ if (assignment.points_possible) {
108
+ console.log(` Points Possible: ${assignment.points_possible}`);
109
+ }
110
+ if (submission && submission.attempt) {
111
+ console.log(` Attempt: ${submission.attempt}`);
112
+ }
113
+ }
114
+
115
+ console.log('');
116
+ });
117
+
118
+ } catch (error) {
119
+ console.error('Error fetching assignments:', error.message);
120
+ process.exit(1);
121
+ }
122
+ }
123
+
124
+ module.exports = {
125
+ listAssignments
126
+ };