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