@mo7yw4ng/openape 1.0.5 → 2.0.3
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/bin/openape +29 -0
- package/bin/openape.js +29 -0
- package/package.json +22 -28
- package/LICENSE +0 -21
- package/README.md +0 -135
- package/esm/_dnt.polyfills.d.ts +0 -101
- package/esm/_dnt.polyfills.d.ts.map +0 -1
- package/esm/_dnt.polyfills.js +0 -127
- package/esm/_dnt.shims.d.ts +0 -6
- package/esm/_dnt.shims.d.ts.map +0 -1
- package/esm/_dnt.shims.js +0 -61
- package/esm/deno.d.ts +0 -25
- package/esm/deno.d.ts.map +0 -1
- package/esm/deno.js +0 -23
- package/esm/package.json +0 -3
- package/esm/src/commands/announcements.d.ts +0 -3
- package/esm/src/commands/announcements.d.ts.map +0 -1
- package/esm/src/commands/announcements.js +0 -134
- package/esm/src/commands/assignments.d.ts +0 -3
- package/esm/src/commands/assignments.d.ts.map +0 -1
- package/esm/src/commands/assignments.js +0 -230
- package/esm/src/commands/auth.d.ts +0 -3
- package/esm/src/commands/auth.d.ts.map +0 -1
- package/esm/src/commands/auth.js +0 -290
- package/esm/src/commands/calendar.d.ts +0 -3
- package/esm/src/commands/calendar.d.ts.map +0 -1
- package/esm/src/commands/calendar.js +0 -179
- package/esm/src/commands/courses.d.ts +0 -3
- package/esm/src/commands/courses.d.ts.map +0 -1
- package/esm/src/commands/courses.js +0 -348
- package/esm/src/commands/forums.d.ts +0 -3
- package/esm/src/commands/forums.d.ts.map +0 -1
- package/esm/src/commands/forums.js +0 -318
- package/esm/src/commands/grades.d.ts +0 -3
- package/esm/src/commands/grades.d.ts.map +0 -1
- package/esm/src/commands/grades.js +0 -121
- package/esm/src/commands/materials.d.ts +0 -3
- package/esm/src/commands/materials.d.ts.map +0 -1
- package/esm/src/commands/materials.js +0 -413
- package/esm/src/commands/quizzes.d.ts +0 -3
- package/esm/src/commands/quizzes.d.ts.map +0 -1
- package/esm/src/commands/quizzes.js +0 -271
- package/esm/src/commands/skills.d.ts +0 -3
- package/esm/src/commands/skills.d.ts.map +0 -1
- package/esm/src/commands/skills.js +0 -106
- package/esm/src/commands/upload.d.ts +0 -3
- package/esm/src/commands/upload.d.ts.map +0 -1
- package/esm/src/commands/upload.js +0 -58
- package/esm/src/commands/videos.d.ts +0 -3
- package/esm/src/commands/videos.d.ts.map +0 -1
- package/esm/src/commands/videos.js +0 -336
- package/esm/src/index.d.ts +0 -27
- package/esm/src/index.d.ts.map +0 -1
- package/esm/src/index.js +0 -160
- package/esm/src/lib/auth.d.ts +0 -47
- package/esm/src/lib/auth.d.ts.map +0 -1
- package/esm/src/lib/auth.js +0 -227
- package/esm/src/lib/config.d.ts +0 -6
- package/esm/src/lib/config.d.ts.map +0 -1
- package/esm/src/lib/config.js +0 -36
- package/esm/src/lib/logger.d.ts +0 -3
- package/esm/src/lib/logger.d.ts.map +0 -1
- package/esm/src/lib/logger.js +0 -27
- package/esm/src/lib/moodle.d.ts +0 -433
- package/esm/src/lib/moodle.d.ts.map +0 -1
- package/esm/src/lib/moodle.js +0 -1318
- package/esm/src/lib/session.d.ts +0 -8
- package/esm/src/lib/session.d.ts.map +0 -1
- package/esm/src/lib/session.js +0 -42
- package/esm/src/lib/token.d.ts +0 -38
- package/esm/src/lib/token.d.ts.map +0 -1
- package/esm/src/lib/token.js +0 -178
- package/esm/src/lib/types.d.ts +0 -189
- package/esm/src/lib/types.d.ts.map +0 -1
- package/esm/src/lib/types.js +0 -2
- package/esm/src/lib/utils.d.ts +0 -57
- package/esm/src/lib/utils.d.ts.map +0 -1
- package/esm/src/lib/utils.js +0 -129
- package/script/_dnt.polyfills.d.ts +0 -101
- package/script/_dnt.polyfills.d.ts.map +0 -1
- package/script/_dnt.polyfills.js +0 -130
- package/script/_dnt.shims.d.ts +0 -6
- package/script/_dnt.shims.d.ts.map +0 -1
- package/script/_dnt.shims.js +0 -65
- package/script/deno.d.ts +0 -25
- package/script/deno.d.ts.map +0 -1
- package/script/deno.js +0 -25
- package/script/package.json +0 -3
- package/script/src/commands/announcements.d.ts +0 -3
- package/script/src/commands/announcements.d.ts.map +0 -1
- package/script/src/commands/announcements.js +0 -140
- package/script/src/commands/assignments.d.ts +0 -3
- package/script/src/commands/assignments.d.ts.map +0 -1
- package/script/src/commands/assignments.js +0 -269
- package/script/src/commands/auth.d.ts +0 -3
- package/script/src/commands/auth.d.ts.map +0 -1
- package/script/src/commands/auth.js +0 -296
- package/script/src/commands/calendar.d.ts +0 -3
- package/script/src/commands/calendar.d.ts.map +0 -1
- package/script/src/commands/calendar.js +0 -185
- package/script/src/commands/courses.d.ts +0 -3
- package/script/src/commands/courses.d.ts.map +0 -1
- package/script/src/commands/courses.js +0 -354
- package/script/src/commands/forums.d.ts +0 -3
- package/script/src/commands/forums.d.ts.map +0 -1
- package/script/src/commands/forums.js +0 -324
- package/script/src/commands/grades.d.ts +0 -3
- package/script/src/commands/grades.d.ts.map +0 -1
- package/script/src/commands/grades.js +0 -127
- package/script/src/commands/materials.d.ts +0 -3
- package/script/src/commands/materials.d.ts.map +0 -1
- package/script/src/commands/materials.js +0 -419
- package/script/src/commands/quizzes.d.ts +0 -3
- package/script/src/commands/quizzes.d.ts.map +0 -1
- package/script/src/commands/quizzes.js +0 -277
- package/script/src/commands/skills.d.ts +0 -3
- package/script/src/commands/skills.d.ts.map +0 -1
- package/script/src/commands/skills.js +0 -112
- package/script/src/commands/upload.d.ts +0 -3
- package/script/src/commands/upload.d.ts.map +0 -1
- package/script/src/commands/upload.js +0 -64
- package/script/src/commands/videos.d.ts +0 -3
- package/script/src/commands/videos.d.ts.map +0 -1
- package/script/src/commands/videos.js +0 -342
- package/script/src/index.d.ts +0 -27
- package/script/src/index.d.ts.map +0 -1
- package/script/src/index.js +0 -167
- package/script/src/lib/auth.d.ts +0 -47
- package/script/src/lib/auth.d.ts.map +0 -1
- package/script/src/lib/auth.js +0 -269
- package/script/src/lib/config.d.ts +0 -6
- package/script/src/lib/config.d.ts.map +0 -1
- package/script/src/lib/config.js +0 -42
- package/script/src/lib/logger.d.ts +0 -3
- package/script/src/lib/logger.d.ts.map +0 -1
- package/script/src/lib/logger.js +0 -30
- package/script/src/lib/moodle.d.ts +0 -433
- package/script/src/lib/moodle.d.ts.map +0 -1
- package/script/src/lib/moodle.js +0 -1389
- package/script/src/lib/session.d.ts +0 -8
- package/script/src/lib/session.d.ts.map +0 -1
- package/script/src/lib/session.js +0 -45
- package/script/src/lib/token.d.ts +0 -38
- package/script/src/lib/token.d.ts.map +0 -1
- package/script/src/lib/token.js +0 -189
- package/script/src/lib/types.d.ts +0 -189
- package/script/src/lib/types.d.ts.map +0 -1
- package/script/src/lib/types.js +0 -3
- package/script/src/lib/utils.d.ts +0 -57
- package/script/src/lib/utils.d.ts.map +0 -1
- package/script/src/lib/utils.js +0 -175
- package/skills/openape/SKILL.md +0 -115
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.registerQuizzesCommand = registerQuizzesCommand;
|
|
7
|
-
const utils_js_1 = require("../lib/utils.js");
|
|
8
|
-
const moodle_js_1 = require("../lib/moodle.js");
|
|
9
|
-
const logger_js_1 = require("../lib/logger.js");
|
|
10
|
-
const index_js_1 = require("../index.js");
|
|
11
|
-
const token_js_1 = require("../lib/token.js");
|
|
12
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
-
function stripHtmlKeepLines(html) {
|
|
15
|
-
return html
|
|
16
|
-
.replace(/<br\s*\/?>/gi, "\n")
|
|
17
|
-
.replace(/<\/p>/gi, "\n")
|
|
18
|
-
.replace(/<[^>]+>/g, "")
|
|
19
|
-
.replace(/ /g, " ")
|
|
20
|
-
.replace(/&/g, "&")
|
|
21
|
-
.replace(/</g, "<")
|
|
22
|
-
.replace(/>/g, ">")
|
|
23
|
-
.replace(/\n{3,}/g, "\n\n")
|
|
24
|
-
.trim();
|
|
25
|
-
}
|
|
26
|
-
function parseQuestionHtml(html) {
|
|
27
|
-
const qtextMatch = html.match(/<div class="qtext">([\s\S]*?)<\/div>\s*<\/div>/);
|
|
28
|
-
const text = stripHtmlKeepLines(qtextMatch?.[1] ?? "");
|
|
29
|
-
const options = [];
|
|
30
|
-
const optionRegex = /data-region="answer-label">([\s\S]*?)<\/div>\s*<\/div>/g;
|
|
31
|
-
let match;
|
|
32
|
-
while ((match = optionRegex.exec(html)) !== null) {
|
|
33
|
-
options.push(stripHtmlKeepLines(match[1]));
|
|
34
|
-
}
|
|
35
|
-
return { text, options };
|
|
36
|
-
}
|
|
37
|
-
function parseSavedAnswer(html) {
|
|
38
|
-
const radioChecked = html.match(/<input type="radio"[^>]*value="(\d+)"[^>]*checked="checked"/);
|
|
39
|
-
if (radioChecked && radioChecked[1] !== "-1")
|
|
40
|
-
return radioChecked[1];
|
|
41
|
-
const checkboxChecked = [...html.matchAll(/<input type="checkbox"[^>]*name="[^"]*choice(\d+)"[^>]*checked="checked"/g)];
|
|
42
|
-
if (checkboxChecked.length > 0)
|
|
43
|
-
return checkboxChecked.map(m => m[1]);
|
|
44
|
-
// Match <input> with both name="*_answer" and type="text" in any attribute order
|
|
45
|
-
const textMatch = html.match(/<input[^>]*(?:name="[^"]*:_answer"|type="text")[^>]*(?:name="[^"]*:_answer"|type="text")[^>]*value="([^"]*)"/);
|
|
46
|
-
if (textMatch && textMatch[1] !== "")
|
|
47
|
-
return textMatch[1];
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
function parseQuizQuestions(questions) {
|
|
51
|
-
return Object.values(questions).map((q) => {
|
|
52
|
-
const parsed = parseQuestionHtml(q.html ?? "");
|
|
53
|
-
const savedAnswer = parseSavedAnswer(q.html ?? "");
|
|
54
|
-
return {
|
|
55
|
-
slot: q.slot,
|
|
56
|
-
type: q.type,
|
|
57
|
-
status: q.status,
|
|
58
|
-
stateclass: q.stateclass,
|
|
59
|
-
savedAnswer,
|
|
60
|
-
question: parsed.text,
|
|
61
|
-
options: parsed.options,
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
function registerQuizzesCommand(program) {
|
|
66
|
-
const quizzesCmd = program.command("quizzes");
|
|
67
|
-
quizzesCmd.description("Quiz operations");
|
|
68
|
-
function getOutputFormat(command) {
|
|
69
|
-
const opts = command.optsWithGlobals();
|
|
70
|
-
return opts.output || "json";
|
|
71
|
-
}
|
|
72
|
-
// Pure API context - no browser required (fast!)
|
|
73
|
-
async function createApiContext(options, command) {
|
|
74
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
75
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
76
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
77
|
-
const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
|
|
78
|
-
const baseDir = (0, utils_js_1.getBaseDir)();
|
|
79
|
-
const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
|
|
80
|
-
// Check if session exists
|
|
81
|
-
if (!node_fs_1.default.existsSync(sessionPath)) {
|
|
82
|
-
log.error("未找到登入 session。請先執行 'openape login' 進行登入。");
|
|
83
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
// Try to load WS token
|
|
87
|
-
const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
|
|
88
|
-
if (!wsToken) {
|
|
89
|
-
log.error("未找到 WS token。請先執行 'openape login' 進行登入。");
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
log,
|
|
94
|
-
session: {
|
|
95
|
-
wsToken,
|
|
96
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
quizzesCmd
|
|
101
|
-
.command("list")
|
|
102
|
-
.description("List incomplete quizzes in a course")
|
|
103
|
-
.argument("<course-id>", "Course ID")
|
|
104
|
-
.option("--all", "Include completed quizzes")
|
|
105
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
106
|
-
.action(async (courseId, options, command) => {
|
|
107
|
-
const output = getOutputFormat(command);
|
|
108
|
-
const apiContext = await createApiContext(options, command);
|
|
109
|
-
if (!apiContext) {
|
|
110
|
-
process.exitCode = 1;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const quizzes = await (0, moodle_js_1.getQuizzesByCoursesApi)(apiContext.session, [parseInt(courseId, 10)]);
|
|
114
|
-
// Default: only show incomplete quizzes
|
|
115
|
-
const filtered = options.all ? quizzes : quizzes.filter(q => !q.isComplete);
|
|
116
|
-
const formattedQuizzes = filtered.map(({ courseId, ...q }) => ({
|
|
117
|
-
...q,
|
|
118
|
-
timeClose: q.timeClose ? (0, utils_js_1.formatTimestamp)(q.timeClose) : null,
|
|
119
|
-
}));
|
|
120
|
-
(0, index_js_1.formatAndOutput)(formattedQuizzes, output, apiContext.log);
|
|
121
|
-
});
|
|
122
|
-
quizzesCmd
|
|
123
|
-
.command("list-all")
|
|
124
|
-
.description("List all incomplete quizzes across all courses")
|
|
125
|
-
.option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
|
|
126
|
-
.option("--all", "Include completed quizzes")
|
|
127
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
128
|
-
.action(async (options, command) => {
|
|
129
|
-
const output = getOutputFormat(command);
|
|
130
|
-
const apiContext = await createApiContext(options, command);
|
|
131
|
-
if (!apiContext) {
|
|
132
|
-
process.exitCode = 1;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const classification = options.level === "all" ? undefined : "inprogress";
|
|
136
|
-
const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
|
|
137
|
-
classification,
|
|
138
|
-
});
|
|
139
|
-
// Get quizzes via WS API (no browser needed!)
|
|
140
|
-
const courseIds = courses.map(c => c.id);
|
|
141
|
-
const apiQuizzes = await (0, moodle_js_1.getQuizzesByCoursesApi)(apiContext.session, courseIds);
|
|
142
|
-
// Build a map of courseId -> course for quick lookup
|
|
143
|
-
const courseMap = new Map(courses.map(c => [c.id, c]));
|
|
144
|
-
const allQuizzes = [];
|
|
145
|
-
for (const q of apiQuizzes) {
|
|
146
|
-
const course = courseMap.get(q.courseId);
|
|
147
|
-
if (course && (options.all || !q.isComplete)) {
|
|
148
|
-
allQuizzes.push({
|
|
149
|
-
courseName: course.fullname,
|
|
150
|
-
courseId: q.courseId,
|
|
151
|
-
name: q.name,
|
|
152
|
-
url: q.url,
|
|
153
|
-
quizid: q.quizid,
|
|
154
|
-
isComplete: q.isComplete,
|
|
155
|
-
attemptsUsed: q.attemptsUsed,
|
|
156
|
-
maxAttempts: q.maxAttempts,
|
|
157
|
-
timeClose: q.timeClose ? (0, utils_js_1.formatTimestamp)(q.timeClose) : null,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
apiContext.log.info(`\n總計發現 ${allQuizzes.length} 個測驗。`);
|
|
162
|
-
(0, index_js_1.formatAndOutput)(allQuizzes, output, apiContext.log);
|
|
163
|
-
});
|
|
164
|
-
quizzesCmd
|
|
165
|
-
.command("start")
|
|
166
|
-
.description("Start a new quiz attempt")
|
|
167
|
-
.argument("<quiz-id>", "Quiz ID")
|
|
168
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
169
|
-
.action(async (quizCmid, options, command) => {
|
|
170
|
-
const output = getOutputFormat(command);
|
|
171
|
-
const apiContext = await createApiContext(options, command);
|
|
172
|
-
if (!apiContext) {
|
|
173
|
-
process.exitCode = 1;
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
const result = await (0, moodle_js_1.startQuizAttemptApi)(apiContext.session, quizCmid);
|
|
178
|
-
apiContext.log.success(`Quiz attempt ${result.attempt.attemptid} started.`);
|
|
179
|
-
const attemptId = result.attempt.attemptid;
|
|
180
|
-
const data = await (0, moodle_js_1.getAllQuizAttemptDataApi)(apiContext.session, attemptId);
|
|
181
|
-
const questions = parseQuizQuestions(data.questions);
|
|
182
|
-
const outputData = [{
|
|
183
|
-
attemptId,
|
|
184
|
-
quizId: result.attempt.quizid,
|
|
185
|
-
state: result.attempt.state,
|
|
186
|
-
timeStart: (0, utils_js_1.formatTimestamp)(result.attempt.timestart),
|
|
187
|
-
timeFinish: result.attempt.timefinish
|
|
188
|
-
? (0, utils_js_1.formatTimestamp)(result.attempt.timefinish)
|
|
189
|
-
: null,
|
|
190
|
-
isPreview: result.attempt.preview,
|
|
191
|
-
totalQuestions: questions.length,
|
|
192
|
-
questions,
|
|
193
|
-
}];
|
|
194
|
-
(0, index_js_1.formatAndOutput)(outputData, output, apiContext.log);
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
apiContext.log.error(`Failed to start quiz attempt: ${error instanceof Error ? error.message : String(error)}`);
|
|
198
|
-
process.exitCode = 1;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
quizzesCmd
|
|
202
|
-
.command("info")
|
|
203
|
-
.description("Get quiz attempt data and questions")
|
|
204
|
-
.argument("<attempt-id>", "Quiz attempt ID")
|
|
205
|
-
.option("--page <number>", "Page number (-1 for all pages)", "-1")
|
|
206
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
207
|
-
.action(async (attemptId, options, command) => {
|
|
208
|
-
const output = getOutputFormat(command);
|
|
209
|
-
const apiContext = await createApiContext(options, command);
|
|
210
|
-
if (!apiContext) {
|
|
211
|
-
process.exitCode = 1;
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
const pageNumber = parseInt(options.page);
|
|
216
|
-
const data = pageNumber === -1
|
|
217
|
-
? await (0, moodle_js_1.getAllQuizAttemptDataApi)(apiContext.session, parseInt(attemptId))
|
|
218
|
-
: await (0, moodle_js_1.getQuizAttemptDataApi)(apiContext.session, parseInt(attemptId), pageNumber);
|
|
219
|
-
const questions = parseQuizQuestions(data.questions);
|
|
220
|
-
const outputData = [{
|
|
221
|
-
attemptId: data.attempt.attemptid,
|
|
222
|
-
quizId: data.attempt.quizid,
|
|
223
|
-
state: data.attempt.state,
|
|
224
|
-
totalQuestions: questions.length,
|
|
225
|
-
questions,
|
|
226
|
-
}];
|
|
227
|
-
apiContext.log.success(`Retrieved attempt ${data.attempt.attemptid}`);
|
|
228
|
-
(0, index_js_1.formatAndOutput)(outputData, output, apiContext.log);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
apiContext.log.error(`Failed to get attempt data: ${error instanceof Error ? error.message : String(error)}`);
|
|
232
|
-
process.exitCode = 1;
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
quizzesCmd
|
|
236
|
-
.command("save")
|
|
237
|
-
.description("Save answers for a quiz attempt")
|
|
238
|
-
.argument("<attempt-id>", "Quiz attempt ID")
|
|
239
|
-
.argument("<answers>", "Answers JSON: [{slot:1,answer:\"0\"}] multichoice=number, multichoices=\"0,2\", shortanswer=\"text\"")
|
|
240
|
-
.option("--submit", "Submit the attempt after saving")
|
|
241
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
242
|
-
.action(async (attemptId, answersJson, options, command) => {
|
|
243
|
-
const output = getOutputFormat(command);
|
|
244
|
-
const apiContext = await createApiContext(options, command);
|
|
245
|
-
if (!apiContext) {
|
|
246
|
-
process.exitCode = 1;
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
let answers;
|
|
250
|
-
try {
|
|
251
|
-
answers = JSON.parse(answersJson);
|
|
252
|
-
}
|
|
253
|
-
catch {
|
|
254
|
-
apiContext.log.error("Invalid answers JSON. Expected format: [{\"slot\":1,\"answer\":\"0\"},...]");
|
|
255
|
-
process.exitCode = 1;
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
// Get attempt data to find uniqueid and sequencecheck values
|
|
260
|
-
const attemptData = await (0, moodle_js_1.getAllQuizAttemptDataApi)(apiContext.session, parseInt(attemptId));
|
|
261
|
-
const uniqueId = attemptData.attempt.uniqueid ?? attemptData.attempt.attemptid;
|
|
262
|
-
const sequenceChecks = new Map();
|
|
263
|
-
for (const q of Object.values(attemptData.questions)) {
|
|
264
|
-
if (q.sequencecheck !== undefined) {
|
|
265
|
-
sequenceChecks.set(q.slot, q.sequencecheck);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const result = await (0, moodle_js_1.processQuizAttemptApi)(apiContext.session, parseInt(attemptId), uniqueId, answers, sequenceChecks, !!options.submit);
|
|
269
|
-
apiContext.log.success(`Attempt ${attemptId} state: ${result.state}`);
|
|
270
|
-
(0, index_js_1.formatAndOutput)([result], output, apiContext.log);
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
apiContext.log.error(`Failed to submit attempt: ${error instanceof Error ? error.message : String(error)}`);
|
|
274
|
-
process.exitCode = 1;
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../../src/src/commands/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2CpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuE5D"}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.registerSkillsCommand = registerSkillsCommand;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
-
const SKILL_NAME = "openape";
|
|
11
|
-
const GITHUB_RAW_URL = `https://raw.githubusercontent.com/mo7yw4ng/openape/refs/heads/main/skills/${SKILL_NAME}/SKILL.md`;
|
|
12
|
-
/**
|
|
13
|
-
* Known agent platforms and their skills directories
|
|
14
|
-
*/
|
|
15
|
-
const PLATFORMS = {
|
|
16
|
-
claude: { name: "Claude Code", path: node_path_1.default.join(node_os_1.default.homedir(), ".claude", "skills") },
|
|
17
|
-
codex: { name: "Codex CLI", path: node_path_1.default.join(node_os_1.default.homedir(), ".codex", "skills") },
|
|
18
|
-
opencode: { name: "OpenCode", path: node_path_1.default.join(node_os_1.default.homedir(), ".opencode", "skills") },
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Try to read SKILL.md from local project first (dev mode / bundled build),
|
|
22
|
-
* fallback to fetching from GitHub (when installed globally via npm).
|
|
23
|
-
*/
|
|
24
|
-
async function readSkillContent() {
|
|
25
|
-
// Try local path first (relative to this file's location)
|
|
26
|
-
try {
|
|
27
|
-
const base = node_path_1.default.dirname(new URL(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url).pathname);
|
|
28
|
-
const normalized = process.platform === "win32" ? base.replace(/^\//, "") : base;
|
|
29
|
-
// When running from source: src/commands/ → ../../skills/openape/SKILL.md
|
|
30
|
-
// When bundled by dnt into build/: esm/commands/ or script/ → ../../skills/openape/SKILL.md
|
|
31
|
-
const localPath = node_path_1.default.resolve(normalized, "..", "..", "skills", SKILL_NAME, "SKILL.md");
|
|
32
|
-
return await node_fs_1.default.promises.readFile(localPath, "utf-8");
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// import.meta.url may be unavailable in some environments, or file doesn't exist
|
|
36
|
-
}
|
|
37
|
-
// Fallback: fetch from GitHub
|
|
38
|
-
const res = await fetch(GITHUB_RAW_URL, { headers: { "User-Agent": "openape-cli" } });
|
|
39
|
-
if (!res.ok) {
|
|
40
|
-
throw new Error(`Failed to fetch skill from GitHub: ${res.status} ${res.statusText}`);
|
|
41
|
-
}
|
|
42
|
-
return res.text();
|
|
43
|
-
}
|
|
44
|
-
function registerSkillsCommand(program) {
|
|
45
|
-
const skills = program
|
|
46
|
-
.command("skills")
|
|
47
|
-
.description("Manage OpenApe skills for AI agents");
|
|
48
|
-
skills
|
|
49
|
-
.command("install [platform]")
|
|
50
|
-
.description("Install the OpenApe skill to an agent platform (claude, codex, opencode)")
|
|
51
|
-
.option("--all", "Detect installed agents and install to all")
|
|
52
|
-
.action(async (platform, opts) => {
|
|
53
|
-
try {
|
|
54
|
-
let targets = [];
|
|
55
|
-
if (opts?.all) {
|
|
56
|
-
for (const [key, info] of Object.entries(PLATFORMS)) {
|
|
57
|
-
const parentDir = node_path_1.default.dirname(info.path);
|
|
58
|
-
if (node_fs_1.default.existsSync(parentDir)) {
|
|
59
|
-
targets.push({ key, ...info });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (targets.length === 0) {
|
|
63
|
-
console.log("No supported agents detected. Supported platforms: " + Object.keys(PLATFORMS).join(", "));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else if (platform) {
|
|
68
|
-
const info = PLATFORMS[platform.toLowerCase()];
|
|
69
|
-
if (!info) {
|
|
70
|
-
console.error(`Unknown platform: ${platform}`);
|
|
71
|
-
console.error(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`);
|
|
72
|
-
process.exitCode = 1;
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
targets = [{ key: platform.toLowerCase(), ...info }];
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
console.error("Specify a platform or use --all.");
|
|
79
|
-
console.error(`Example: openape skills install claude`);
|
|
80
|
-
process.exitCode = 1;
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
console.log(`Fetching ${SKILL_NAME} skill...`);
|
|
84
|
-
const content = await readSkillContent();
|
|
85
|
-
for (const target of targets) {
|
|
86
|
-
console.log(`Installing to ${target.name} (${target.path})...`);
|
|
87
|
-
const destDir = node_path_1.default.join(target.path, SKILL_NAME);
|
|
88
|
-
await node_fs_1.default.promises.mkdir(destDir, { recursive: true });
|
|
89
|
-
await node_fs_1.default.promises.writeFile(node_path_1.default.join(destDir, "SKILL.md"), content, "utf-8");
|
|
90
|
-
console.log(` \x1b[32m✔\x1b[0m ${SKILL_NAME} installed!`);
|
|
91
|
-
}
|
|
92
|
-
console.log("\nDone!");
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
console.error(`\x1b[31mFailed to install skill: ${err.message}\x1b[0m`);
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
skills
|
|
100
|
-
.command("show")
|
|
101
|
-
.description("Print the raw SKILL.md content")
|
|
102
|
-
.action(async () => {
|
|
103
|
-
try {
|
|
104
|
-
const content = await readSkillContent();
|
|
105
|
-
process.stdout.write(content);
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
console.error(`\x1b[31mFailed: ${err.message}\x1b[0m`);
|
|
109
|
-
process.exitCode = 1;
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/src/commands/upload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2D5D"}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.registerUploadCommand = registerUploadCommand;
|
|
7
|
-
const utils_js_1 = require("../lib/utils.js");
|
|
8
|
-
const moodle_js_1 = require("../lib/moodle.js");
|
|
9
|
-
const auth_js_1 = require("../lib/auth.js");
|
|
10
|
-
const index_js_1 = require("../index.js");
|
|
11
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
13
|
-
function registerUploadCommand(program) {
|
|
14
|
-
const uploadCmd = program.command("upload");
|
|
15
|
-
uploadCmd.description("Upload files to Moodle draft area");
|
|
16
|
-
uploadCmd
|
|
17
|
-
.command("file")
|
|
18
|
-
.description("Upload a file to Moodle draft area")
|
|
19
|
-
.argument("<file-path>", "Path to the file to upload")
|
|
20
|
-
.option("--filename <name>", "Custom filename (default: original filename)")
|
|
21
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
22
|
-
.action(async (filePath, options, command) => {
|
|
23
|
-
const output = (0, utils_js_1.getOutputFormat)(command);
|
|
24
|
-
const apiContext = await (0, auth_js_1.createApiContext)(options, command);
|
|
25
|
-
if (!apiContext) {
|
|
26
|
-
process.exitCode = 1;
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// Resolve file path
|
|
30
|
-
const resolvedPath = node_path_1.default.resolve(filePath);
|
|
31
|
-
// Check if file exists
|
|
32
|
-
try {
|
|
33
|
-
await promises_1.default.access(resolvedPath);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
apiContext.log.error(`檔案不存在: ${filePath}`);
|
|
37
|
-
process.exitCode = 1;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
// Get file size
|
|
41
|
-
const stats = await promises_1.default.stat(resolvedPath);
|
|
42
|
-
const fileSizeKB = (0, utils_js_1.formatFileSize)(stats.size);
|
|
43
|
-
apiContext.log.info(`上傳檔案: ${node_path_1.default.basename(resolvedPath)} (${fileSizeKB} KB)`);
|
|
44
|
-
// Upload file
|
|
45
|
-
const result = await (0, moodle_js_1.uploadFileApi)(apiContext.session, resolvedPath, {
|
|
46
|
-
filename: options.filename,
|
|
47
|
-
});
|
|
48
|
-
if (!result.success) {
|
|
49
|
-
apiContext.log.error(`上傳失敗: ${result.error}`);
|
|
50
|
-
process.exitCode = 1;
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
apiContext.log.info(`✓ 上傳成功!Draft ID: ${result.draftId}`);
|
|
54
|
-
const uploadResult = {
|
|
55
|
-
success: true,
|
|
56
|
-
draft_id: result.draftId,
|
|
57
|
-
filename: node_path_1.default.basename(resolvedPath),
|
|
58
|
-
filesize: stats.size,
|
|
59
|
-
filesize_kb: (0, utils_js_1.formatFileSize)(stats.size),
|
|
60
|
-
message: "Use this draft ID for assignment submission or forum posts",
|
|
61
|
-
};
|
|
62
|
-
(0, index_js_1.formatAndOutput)(uploadResult, output, apiContext.log);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"videos.d.ts","sourceRoot":"","sources":["../../../src/src/commands/videos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgY5D"}
|