@mo7yw4ng/openape 1.0.4 → 1.0.6
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/README.md +11 -8
- package/esm/deno.js +1 -1
- package/esm/src/commands/announcements.d.ts.map +1 -1
- package/esm/src/commands/announcements.js +22 -85
- package/esm/src/commands/assignments.d.ts.map +1 -1
- package/esm/src/commands/assignments.js +2 -3
- package/esm/src/commands/auth.d.ts.map +1 -1
- package/esm/src/commands/auth.js +24 -14
- package/esm/src/commands/calendar.d.ts.map +1 -1
- package/esm/src/commands/calendar.js +32 -84
- package/esm/src/commands/courses.d.ts.map +1 -1
- package/esm/src/commands/courses.js +2 -38
- package/esm/src/commands/forums.d.ts.map +1 -1
- package/esm/src/commands/forums.js +47 -175
- package/esm/src/commands/grades.d.ts.map +1 -1
- package/esm/src/commands/grades.js +10 -47
- package/esm/src/commands/materials.d.ts.map +1 -1
- package/esm/src/commands/materials.js +135 -223
- package/esm/src/commands/quizzes.d.ts.map +1 -1
- package/esm/src/commands/quizzes.js +32 -56
- package/esm/src/commands/skills.js +3 -3
- package/esm/src/commands/upload.d.ts.map +1 -1
- package/esm/src/commands/upload.js +2 -5
- package/esm/src/commands/videos.d.ts.map +1 -1
- package/esm/src/commands/videos.js +6 -76
- package/esm/src/index.d.ts +2 -1
- package/esm/src/index.d.ts.map +1 -1
- package/esm/src/index.js +5 -1
- package/esm/src/lib/auth.d.ts +21 -2
- package/esm/src/lib/auth.d.ts.map +1 -1
- package/esm/src/lib/auth.js +79 -20
- package/esm/src/lib/logger.d.ts +2 -2
- package/esm/src/lib/logger.d.ts.map +1 -1
- package/esm/src/lib/logger.js +6 -4
- package/esm/src/lib/moodle.d.ts +18 -0
- package/esm/src/lib/moodle.d.ts.map +1 -1
- package/esm/src/lib/moodle.js +54 -2
- package/esm/src/lib/utils.d.ts +3 -8
- package/esm/src/lib/utils.d.ts.map +1 -1
- package/esm/src/lib/utils.js +3 -10
- package/package.json +1 -1
- package/script/deno.js +1 -1
- package/script/src/commands/announcements.d.ts.map +1 -1
- package/script/src/commands/announcements.js +23 -89
- package/script/src/commands/assignments.d.ts.map +1 -1
- package/script/src/commands/assignments.js +2 -3
- package/script/src/commands/auth.d.ts.map +1 -1
- package/script/src/commands/auth.js +24 -14
- package/script/src/commands/calendar.d.ts.map +1 -1
- package/script/src/commands/calendar.js +33 -85
- package/script/src/commands/courses.d.ts.map +1 -1
- package/script/src/commands/courses.js +9 -48
- package/script/src/commands/forums.d.ts.map +1 -1
- package/script/src/commands/forums.js +50 -181
- package/script/src/commands/grades.d.ts.map +1 -1
- package/script/src/commands/grades.js +14 -54
- package/script/src/commands/materials.d.ts.map +1 -1
- package/script/src/commands/materials.js +132 -220
- package/script/src/commands/quizzes.d.ts.map +1 -1
- package/script/src/commands/quizzes.js +40 -67
- package/script/src/commands/skills.js +3 -3
- package/script/src/commands/upload.d.ts.map +1 -1
- package/script/src/commands/upload.js +2 -5
- package/script/src/commands/videos.d.ts.map +1 -1
- package/script/src/commands/videos.js +11 -81
- package/script/src/index.d.ts +2 -1
- package/script/src/index.d.ts.map +1 -1
- package/script/src/index.js +5 -1
- package/script/src/lib/auth.d.ts +21 -2
- package/script/src/lib/auth.d.ts.map +1 -1
- package/script/src/lib/auth.js +83 -56
- package/script/src/lib/logger.d.ts +2 -2
- package/script/src/lib/logger.d.ts.map +1 -1
- package/script/src/lib/logger.js +6 -4
- package/script/src/lib/moodle.d.ts +18 -0
- package/script/src/lib/moodle.d.ts.map +1 -1
- package/script/src/lib/moodle.js +56 -2
- package/script/src/lib/utils.d.ts +3 -8
- package/script/src/lib/utils.d.ts.map +1 -1
- package/script/src/lib/utils.js +3 -11
- package/skills/openape/SKILL.md +10 -8
|
@@ -1,62 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getEnrolledCoursesApi, getForumsApi, getForumDiscussionsApi, getDiscussionPostsApi, addForumDiscussionApi, addForumPostApi, deleteForumPostApi } from "../lib/moodle.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import fs from "node:fs";
|
|
1
|
+
import { stripHtmlTags, getOutputFormat, formatTimestamp } from "../lib/utils.js";
|
|
2
|
+
import { getEnrolledCoursesApi, getForumsApi, getForumDiscussionsApi, getDiscussionPostsApi, addForumDiscussionApi, addForumPostApi, deleteForumPostApi, resolveForumId } from "../lib/moodle.js";
|
|
3
|
+
import { createApiContext } from "../lib/auth.js";
|
|
4
|
+
import { formatAndOutput } from "../index.js";
|
|
7
5
|
export function registerForumsCommand(program) {
|
|
8
6
|
const forumsCmd = program.command("forums");
|
|
9
7
|
forumsCmd.description("Forum operations");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
13
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
14
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
15
|
-
const log = createLogger(opts.verbose, silent);
|
|
16
|
-
const baseDir = getBaseDir();
|
|
17
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
18
|
-
// Check if session exists
|
|
19
|
-
if (!fs.existsSync(sessionPath)) {
|
|
20
|
-
log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
|
|
21
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
// Try to load WS token
|
|
25
|
-
const wsToken = loadWsToken(sessionPath);
|
|
26
|
-
if (!wsToken) {
|
|
27
|
-
log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
// Try to load sesskey from cache
|
|
31
|
-
const sesskey = loadSesskey(sessionPath) || undefined;
|
|
32
|
-
return {
|
|
33
|
-
log,
|
|
34
|
-
session: {
|
|
35
|
-
wsToken,
|
|
36
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
37
|
-
sesskey,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
forumsCmd
|
|
42
|
-
.command("list")
|
|
43
|
-
.description("List forums from in-progress courses")
|
|
44
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
45
|
-
.action(async (options, command) => {
|
|
46
|
-
const apiContext = await createApiContext(options, command);
|
|
8
|
+
async function listForums(classification) {
|
|
9
|
+
const apiContext = await createApiContext({});
|
|
47
10
|
if (!apiContext) {
|
|
48
11
|
process.exitCode = 1;
|
|
49
12
|
return;
|
|
50
13
|
}
|
|
51
14
|
const courses = await getEnrolledCoursesApi(apiContext.session, {
|
|
52
|
-
classification
|
|
15
|
+
classification,
|
|
53
16
|
});
|
|
54
|
-
// Get forums via WS API (no browser needed!)
|
|
55
17
|
const courseIds = courses.map(c => c.id);
|
|
56
18
|
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
19
|
+
const courseMap = new Map(courses.map(c => [c.id, c]));
|
|
57
20
|
const allForums = [];
|
|
58
21
|
for (const wsForum of wsForums) {
|
|
59
|
-
const course =
|
|
22
|
+
const course = courseMap.get(wsForum.courseid);
|
|
60
23
|
if (course) {
|
|
61
24
|
allForums.push({
|
|
62
25
|
course_id: wsForum.courseid,
|
|
@@ -66,62 +29,22 @@ export function registerForumsCommand(program) {
|
|
|
66
29
|
forum_id: wsForum.id,
|
|
67
30
|
name: wsForum.name,
|
|
68
31
|
timemodified: wsForum.timemodified,
|
|
69
|
-
// url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
|
|
70
32
|
});
|
|
71
33
|
}
|
|
72
34
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const forum of allForums) {
|
|
80
|
-
console.log(JSON.stringify(forum));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
35
|
+
formatAndOutput(allForums, "json", apiContext.log, { status: "success", timestamp: new Date().toISOString(), total_courses: courses.length, total_forums: allForums.length });
|
|
36
|
+
}
|
|
37
|
+
forumsCmd
|
|
38
|
+
.command("list")
|
|
39
|
+
.description("List forums from in-progress courses")
|
|
40
|
+
.action(() => listForums("inprogress"));
|
|
83
41
|
forumsCmd
|
|
84
42
|
.command("list-all")
|
|
85
43
|
.description("List all forums across all courses")
|
|
86
44
|
.option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
|
|
87
|
-
.
|
|
88
|
-
.action(async (options, command) => {
|
|
89
|
-
const apiContext = await createApiContext(options, command);
|
|
90
|
-
if (!apiContext) {
|
|
91
|
-
process.exitCode = 1;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
45
|
+
.action(async (options) => {
|
|
94
46
|
const classification = options.level === "all" ? undefined : "inprogress";
|
|
95
|
-
|
|
96
|
-
classification,
|
|
97
|
-
});
|
|
98
|
-
// Get forums via WS API (no browser needed!)
|
|
99
|
-
const courseIds = courses.map(c => c.id);
|
|
100
|
-
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
101
|
-
const allForums = [];
|
|
102
|
-
for (const wsForum of wsForums) {
|
|
103
|
-
const course = courses.find(c => c.id === wsForum.courseid);
|
|
104
|
-
if (course) {
|
|
105
|
-
allForums.push({
|
|
106
|
-
course_id: wsForum.courseid,
|
|
107
|
-
course_name: course.fullname,
|
|
108
|
-
intro: wsForum.intro,
|
|
109
|
-
cmid: wsForum.cmid.toString(),
|
|
110
|
-
forum_id: wsForum.id,
|
|
111
|
-
name: wsForum.name,
|
|
112
|
-
timemodified: wsForum.timemodified,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
console.log(JSON.stringify({
|
|
117
|
-
status: "success",
|
|
118
|
-
timestamp: new Date().toISOString(),
|
|
119
|
-
total_courses: courses.length,
|
|
120
|
-
total_forums: allForums.length,
|
|
121
|
-
}));
|
|
122
|
-
for (const forum of allForums) {
|
|
123
|
-
console.log(JSON.stringify(forum));
|
|
124
|
-
}
|
|
47
|
+
await listForums(classification);
|
|
125
48
|
});
|
|
126
49
|
forumsCmd
|
|
127
50
|
.command("discussions")
|
|
@@ -129,51 +52,29 @@ export function registerForumsCommand(program) {
|
|
|
129
52
|
.argument("<forum-id>", "Forum ID")
|
|
130
53
|
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
131
54
|
.action(async (forumId, options, command) => {
|
|
55
|
+
const output = getOutputFormat(command);
|
|
132
56
|
const apiContext = await createApiContext(options, command);
|
|
133
57
|
if (!apiContext) {
|
|
134
58
|
process.exitCode = 1;
|
|
135
59
|
return;
|
|
136
60
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
141
|
-
// Get forums via WS API
|
|
142
|
-
const courseIds = courses.map(c => c.id);
|
|
143
|
-
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
144
|
-
// Find forum by cmid or instance ID
|
|
145
|
-
const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
|
|
146
|
-
if (!targetForum) {
|
|
147
|
-
console.log(JSON.stringify({ status: "error", error: "Forum not found" }));
|
|
61
|
+
const resolved = await resolveForumId(apiContext.session, forumId);
|
|
62
|
+
if (!resolved) {
|
|
63
|
+
apiContext.log.error("Forum not found");
|
|
148
64
|
process.exitCode = 1;
|
|
149
65
|
return;
|
|
150
66
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
course_name: course?.fullname,
|
|
163
|
-
total_discussions: discussions.length,
|
|
164
|
-
};
|
|
165
|
-
console.log(JSON.stringify(meta));
|
|
166
|
-
for (const d of discussions) {
|
|
167
|
-
console.log(JSON.stringify({
|
|
168
|
-
id: d.id,
|
|
169
|
-
name: d.name,
|
|
170
|
-
user_id: d.userId,
|
|
171
|
-
time_modified: d.timeModified,
|
|
172
|
-
post_count: d.postCount,
|
|
173
|
-
unread: d.unread,
|
|
174
|
-
message: stripHtmlTags(d.message || ""),
|
|
175
|
-
}));
|
|
176
|
-
}
|
|
67
|
+
const discussions = await getForumDiscussionsApi(apiContext.session, resolved.forumId);
|
|
68
|
+
const items = discussions.map(d => ({
|
|
69
|
+
id: d.id,
|
|
70
|
+
name: d.name,
|
|
71
|
+
user_id: d.userId,
|
|
72
|
+
time_modified: d.timeModified,
|
|
73
|
+
post_count: d.postCount,
|
|
74
|
+
unread: d.unread,
|
|
75
|
+
message: stripHtmlTags(d.message || ""),
|
|
76
|
+
}));
|
|
77
|
+
formatAndOutput(items, output, apiContext.log, { status: "success", timestamp: new Date().toISOString(), forum_id: resolved.forumId, forum_name: resolved.name ?? null, course_id: resolved.courseid ?? null, total_discussions: discussions.length });
|
|
177
78
|
});
|
|
178
79
|
forumsCmd
|
|
179
80
|
.command("posts")
|
|
@@ -188,38 +89,17 @@ export function registerForumsCommand(program) {
|
|
|
188
89
|
return;
|
|
189
90
|
}
|
|
190
91
|
const posts = await getDiscussionPostsApi(apiContext.session, parseInt(discussionId, 10));
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
modified: formatTimestamp(p.modified),
|
|
203
|
-
message: p.message,
|
|
204
|
-
unread: p.unread,
|
|
205
|
-
})),
|
|
206
|
-
summary: {
|
|
207
|
-
total_posts: posts.length,
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
console.log(JSON.stringify(result));
|
|
211
|
-
}
|
|
212
|
-
else if (output === "table") {
|
|
213
|
-
console.log(`Discussion ${discussionId} - ${posts.length} posts`);
|
|
214
|
-
console.log("Use --output json to see full post content");
|
|
215
|
-
const tablePosts = posts.map(p => ({
|
|
216
|
-
id: p.id,
|
|
217
|
-
subject: p.subject.substring(0, 50) + (p.subject.length > 50 ? "..." : ""),
|
|
218
|
-
author: p.author,
|
|
219
|
-
created: new Date(p.created * 1000).toLocaleString(),
|
|
220
|
-
}));
|
|
221
|
-
console.table(tablePosts);
|
|
222
|
-
}
|
|
92
|
+
const items = posts.map(p => ({
|
|
93
|
+
id: p.id,
|
|
94
|
+
subject: p.subject,
|
|
95
|
+
author: p.author,
|
|
96
|
+
author_id: p.authorId,
|
|
97
|
+
created: formatTimestamp(p.created),
|
|
98
|
+
modified: formatTimestamp(p.modified),
|
|
99
|
+
message: p.message,
|
|
100
|
+
unread: p.unread,
|
|
101
|
+
}));
|
|
102
|
+
formatAndOutput(items, output, apiContext.log, { status: "success", timestamp: new Date().toISOString(), discussion_id: discussionId, total_posts: posts.length });
|
|
223
103
|
});
|
|
224
104
|
forumsCmd
|
|
225
105
|
.command("post")
|
|
@@ -236,22 +116,14 @@ export function registerForumsCommand(program) {
|
|
|
236
116
|
return;
|
|
237
117
|
}
|
|
238
118
|
const { log, session } = apiContext;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
classification: "inprogress",
|
|
242
|
-
});
|
|
243
|
-
const courseIds = courses.map(c => c.id);
|
|
244
|
-
const wsForums = await getForumsApi(session, courseIds);
|
|
245
|
-
// Find forum by cmid or instance ID
|
|
246
|
-
const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
|
|
247
|
-
if (!targetForum) {
|
|
119
|
+
const resolved = await resolveForumId(session, forumId);
|
|
120
|
+
if (!resolved) {
|
|
248
121
|
log.error(`Forum not found: ${forumId}`);
|
|
249
122
|
process.exitCode = 1;
|
|
250
123
|
return;
|
|
251
124
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const result = await addForumDiscussionApi(session, targetForum.id, subject, message);
|
|
125
|
+
log.info(`Posting to forum: ${resolved.name ?? forumId}`);
|
|
126
|
+
const result = await addForumDiscussionApi(session, resolved.forumId, subject, message);
|
|
255
127
|
if (result.success) {
|
|
256
128
|
log.success(`✓ Discussion posted successfully!`);
|
|
257
129
|
log.info(` Discussion ID: ${result.discussionId}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grades.d.ts","sourceRoot":"","sources":["../../../src/src/commands/grades.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grades.d.ts","sourceRoot":"","sources":["../../../src/src/commands/grades.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+F5D"}
|
|
@@ -1,45 +1,10 @@
|
|
|
1
|
-
import { getBaseDir } from "../lib/utils.js";
|
|
2
1
|
import { getEnrolledCoursesApi, getCourseGradesApi } from "../lib/moodle.js";
|
|
3
|
-
import {
|
|
4
|
-
import { loadWsToken } from "../lib/token.js";
|
|
2
|
+
import { createApiContext } from "../lib/auth.js";
|
|
5
3
|
import { formatAndOutput } from "../index.js";
|
|
6
|
-
import
|
|
7
|
-
import fs from "node:fs";
|
|
4
|
+
import { getOutputFormat } from "../lib/utils.js";
|
|
8
5
|
export function registerGradesCommand(program) {
|
|
9
6
|
const gradesCmd = program.command("grades");
|
|
10
7
|
gradesCmd.description("Grade operations");
|
|
11
|
-
function getOutputFormat(command) {
|
|
12
|
-
const opts = command.optsWithGlobals();
|
|
13
|
-
return opts.output || "json";
|
|
14
|
-
}
|
|
15
|
-
// Pure API context - no browser required (fast!)
|
|
16
|
-
async function createApiContext(options, command) {
|
|
17
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
18
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
19
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
20
|
-
const log = createLogger(opts.verbose, silent);
|
|
21
|
-
const baseDir = getBaseDir();
|
|
22
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
23
|
-
// Check if session exists
|
|
24
|
-
if (!fs.existsSync(sessionPath)) {
|
|
25
|
-
log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
|
|
26
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
// Try to load WS token
|
|
30
|
-
const wsToken = loadWsToken(sessionPath);
|
|
31
|
-
if (!wsToken) {
|
|
32
|
-
log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
log,
|
|
37
|
-
session: {
|
|
38
|
-
wsToken,
|
|
39
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
8
|
gradesCmd
|
|
44
9
|
.command("summary")
|
|
45
10
|
.description("Show grade summary across all courses")
|
|
@@ -52,9 +17,13 @@ export function registerGradesCommand(program) {
|
|
|
52
17
|
return;
|
|
53
18
|
}
|
|
54
19
|
const courses = await getEnrolledCoursesApi(apiContext.session);
|
|
20
|
+
const gradeResults = await Promise.allSettled(courses.map(course => getCourseGradesApi(apiContext.session, course.id)
|
|
21
|
+
.then(grades => ({ course, grades }))));
|
|
55
22
|
const gradeSummaries = [];
|
|
56
|
-
for (const
|
|
57
|
-
|
|
23
|
+
for (const result of gradeResults) {
|
|
24
|
+
if (result.status !== "fulfilled")
|
|
25
|
+
continue;
|
|
26
|
+
const { course, grades } = result.value;
|
|
58
27
|
gradeSummaries.push({
|
|
59
28
|
courseId: course.id,
|
|
60
29
|
courseName: course.fullname,
|
|
@@ -64,19 +33,13 @@ export function registerGradesCommand(program) {
|
|
|
64
33
|
totalUsers: grades.totalUsers,
|
|
65
34
|
});
|
|
66
35
|
}
|
|
67
|
-
// Calculate overall statistics
|
|
68
36
|
const gradedCourses = gradeSummaries.filter(g => g.grade !== undefined && g.grade !== null && g.grade !== "-");
|
|
69
37
|
const averageRank = gradeSummaries
|
|
70
38
|
.filter(g => g.rank !== undefined && g.rank !== null)
|
|
71
39
|
.reduce((sum, g) => sum + (g.rank || 0), 0) /
|
|
72
40
|
(gradeSummaries.filter(g => g.rank !== undefined && g.rank !== null).length || 1);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
graded_courses: gradedCourses.length,
|
|
76
|
-
average_rank: averageRank.toFixed(1),
|
|
77
|
-
grades: gradeSummaries,
|
|
78
|
-
};
|
|
79
|
-
formatAndOutput(summaryData, output, apiContext.log);
|
|
41
|
+
apiContext.log.info(`Total: ${courses.length} courses, ${gradedCourses.length} graded, avg rank: ${averageRank.toFixed(1)}`);
|
|
42
|
+
formatAndOutput(gradeSummaries, output, apiContext.log);
|
|
80
43
|
});
|
|
81
44
|
gradesCmd
|
|
82
45
|
.command("course")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../../src/src/commands/materials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../../src/src/commands/materials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwd/D"}
|