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,366 @@
1
+ /**
2
+ * Submit command for interactive assignment submission
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { makeCanvasRequest } = require('../lib/api-client');
8
+ const { createReadlineInterface, askQuestion } = require('../lib/interactive');
9
+ const { uploadSingleFileToCanvas, submitAssignmentWithFiles } = require('../lib/file-upload');
10
+
11
+ async function submitAssignment(options) {
12
+ const rl = createReadlineInterface();
13
+
14
+ try {
15
+ let courseId = options.course;
16
+ let assignmentId = options.assignment;
17
+ let filePath = options.file;
18
+
19
+ // Step 1: Select Course (if not provided)
20
+ if (!courseId) {
21
+ console.log('📚 Loading your starred courses...\n');
22
+
23
+ const courses = await makeCanvasRequest('get', 'courses', [
24
+ 'enrollment_state=active',
25
+ 'include[]=favorites'
26
+ ]);
27
+
28
+ if (!courses || courses.length === 0) {
29
+ console.log('No courses found.');
30
+ rl.close();
31
+ return;
32
+ }
33
+
34
+ // Filter for starred courses by default
35
+ let starredCourses = courses.filter(course => course.is_favorite);
36
+
37
+ if (starredCourses.length === 0) {
38
+ console.log('No starred courses found. Showing all enrolled courses...\n');
39
+ starredCourses = courses;
40
+ }
41
+
42
+ console.log('Select a course:');
43
+ starredCourses.forEach((course, index) => {
44
+ const starIcon = course.is_favorite ? '⭐ ' : '';
45
+ console.log(`${index + 1}. ${starIcon}${course.name} (ID: ${course.id})`);
46
+ });
47
+
48
+ const courseChoice = await askQuestion(rl, '\nEnter course number: ');
49
+ const courseIndex = parseInt(courseChoice) - 1;
50
+
51
+ if (courseIndex < 0 || courseIndex >= starredCourses.length) {
52
+ console.log('Invalid course selection.');
53
+ rl.close();
54
+ return;
55
+ }
56
+
57
+ courseId = starredCourses[courseIndex].id;
58
+ console.log(`Selected: ${starredCourses[courseIndex].name}\n`);
59
+ }
60
+
61
+ // Step 2: Select Assignment (if not provided)
62
+ if (!assignmentId) {
63
+ console.log('📝 Loading assignments...\n');
64
+
65
+ const assignments = await makeCanvasRequest('get', `courses/${courseId}/assignments`, [
66
+ 'include[]=submission',
67
+ 'order_by=due_at',
68
+ 'per_page=100'
69
+ ]);
70
+
71
+ if (!assignments || assignments.length === 0) {
72
+ console.log('No assignments found for this course.');
73
+ rl.close();
74
+ return;
75
+ }
76
+
77
+ console.log(`Found ${assignments.length} assignment(s):\n`);
78
+
79
+ // Show summary of assignment statuses
80
+ const submittedCount = assignments.filter(a => a.submission && a.submission.submitted_at).length;
81
+ const pendingCount = assignments.length - submittedCount;
82
+ const uploadableCount = assignments.filter(a =>
83
+ a.submission_types &&
84
+ a.submission_types.includes('online_upload') &&
85
+ a.workflow_state === 'published'
86
+ ).length;
87
+
88
+ console.log(`📊 Summary: ${submittedCount} submitted, ${pendingCount} pending, ${uploadableCount} accept file uploads\n`);
89
+ console.log('Select an assignment:');
90
+ assignments.forEach((assignment, index) => {
91
+ const dueDate = assignment.due_at ? new Date(assignment.due_at).toLocaleDateString() : 'No due date';
92
+ const submitted = assignment.submission && assignment.submission.submitted_at ? '✅' : '❌';
93
+
94
+ // Check if assignment accepts file uploads
95
+ const canSubmitFiles = assignment.submission_types &&
96
+ assignment.submission_types.includes('online_upload') &&
97
+ assignment.workflow_state === 'published';
98
+ const submissionIcon = canSubmitFiles ? '📤' : '📋';
99
+
100
+ // Format grade like Canvas web interface
101
+ let gradeDisplay = '';
102
+ const submission = assignment.submission;
103
+
104
+ if (submission && submission.score !== null && submission.score !== undefined) {
105
+ const score = submission.score % 1 === 0 ? Math.round(submission.score) : submission.score;
106
+ const total = assignment.points_possible || 0;
107
+ gradeDisplay = ` | Grade: ${score}/${total}`;
108
+ } else if (submission && submission.excused) {
109
+ gradeDisplay = ' | Grade: Excused';
110
+ } else if (submission && submission.missing) {
111
+ gradeDisplay = ' | Grade: Missing';
112
+ } else if (assignment.points_possible) {
113
+ gradeDisplay = ` | Grade: –/${assignment.points_possible}`;
114
+ }
115
+
116
+ console.log(`${index + 1}. ${submissionIcon} ${assignment.name} ${submitted}`);
117
+ console.log(` Due: ${dueDate} | Points: ${assignment.points_possible || 'N/A'}${gradeDisplay}`);
118
+ if (!canSubmitFiles) {
119
+ console.log(` 📋 Note: This assignment doesn't accept file uploads`);
120
+ }
121
+ });
122
+
123
+ const assignmentChoice = await askQuestion(rl, '\nEnter assignment number: ');
124
+ const assignmentIndex = parseInt(assignmentChoice) - 1;
125
+
126
+ if (assignmentIndex < 0 || assignmentIndex >= assignments.length) {
127
+ console.log('Invalid assignment selection.');
128
+ rl.close();
129
+ return;
130
+ }
131
+
132
+ const selectedAssignment = assignments[assignmentIndex];
133
+
134
+ // Check if assignment accepts file uploads
135
+ if (!selectedAssignment.submission_types ||
136
+ !selectedAssignment.submission_types.includes('online_upload') ||
137
+ selectedAssignment.workflow_state !== 'published') {
138
+ console.log('❌ This assignment does not accept file uploads or is not published.');
139
+ rl.close();
140
+ return;
141
+ }
142
+
143
+ assignmentId = selectedAssignment.id;
144
+ console.log(`Selected: ${selectedAssignment.name}\n`);
145
+
146
+ // Check if already submitted
147
+ if (selectedAssignment.submission && selectedAssignment.submission.submitted_at) {
148
+ const resubmit = await askQuestion(rl, 'This assignment has already been submitted. Do you want to resubmit? (y/N): ');
149
+ if (resubmit.toLowerCase() !== 'y' && resubmit.toLowerCase() !== 'yes') {
150
+ console.log('Submission cancelled.');
151
+ rl.close();
152
+ return;
153
+ }
154
+ }
155
+ }
156
+
157
+ // Step 3: Select Files (if not provided)
158
+ let filePaths = [];
159
+ if (filePath) {
160
+ filePaths = [filePath]; // Single file provided via option
161
+ } else {
162
+ filePaths = await selectFiles(rl);
163
+ }
164
+
165
+ // Validate all selected files exist
166
+ const validFiles = [];
167
+ for (const file of filePaths) {
168
+ if (fs.existsSync(file)) {
169
+ validFiles.push(file);
170
+ } else {
171
+ console.log(`âš ī¸ File not found: ${file}`);
172
+ }
173
+ }
174
+
175
+ if (validFiles.length === 0) {
176
+ console.log('No valid files selected.');
177
+ rl.close();
178
+ return;
179
+ }
180
+
181
+ filePaths = validFiles;
182
+
183
+ // Step 4: Confirm and Submit
184
+ console.log('\n📋 Submission Summary:');
185
+ console.log(`Course ID: ${courseId}`);
186
+ console.log(`Assignment ID: ${assignmentId}`);
187
+ console.log(`Files (${filePaths.length}):`);
188
+ filePaths.forEach((file, index) => {
189
+ const stats = fs.statSync(file);
190
+ const size = (stats.size / 1024).toFixed(1) + ' KB';
191
+ console.log(` ${index + 1}. ${file} (${size})`);
192
+ });
193
+
194
+ const confirm = await askQuestion(rl, '\nProceed with submission? (Y/n): ');
195
+ if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
196
+ console.log('Submission cancelled.');
197
+ rl.close();
198
+ return;
199
+ }
200
+
201
+ console.log('\n🚀 Uploading files...');
202
+
203
+ // Upload all files
204
+ const uploadedFileIds = [];
205
+ for (let i = 0; i < filePaths.length; i++) {
206
+ const currentFile = filePaths[i];
207
+ console.log(`📤 Uploading ${i + 1}/${filePaths.length}: ${path.basename(currentFile)}`);
208
+
209
+ try {
210
+ const fileId = await uploadSingleFileToCanvas(courseId, assignmentId, currentFile);
211
+ uploadedFileIds.push(fileId);
212
+ console.log(`✅ ${path.basename(currentFile)} uploaded successfully`);
213
+ } catch (error) {
214
+ console.error(`❌ Failed to upload ${currentFile}: ${error.message}`);
215
+ const continueUpload = await askQuestion(rl, 'Continue with remaining files? (Y/n): ');
216
+ if (continueUpload.toLowerCase() === 'n' || continueUpload.toLowerCase() === 'no') {
217
+ break;
218
+ }
219
+ }
220
+ }
221
+
222
+ if (uploadedFileIds.length === 0) {
223
+ console.log('❌ No files were uploaded successfully.');
224
+ rl.close();
225
+ return;
226
+ }
227
+
228
+ // Submit the assignment with all uploaded files
229
+ console.log('\n📝 Submitting assignment...');
230
+ const submission = await submitAssignmentWithFiles(courseId, assignmentId, uploadedFileIds);
231
+
232
+ console.log(`✅ Assignment submitted successfully with ${uploadedFileIds.length} file(s)!`);
233
+ console.log(`Submission ID: ${submission.id}`);
234
+ console.log(`Submitted at: ${new Date(submission.submitted_at).toLocaleString()}`);
235
+
236
+ } catch (error) {
237
+ console.error('❌ Submission failed:', error.message);
238
+ } finally {
239
+ rl.close();
240
+ }
241
+ }
242
+
243
+ async function selectFiles(rl) {
244
+ console.log('📁 File selection options:');
245
+ console.log('1. Enter file path(s) manually');
246
+ console.log('2. Select from current directory');
247
+
248
+ const fileChoice = await askQuestion(rl, '\nChoose option (1-2): ');
249
+
250
+ if (fileChoice === '1') {
251
+ return await selectFilesManually(rl);
252
+ } else if (fileChoice === '2') {
253
+ return await selectFilesFromDirectory(rl);
254
+ } else {
255
+ console.log('Invalid option.');
256
+ return [];
257
+ }
258
+ }
259
+
260
+ async function selectFilesManually(rl) {
261
+ const singleOrMultiple = await askQuestion(rl, 'Submit single file or multiple files? (s/m): ');
262
+ const filePaths = [];
263
+
264
+ if (singleOrMultiple.toLowerCase() === 'm' || singleOrMultiple.toLowerCase() === 'multiple') {
265
+ console.log('Enter file paths (one per line). Press Enter on empty line to finish:');
266
+ let fileInput = '';
267
+ while (true) {
268
+ fileInput = await askQuestion(rl, 'File path: ');
269
+ if (fileInput === '') break;
270
+ filePaths.push(fileInput);
271
+ }
272
+ } else {
273
+ const singleFile = await askQuestion(rl, 'Enter file path: ');
274
+ filePaths.push(singleFile);
275
+ }
276
+
277
+ return filePaths;
278
+ }
279
+
280
+ async function selectFilesFromDirectory(rl) {
281
+ try {
282
+ const files = fs.readdirSync('.').filter(file =>
283
+ fs.statSync(file).isFile() &&
284
+ !file.startsWith('.') &&
285
+ file !== 'package.json' &&
286
+ file !== 'README.md'
287
+ );
288
+
289
+ if (files.length === 0) {
290
+ console.log('No suitable files found in current directory.');
291
+ const manualFile = await askQuestion(rl, 'Enter file path manually: ');
292
+ return [manualFile];
293
+ }
294
+
295
+ console.log('\nFiles in current directory:');
296
+ files.forEach((file, index) => {
297
+ const stats = fs.statSync(file);
298
+ const size = (stats.size / 1024).toFixed(1) + ' KB';
299
+ console.log(`${index + 1}. ${file} (${size})`);
300
+ });
301
+
302
+ const multipleFiles = await askQuestion(rl, '\nSelect multiple files? (y/N): ');
303
+
304
+ if (multipleFiles.toLowerCase() === 'y' || multipleFiles.toLowerCase() === 'yes') {
305
+ return await selectMultipleFilesFromList(rl, files);
306
+ } else {
307
+ return await selectSingleFileFromList(rl, files);
308
+ }
309
+ } catch (error) {
310
+ console.log('Error reading directory.');
311
+ const manualFile = await askQuestion(rl, 'Enter file path manually: ');
312
+ return [manualFile];
313
+ }
314
+ }
315
+
316
+ async function selectMultipleFilesFromList(rl, files) {
317
+ console.log('Enter file numbers separated by commas (e.g., 1,3,5) or ranges (e.g., 1-3):');
318
+ const fileIndices = await askQuestion(rl, 'File numbers: ');
319
+
320
+ const selectedIndices = [];
321
+ const parts = fileIndices.split(',');
322
+
323
+ for (const part of parts) {
324
+ const trimmed = part.trim();
325
+ if (trimmed.includes('-')) {
326
+ // Handle range (e.g., 1-3)
327
+ const [start, end] = trimmed.split('-').map(n => parseInt(n.trim()));
328
+ for (let i = start; i <= end; i++) {
329
+ selectedIndices.push(i - 1); // Convert to 0-based index
330
+ }
331
+ } else {
332
+ // Handle single number
333
+ selectedIndices.push(parseInt(trimmed) - 1); // Convert to 0-based index
334
+ }
335
+ }
336
+
337
+ // Remove duplicates and filter valid indices
338
+ const uniqueIndices = [...new Set(selectedIndices)].filter(
339
+ index => index >= 0 && index < files.length
340
+ );
341
+
342
+ if (uniqueIndices.length === 0) {
343
+ console.log('No valid file selections.');
344
+ const manualFile = await askQuestion(rl, 'Enter file path manually: ');
345
+ return [manualFile];
346
+ }
347
+
348
+ return uniqueIndices.map(index => files[index]);
349
+ }
350
+
351
+ async function selectSingleFileFromList(rl, files) {
352
+ const fileIndex = await askQuestion(rl, '\nEnter file number: ');
353
+ const selectedFileIndex = parseInt(fileIndex) - 1;
354
+
355
+ if (selectedFileIndex >= 0 && selectedFileIndex < files.length) {
356
+ return [files[selectedFileIndex]];
357
+ } else {
358
+ console.log('Invalid file selection.');
359
+ const manualFile = await askQuestion(rl, 'Enter file path manually: ');
360
+ return [manualFile];
361
+ }
362
+ }
363
+
364
+ module.exports = {
365
+ submitAssignment
366
+ };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Canvas API client
3
+ */
4
+
5
+ const axios = require('axios');
6
+ const fs = require('fs');
7
+ const { getInstanceConfig } = require('./config');
8
+
9
+ /**
10
+ * Make Canvas API request
11
+ */
12
+ async function makeCanvasRequest(method, endpoint, queryParams = [], requestBody = null) {
13
+ const instanceConfig = getInstanceConfig();
14
+
15
+ // Construct the full URL
16
+ const baseUrl = `https://${instanceConfig.domain}/api/v1`;
17
+ const url = `${baseUrl}/${endpoint.replace(/^\//, '')}`;
18
+
19
+ // Setup request configuration
20
+ const config = {
21
+ method: method.toLowerCase(),
22
+ url: url,
23
+ headers: {
24
+ 'Authorization': `Bearer ${instanceConfig.token}`,
25
+ 'Content-Type': 'application/json'
26
+ }
27
+ };
28
+
29
+ // Add query parameters
30
+ if (queryParams.length > 0) {
31
+ const params = new URLSearchParams();
32
+ queryParams.forEach(param => {
33
+ const [key, value] = param.split('=', 2);
34
+ params.append(key, value || '');
35
+ });
36
+ config.params = params;
37
+ }
38
+
39
+ // Add request body for POST/PUT requests
40
+ if (requestBody && (method.toLowerCase() === 'post' || method.toLowerCase() === 'put')) {
41
+ if (requestBody.startsWith('@')) {
42
+ // Read from file
43
+ const filename = requestBody.substring(1);
44
+ try {
45
+ config.data = JSON.parse(fs.readFileSync(filename, 'utf8'));
46
+ } catch (error) {
47
+ console.error(`Error reading file ${filename}: ${error.message}`);
48
+ process.exit(1);
49
+ }
50
+ } else {
51
+ // Parse JSON string
52
+ try {
53
+ config.data = JSON.parse(requestBody);
54
+ } catch (error) {
55
+ console.error(`Error parsing JSON: ${error.message}`);
56
+ process.exit(1);
57
+ }
58
+ }
59
+ }
60
+
61
+ try {
62
+ const response = await axios(config);
63
+ return response.data;
64
+ } catch (error) {
65
+ if (error.response) {
66
+ console.error(`HTTP ${error.response.status}: ${error.response.statusText}`);
67
+ if (error.response.data) {
68
+ console.error(JSON.stringify(error.response.data, null, 2));
69
+ }
70
+ } else {
71
+ console.error(`Request failed: ${error.message}`);
72
+ }
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ makeCanvasRequest
79
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Configuration validation and setup helpers
3
+ */
4
+
5
+ const { configExists, readConfig } = require('./config');
6
+ const { createReadlineInterface, askQuestion } = require('./interactive');
7
+
8
+ /**
9
+ * Check if configuration is valid and prompt setup if needed
10
+ */
11
+ async function ensureConfig() {
12
+ if (!configExists()) {
13
+ console.log('❌ No Canvas configuration found!');
14
+ console.log('\n🚀 Let\'s set up your Canvas CLI configuration...\n');
15
+
16
+ const rl = createReadlineInterface();
17
+ const setup = await askQuestion(rl, 'Would you like to set up your configuration now? (Y/n): ');
18
+ rl.close();
19
+
20
+ if (setup.toLowerCase() === 'n' || setup.toLowerCase() === 'no') {
21
+ console.log('\n📝 To set up later, run: canvas config setup');
22
+ console.log('💡 Or set environment variables: CANVAS_DOMAIN and CANVAS_API_TOKEN');
23
+ process.exit(1);
24
+ }
25
+
26
+ // Import and run setup (dynamic import to avoid circular dependency)
27
+ const { setupConfig } = require('../commands/config');
28
+ await setupConfig();
29
+
30
+ // Check if setup was successful
31
+ if (!configExists()) {
32
+ console.log('\n❌ Configuration setup was not completed. Please run "canvas config setup" to try again.');
33
+ process.exit(1);
34
+ }
35
+
36
+ console.log('\n✅ Configuration complete! You can now use Canvas CLI commands.');
37
+ return true;
38
+ }
39
+
40
+ // Validate existing config
41
+ const config = readConfig();
42
+ if (!config || !config.domain || !config.token) {
43
+ console.log('❌ Invalid configuration found. Please run "canvas config setup" to reconfigure.');
44
+ process.exit(1);
45
+ }
46
+
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Wrapper function to ensure config exists before running a command
52
+ */
53
+ function requireConfig(commandFunction) {
54
+ return async function(...args) {
55
+ await ensureConfig();
56
+ return commandFunction(...args);
57
+ };
58
+ }
59
+
60
+ module.exports = {
61
+ ensureConfig,
62
+ requireConfig
63
+ };
package/lib/config.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Configuration management for Canvas CLI
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ require('dotenv').config();
9
+
10
+ // Configuration file path in user's home directory
11
+ const CONFIG_FILE = path.join(os.homedir(), '.canvas-cli-config.json');
12
+
13
+ /**
14
+ * Get default configuration structure
15
+ */
16
+ function getDefaultConfig() {
17
+ return {
18
+ domain: '',
19
+ token: '',
20
+ createdAt: new Date().toISOString(),
21
+ lastUpdated: new Date().toISOString()
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Load configuration from file or environment variables
27
+ */
28
+ function loadConfig() {
29
+ try {
30
+ // First, try to load from config file
31
+ if (fs.existsSync(CONFIG_FILE)) {
32
+ const configData = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
33
+ if (configData.domain && configData.token) {
34
+ // Clean up domain - remove https:// and trailing slashes
35
+ const domain = configData.domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
36
+ return { domain, token: configData.token };
37
+ }
38
+ }
39
+
40
+ // Fallback to environment variables
41
+ let domain = process.env.CANVAS_DOMAIN;
42
+ const token = process.env.CANVAS_API_TOKEN;
43
+
44
+ if (!domain || !token) {
45
+ console.error('❌ No Canvas configuration found!');
46
+ console.error('\nPlease run "canvas config setup" to configure your Canvas credentials.');
47
+ console.error('Or set environment variables:');
48
+ console.error(' CANVAS_DOMAIN=your-canvas-domain.instructure.com');
49
+ console.error(' CANVAS_API_TOKEN=your-api-token');
50
+ process.exit(1);
51
+ }
52
+
53
+ // Clean up domain - remove https:// and trailing slashes
54
+ domain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
55
+
56
+ return { domain, token };
57
+ } catch (error) {
58
+ console.error(`❌ Error loading configuration: ${error.message}`);
59
+ process.exit(1);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Save configuration to file
65
+ */
66
+ function saveConfig(domain, token) {
67
+ try {
68
+ const config = {
69
+ domain: domain.replace(/^https?:\/\//, '').replace(/\/$/, ''),
70
+ token,
71
+ createdAt: fs.existsSync(CONFIG_FILE) ?
72
+ JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')).createdAt :
73
+ new Date().toISOString(),
74
+ lastUpdated: new Date().toISOString()
75
+ };
76
+
77
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
78
+ console.log(`✅ Configuration saved to ${CONFIG_FILE}`);
79
+ return true;
80
+ } catch (error) {
81
+ console.error(`❌ Error saving configuration: ${error.message}`);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get configuration file path
88
+ */
89
+ function getConfigPath() {
90
+ return CONFIG_FILE;
91
+ }
92
+
93
+ /**
94
+ * Check if configuration file exists
95
+ */
96
+ function configExists() {
97
+ return fs.existsSync(CONFIG_FILE);
98
+ }
99
+
100
+ /**
101
+ * Read current configuration (if exists)
102
+ */
103
+ function readConfig() {
104
+ try {
105
+ if (fs.existsSync(CONFIG_FILE)) {
106
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
107
+ }
108
+ return null;
109
+ } catch (error) {
110
+ console.error(`❌ Error reading configuration: ${error.message}`);
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Delete configuration file
117
+ */
118
+ function deleteConfig() {
119
+ try {
120
+ if (fs.existsSync(CONFIG_FILE)) {
121
+ fs.unlinkSync(CONFIG_FILE);
122
+ console.log('✅ Configuration file deleted successfully.');
123
+ return true;
124
+ } else {
125
+ console.log('â„šī¸ No configuration file found.');
126
+ return false;
127
+ }
128
+ } catch (error) {
129
+ console.error(`❌ Error deleting configuration: ${error.message}`);
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get the Canvas instance configuration
136
+ */
137
+ function getInstanceConfig() {
138
+ return loadConfig();
139
+ }
140
+
141
+ module.exports = {
142
+ loadConfig,
143
+ getInstanceConfig,
144
+ saveConfig,
145
+ getConfigPath,
146
+ configExists,
147
+ readConfig,
148
+ deleteConfig,
149
+ getDefaultConfig
150
+ };